diff --git a/.dockerignore b/.dockerignore index 305b559..f4c17db 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,7 +2,9 @@ test/ storage/ dist/ client/dev/ +client/dist/ client/node_modules/ +client/fluid-player/node_modules/ __pycache__ venv .env @@ -19,3 +21,4 @@ docker-compose* node_modules npm-debug.log README.md +.history diff --git a/.flake8 b/.flake8 index 533d94a..a8fae70 100644 --- a/.flake8 +++ b/.flake8 @@ -6,6 +6,10 @@ ignore = F401 # flake struggles with endpoints returning tuples on exceptions E722 + # apparently it's both an antipattern and a best practice: + # https://www.flake8rules.com/rules/W503.html + # just python things + W503 exclude = .git, __pycache__, diff --git a/.gitignore b/.gitignore index 94a0099..6d3d19e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ flask.cfg /config.py redis_map.py -# Dev only files -test/ .idea dev_* @@ -15,7 +13,7 @@ dev_* client/dev # Dev file server -storage/ +/storage/ # Javascript packages node_modules @@ -164,3 +162,5 @@ dmypy.json # Cython debug symbols cython_debug/ + +.history diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 0a2c97d..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,16 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 38b88246ccc552bffaaf54259d064beeee434539 # frozen: v4.0.1 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - repo: https://github.com/pycqa/flake8 - rev: "cbeb4c9c4137cff1568659fcc48e8b85cddd0c8d" # frozen: 4.0.1 - hooks: - - id: flake8 - - repo: https://github.com/pre-commit/mirrors-autopep8 - rev: "7d14f78422aef2153a90e33373d2515bcc99038d" # frozen: v1.5.7 - hooks: - - id: autopep8 diff --git a/Dockerfile b/Dockerfile index 0cb1a1a..2544391 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,18 @@ -FROM nikolaik/python-nodejs:python3.12-nodejs18 - -RUN apt-get update && apt-get install -y libpq-dev curl jq - -RUN curl -s -L $(curl https://api.github.com/repos/tus/tusd/releases/latest -s | jq '.assets[] | select(.name=="tusd_linux_amd64.tar.gz") | .browser_download_url' -r) | tar -xzvf - -C /usr/local/bin/ --strip-components=1 tusd_linux_amd64/tusd && chmod +x /usr/local/bin/tusd +FROM python:3.12 WORKDIR /app +RUN apt-get update && apt-get install -y libpq-dev curl jq + COPY requirements.txt requirements.txt RUN pip3 install -r requirements.txt -RUN npm install -g npm - -RUN mkdir client -COPY ./client /app/client - -RUN cd client && npm ci --also=dev && cd .. - COPY . /app ENV LANG=C.UTF-8 ARG GIT_COMMIT_HASH ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined} +ARG BUILD_DATE +ENV BUILD_DATE=${BUILD_DATE:-undefined} CMD python -m src daemon diff --git a/Dockerfile-ci b/Dockerfile-ci new file mode 100644 index 0000000..3c8dbf6 --- /dev/null +++ b/Dockerfile-ci @@ -0,0 +1,42 @@ +# Client build +FROM node:22.13 AS client-builder + +WORKDIR /app/client + +# kind of retarded but such is a price for inlining dependencies +COPY client/fluid-player/package.json client/fluid-player/package-lock.json ./fluid-player/ + +COPY client/package.json client/package-lock.json ./ + +RUN npm ci --include=dev + +COPY schema ../schema + +COPY config.json ../config.json + +COPY client ./ + +RUN npm run build + +# Server build +FROM python:3.12 AS server-builder + +RUN apt-get update \ + && apt-get install -y libpq-dev + +WORKDIR /app + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY . /app + +COPY --from=client-builder /app/client/dist ./client/dist + +ENV LANG=C.UTF-8 +ARG GIT_COMMIT_HASH +ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined} +ARG BUILD_DATE +ENV BUILD_DATE=${BUILD_DATE:-undefined} + +CMD python -m src daemon diff --git a/Dockerfile-client b/Dockerfile-client new file mode 100644 index 0000000..fae299a --- /dev/null +++ b/Dockerfile-client @@ -0,0 +1,13 @@ +FROM node:22.13 +WORKDIR /app/client +CMD [ "npm", "run", "build" ] + +RUN npm install -g npm +COPY ./ /app +RUN npm ci --include=dev + +ENV LANG=C.UTF-8 +ARG GIT_COMMIT_HASH +ENV GIT_COMMIT_HASH=${GIT_COMMIT_HASH:-undefined} +ARG BUILD_DATE +ENV BUILD_DATE=${BUILD_DATE:-undefined} diff --git a/client/.dockerignore b/client/.dockerignore deleted file mode 100644 index f3c73a0..0000000 --- a/client/.dockerignore +++ /dev/null @@ -1,29 +0,0 @@ -# webpack output -**/dev -**/dist - -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.code-workspace -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/charts -**/docker-compose* -**/compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -README.md diff --git a/client/.vscode/extensions.json b/client/.vscode/extensions.json deleted file mode 100644 index 1612a87..0000000 --- a/client/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": [] -} diff --git a/client/.vscode/settings.json b/client/.vscode/settings.json index 6a2ff3b..8b32f58 100644 --- a/client/.vscode/settings.json +++ b/client/.vscode/settings.json @@ -1,14 +1,9 @@ { + "typescript.tsdk": "./node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, "files.exclude": { "node_modules": true }, - // this option does work and is required for emmet in jinja to work - "files.associations": { - "*.html": "jinja-html" - }, - "emmet.includeLanguages": { - "jinja-html": "html" - }, "search.exclude": { "**/node_modules": true, "**/bower_components": true, @@ -19,9 +14,6 @@ "javascript.preferences.importModuleSpecifierEnding": "js", "javascript.preferences.quoteStyle": "double", "javascript.format.semicolons": "insert", - "[jinja-html]": { - "editor.tabSize": 2 - }, "[javascript]": { "editor.tabSize": 2 }, diff --git a/client/Dockerfile b/client/Dockerfile deleted file mode 100644 index f0a5c79..0000000 --- a/client/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM node:16.14 - -ENV NODE_ENV=production - -WORKDIR /app - -COPY ["package.json", "package-lock.json", "/app/"] - -RUN npm install -g npm -RUN npm ci --also=dev - -COPY . /app - -CMD [ "npm", "run", "build" ] \ No newline at end of file diff --git a/client/Dockerfile.dev b/client/Dockerfile.dev deleted file mode 100644 index d6beb4e..0000000 --- a/client/Dockerfile.dev +++ /dev/null @@ -1,15 +0,0 @@ -# syntax=docker/dockerfile:1 - -FROM node:12.22 - -ENV NODE_ENV=development - -WORKDIR /app - -COPY ["package.json", "package-lock.json*", "./"] - -RUN npm install - -COPY . . - -CMD ["npm", "run", "dev"] diff --git a/client/configs/build-templates.js b/client/configs/build-templates.js deleted file mode 100644 index 49f560b..0000000 --- a/client/configs/build-templates.js +++ /dev/null @@ -1,92 +0,0 @@ -const path = require("path"); -const fse = require("fs-extra"); -const HTMLWebpackPlugin = require("html-webpack-plugin"); - -/** - * @typedef BuildOptions - * @property {string} fileExtension - * @property {string} outputPrefix - * @property {HTMLWebpackPlugin.Options} pluginOptions Webpack plugin options. - */ - -/** */ -class TemplateFile { - /** - * @param {fse.Dirent} dirent - * @param {string} path Absolute path to the file. - */ - constructor(dirent, path) { - this.dirent = dirent; - this.path = path; - } -} - -/** - * Builds an array of HTML webpack plugins from the provided folder. - * @param {string} basePath Absolute path to the template folder. - * @param {BuildOptions} options Build optons. - */ -function buildHTMLWebpackPluginsRecursive(basePath, options) { - /** - * @type {HTMLWebpackPlugin[]} - */ - const plugins = []; - const files = walkFolder(basePath); - - files.forEach((file) => { - const isTemplateFile = file.dirent.isFile() && file.path.endsWith(`${options.fileExtension}`); - - if (isTemplateFile) { - const outputBase = path.relative(basePath, file.path); - const outputPath = path.join(path.basename(basePath), outputBase); - - const webpackPlugin = new HTMLWebpackPlugin({ - ...options.pluginOptions, - template: file.path, - filename: outputPath, - }); - - plugins.push(webpackPlugin); - } - }); - - return plugins; -} - -/** - * @param {string} folderPath Absolute path to the folder. - * @param {TemplateFile[]} files - */ -function walkFolder(folderPath, files = [], currentCount = 0) { - const nestedLimit = 1000; - const folderContents = fse.readdirSync(folderPath, { withFileTypes: true }); - - folderContents.forEach((entry) => { - const file = entry.isFile() && entry; - const folder = entry.isDirectory() && entry; - - if (file) { - const filePath = path.join(folderPath, file.name); - files.push(new TemplateFile(file, filePath)); - return; - } - - if (folder) { - currentCount++; - - if (currentCount > nestedLimit) { - throw new Error(`The folder at "${folderPath}" contains more than ${nestedLimit} folders.`); - } - - const newFolderPath = path.join(folderPath, folder.name); - - return walkFolder(newFolderPath, files, currentCount); - } - }); - - return files; -} - -module.exports = { - buildHTMLWebpackPluginsRecursive, -}; diff --git a/client/configs/emmet/snippets.json b/client/configs/emmet/snippets.json deleted file mode 100644 index 8ec5f82..0000000 --- a/client/configs/emmet/snippets.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "html": { - "snippets": {} - } -} diff --git a/client/configs/parse-config.mjs b/client/configs/parse-config.mjs new file mode 100644 index 0000000..7433604 --- /dev/null +++ b/client/configs/parse-config.mjs @@ -0,0 +1,42 @@ +// @ts-check +import path from "node:path"; +import fs from "node:fs"; +import Ajv from "ajv"; + +const ajv = new Ajv(); + +/** + * @returns {import("../../schema/config.ts").IConfiguration} + */ +export function parseConfiguration() { + const configSchemaPath = path.resolve( + __dirname, + "..", + "..", + "schema", + "config.schema.json" + ); + const configPath = path.resolve(__dirname, "..", "..", "config.json"); + const configFileSchemaContent = fs.readFileSync(configSchemaPath, { + encoding: "utf8", + }); + const configFileContent = fs.readFileSync(configPath, { encoding: "utf8" }); + const configSchema = JSON.parse(configFileSchemaContent); + /** + * @type {import("../../schema/config.ts").IConfiguration} + */ + const config = JSON.parse(configFileContent); + + const isValid = ajv.validate(configSchema, config); + + if (!isValid) { + /** + * @type {import("ajv").ErrorObject, unknown>[]} + */ + // @ts-expect-error + const errors = ajv.errors + throw new AggregateError(errors, "Failed to validate the config.") + } + + return config; +} diff --git a/client/configs/vars.js b/client/configs/vars.js deleted file mode 100644 index 997dea1..0000000 --- a/client/configs/vars.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require("path"); - -require("dotenv").config({ - path: path.resolve(__dirname, "..", ".."), -}); - -const kemonoSite = process.env.KEMONO_SITE || "http://localhost:5000"; -const nodeEnv = process.env.NODE_ENV || "production"; -const iconsPrepend = process.env.ICONS_PREPEND || ""; -const bannersPrepend = process.env.BANNERS_PREPEND || ""; -const thumbnailsPrepend = process.env.THUMBNAILS_PREPEND || ""; -const creatorsLocation = process.env.CREATORS_LOCATION || ""; - -module.exports = { - kemonoSite, - nodeEnv, - iconsPrepend, - bannersPrepend, - thumbnailsPrepend, - creatorsLocation, -}; diff --git a/client/configs/vars.mjs b/client/configs/vars.mjs new file mode 100644 index 0000000..7c61414 --- /dev/null +++ b/client/configs/vars.mjs @@ -0,0 +1,49 @@ +// @ts-check +import { parseConfiguration } from "./parse-config.mjs"; + +export const configuration = parseConfiguration(); +export const { webserver } = configuration; +export const apiServerBaseURL = configuration.webserver.base_url; +export const sentryDSN = configuration.sentry_dsn_js; +export const apiServerPort = !apiServerBaseURL + ? undefined + : configuration.webserver?.port; +export const siteName = configuration.webserver.ui.home.site_name || "Kemono"; +export const favicon = configuration.webserver.ui.favicon || "./static/favicon.ico"; +export const homeBackgroundImage = + configuration.webserver.ui.home.home_background_image; +export const homeMascotPath = configuration.webserver.ui.home.mascot_path; +export const homeLogoPath = configuration.webserver.ui.home.logo_path; +export const homeWelcomeCredits = configuration.webserver.ui.home.welcome_credits; +export const homeAnnouncements = configuration.webserver.ui.home.announcements; +// TODO: in development it should point to webpack server +export const kemonoSite = configuration.site || "http://localhost:5000"; +export const paysiteList = configuration.webserver.ui.config.paysite_list; +export const artistsOrCreators = + configuration.webserver.ui.config.artists_or_creators ?? "Artists"; +export const disableDMs = configuration.webserver.ui.sidebar?.disable_dms ?? true; +export const disableFAQ = configuration.webserver.ui.sidebar?.disable_faq ?? true; +export const disableFilehaus = + configuration.webserver.ui.sidebar?.disable_filehaus ?? true; +export const sidebarItems = configuration.webserver.ui.sidebar_items; +export const footerItems = configuration.webserver.ui.footer_items; +export const bannerGlobal = configuration.webserver.ui.banner?.global; +export const AnnouncementBannerGlobal = configuration.webserver.ui.banner?.announcement_global; +export const bannerWelcome = configuration.webserver.ui.banner?.welcome; +export const headerAd = configuration.webserver.ui.ads?.header; +export const middleAd = configuration.webserver.ui.ads?.middle; +export const footerAd = configuration.webserver.ui.ads?.footer; +export const sliderAd = configuration.webserver.ui.ads?.slider; +export const videoAd = configuration.webserver.ui.ads?.video; +export const isArchiveServerEnabled = configuration.archive_server?.enabled ?? false; +export const analyticsEnabled = configuration.webserver.ui.matomo?.enabled ?? false; +export const analyticsCode = configuration.webserver.ui.matomo?.plain_code; +export const iconsPrepend = webserver.ui.files_url_prepend?.icons_base_url || ""; +export const bannersPrepend = webserver.ui.files_url_prepend?.banners_base_url || ""; +export const thumbnailsPrepend = + webserver.ui.files_url_prepend?.thumbnails_base_url || ""; +export const isFileServingEnabled = Boolean( + configuration.archive_server?.file_serving_enabled +); +export const gitCommitHash = process.env.GIT_COMMIT_HASH; +export const buildDate = process.env.BUILD_DATE; diff --git a/client/extra.d.ts b/client/extra.d.ts new file mode 100644 index 0000000..f741f1f --- /dev/null +++ b/client/extra.d.ts @@ -0,0 +1,10 @@ +// required for typescript not to choke on css modules +declare module '*.scss' { + const classes: { [key: string]: string }; + export = classes; +} + +declare module '*.yaml' { + const data: any + export default data +} diff --git a/client/fluid-player/.babelrc b/client/fluid-player/.babelrc new file mode 100644 index 0000000..1320b9a --- /dev/null +++ b/client/fluid-player/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env"] +} diff --git a/client/fluid-player/.editorconfig b/client/fluid-player/.editorconfig new file mode 100644 index 0000000..7d87ba5 --- /dev/null +++ b/client/fluid-player/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[package.json] +indent_size = 2 + +[*.{js, css}] +indent_size = 4 diff --git a/client/fluid-player/.github/ISSUE_TEMPLATE/bug_report.md b/client/fluid-player/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7a4f71f --- /dev/null +++ b/client/fluid-player/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,41 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug, needs triage +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots or Links** +If applicable, add screenshots or links to help explain your problem. **DO NOT** include links to NSFW webpages. Preferably create code to reproduce using https://jsfiddle.net + +**Desktop (please complete the following information if relevant):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information if relevant):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Affected version** +For example, v3.0.0 + +**Additional context** +Add any other context about the problem here. diff --git a/client/fluid-player/.github/ISSUE_TEMPLATE/feature_request.md b/client/fluid-player/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e2c46f1 --- /dev/null +++ b/client/fluid-player/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: feature request, needs triage +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/client/fluid-player/.github/ISSUE_TEMPLATE/question.md b/client/fluid-player/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..1962d96 --- /dev/null +++ b/client/fluid-player/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,13 @@ +--- +name: Question +about: Question or clarification about Fluid Player or related technologies +title: '' +labels: question, needs triage +assignees: '' + +--- + +A clear and concise description of what the problem is. + +**Additional context** +Add any other context or screenshots about the request here. diff --git a/client/fluid-player/.github/pull_request_template.md b/client/fluid-player/.github/pull_request_template.md new file mode 100644 index 0000000..ce16278 --- /dev/null +++ b/client/fluid-player/.github/pull_request_template.md @@ -0,0 +1,16 @@ +**IMPORTANT: Please do not create a Pull Request without creating an issue first.** + +*Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request.* + +## Proposed Changes + +1. ... +2. ... +3. ... + +## Relevant issues + +Closes #... +Closes #... +Closes #... +Related #... diff --git a/client/fluid-player/.gitignore b/client/fluid-player/.gitignore new file mode 100644 index 0000000..fa8c7d7 --- /dev/null +++ b/client/fluid-player/.gitignore @@ -0,0 +1,25 @@ +node_modules/ +dist/ +dist-cdn/ + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# E2E +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/client/fluid-player/.npmignore b/client/fluid-player/.npmignore new file mode 100644 index 0000000..73d1c4e --- /dev/null +++ b/client/fluid-player/.npmignore @@ -0,0 +1,9 @@ +test +.idea +e2e +.editorconfig +yarn.lock +webpack.config.js +dist +dist-cdn +.github diff --git a/client/fluid-player/CHANGELOG.md b/client/fluid-player/CHANGELOG.md new file mode 100644 index 0000000..2216964 --- /dev/null +++ b/client/fluid-player/CHANGELOG.md @@ -0,0 +1,372 @@ +# CHANGELOG + +## 3.46.0 (2024-12-12) +* [Pull #854](https://github.com/fluid-player/fluid-player/pull/854) Add support for automatic landscape screen orientation + +## 3.45.0 (2024-12-09) +* [Pull #859](https://github.com/fluid-player/fluid-player/pull/859) Create E2E project for Fluid Player +* [Pull #857](https://github.com/fluid-player/fluid-player/pull/857) Live Indicator + +## 3.44.0 (2024-11-14) +* [Pull #851](https://github.com/fluid-player/fluid-player/pull/851) Mobile on click show control bar instead of pausing video + +## 3.43.0 (2024-11-13) +* [Pull #855](https://github.com/fluid-player/fluid-player/pull/855) Fluidplayer doesn't play Wrapper ads + +## 3.42.0 (2024-11-05) +* [Pull #836](https://github.com/fluid-player/fluid-player/pull/836) FallbackVastTags may not be working + +## 3.41.0 (2024-11-04) +* [Pull #850](https://github.com/fluid-player/fluid-player/pull/850) New event system breaks current implementations + +## 3.40.0 (2024-10-24) +* [Pull #844](https://github.com/fluid-player/fluid-player/pull/844) MiniPlayer Follow Up > Improving UX and be Google compliant +* [Pull #846](https://github.com/fluid-player/fluid-player/pull/846) Rounded corners on player +* [Pull #848](https://github.com/fluid-player/fluid-player/pull/848) Uncaught TypeError: Cannot read properties of undefined (reading 'Parser') when using subtitles + +## 3.39.0 (2024-10-15) +* [Pull #842](https://github.com/fluid-player/fluid-player/pull/842) Ads events +* [Pull #843](https://github.com/fluid-player/fluid-player/pull/843) Mid Roll is not working with Live stream videos + +## 3.38.0 (2024-10-02) +* [Pull #838](https://github.com/fluid-player/fluid-player/pull/838) TheatreAdvanced is not working and breaks player + +## 3.37.0 (2024-09-17) +* [Pull #821](https://github.com/fluid-player/fluid-player/pull/821) Added support for VAST ViewableImpression +* [Pull #833](https://github.com/fluid-player/fluid-player/pull/833) doubleclickFullscreen not working + +## 3.36.0 (2024-09-05) +* [Pull #832](https://github.com/fluid-player/fluid-player/pull/832) Show quality change icon in player when working with hls (m3u8) + +## 3.35.0 (2024-08-01) +* [Pull #825](https://github.com/fluid-player/fluid-player/pull/825) Fix image freezing +* [Pull #826](https://github.com/fluid-player/fluid-player/pull/826) Ads cannot be displayed when XML content has two Creative tags + +## 3.34.0 (2024-07-11) +* [Pull #819](https://github.com/fluid-player/fluid-player/pull/819) When users don't have suggested videos, there's a console error + +## 3.33.0 (2024-07-02) +* [Pull #818](https://github.com/fluid-player/fluid-player/pull/818) Suggested Videos Feature in Fluid Player + +## 3.32.0 (2024-03-21) +* [Pull #804](https://github.com/fluid-player/fluid-player/pull/804) htmlOnPauseBlock not being displayed with last version of FP + +## 3.31.0 (2024-01-25) +* [Pull #788](https://github.com/fluid-player/fluid-player/pull/788) Fluid Player doesn't work with Next.js +* [Pull #791](https://github.com/fluid-player/fluid-player/pull/791) Impression events aren't called for some VAST tags within the VAST wrapper chain + +## 3.30.0 (2024-01-11) +* [Pull #789](https://github.com/fluid-player/fluid-player/pull/789) Uncaught TypeError: Cannot set properties of null (setting 'slotIframe') while testing on VOD with VPAID Non Linear + +## 3.29.0 (2023-12-14) +* [Pull #783](https://github.com/fluid-player/fluid-player/pull/783) Fix test cases in the development sever +* [Pull #785](https://github.com/fluid-player/fluid-player/pull/785) FluidPlayer > Error > onPauseRoll In Video Banner VAST + +## 3.28.0 (2023-11-16) +* [Pull #776](https://github.com/fluid-player/fluid-player/pull/776) Fluid Player doesn't work with shadow DOM + +## 3.27.0 (2023-10-19) +* [Pull #771](https://github.com/fluid-player/fluid-player/pull/771) Method loadVpaid create frame without body + +## 3.26.0 (2023-10-12) +* [Pull #768](https://github.com/fluid-player/fluid-player/pull/768) Stretch posterImage to playerwindow +* [Pull #769](https://github.com/fluid-player/fluid-player/pull/769) Events don't differ from Ad and Main video + +## 3.25.0 (2023-09-21) +* [Pull #766](https://github.com/fluid-player/fluid-player/pull/766) adClickable and CTA parameters don't work when 2 VAST ad is loaded + +## 3.24.0 (2023-09-08) +* [Pull #763](https://github.com/fluid-player/fluid-player/pull/763) Player can't be restarted by destroying it and initializing it again + +## 3.23.0 (2023-08-28) +* [Pull #757](https://github.com/fluid-player/fluid-player/pull/757) Preload doesn't work for .m3u8 files when the player is serving in-stream ads +* [Pull #760](https://github.com/fluid-player/fluid-player/pull/760) Lighthouse says: Does not use passive listeners to improve scrolling performance + +## 3.22.0 (2023-08-17) +* [Pull #755](https://github.com/fluid-player/fluid-player/pull/755) Percentages are not accepted for "timer" property + +## 3.21.0 (2023-08-08) +* [Pull #748](https://github.com/fluid-player/fluid-player/pull/748) CurrentTime reset after switch HLS source on IOS +* [Pull #752](https://github.com/fluid-player/fluid-player/pull/752) Add "controlForwardBackward" setting inside the player instead of the control bar + +## 3.20.0 (2023-07-24) +* [Pull #749](https://github.com/fluid-player/fluid-player/pull/749) Mouse disappears +* [Pull #750](https://github.com/fluid-player/fluid-player/pull/750) In mobile and when using .m3u8 files, user needs to click the player twice in order to play the video + +## 3.19.0 (2023-07-12) +* [Pull #746](https://github.com/fluid-player/fluid-player/pull/746) Could not find a declaration file for module 'fluid-player' + +## 3.18.0 (2023-06-30) +* [Pull #744](https://github.com/fluid-player/fluid-player/pull/744) Failed to resolve import "cheerio/lib/api/traversing" + +## 3.17.0 (2023-06-16) +* [Pull #734](https://github.com/fluid-player/fluid-player/pull/734) MiniPlayer Mobile support +* [Pull #735](https://github.com/fluid-player/fluid-player/pull/735) MiniPlayer position configuration +* [Pull #739](https://github.com/fluid-player/fluid-player/pull/739) MiniPlayer Activate miniplayer when scrolling goes out of viewport +* [Pull #740](https://github.com/fluid-player/fluid-player/pull/740) MiniPlayer Allowing trigger MiniPlayer by code +* [Pull #736](https://github.com/fluid-player/fluid-player/pull/736) Fluid player doesn't track clicks on some sites + +## 3.16.0 (2023-05-31) +* [Pull #721](https://github.com/fluid-player/fluid-player/pull/721) Fluid Player loads midRoll even if timer is longer than main video +* [Pull #732](https://github.com/fluid-player/fluid-player/pull/732) MiniPlayer MVP + +## 3.15.0 (2023-05-09) +* [Pull #727](https://github.com/fluid-player/fluid-player/pull/727) Player required 2 clicks to play video New Iphone 11 Pro Chrome + +## 3.14.0 (2023-04-27) +* [Pull #722](https://github.com/fluid-player/fluid-player/pull/722) Support Video Waterfall VAST Response + +## 3.13.0 (2023-03-09) +* [Pull #714](https://github.com/fluid-player/fluid-player/pull/714) Support Video Waterfall VAST Response + +## 3.12.0 (2023-01-10) +* [Pull #707](https://github.com/fluid-player/fluid-player/pull/707) Decrease volumes & revenue on In-stream zones + +## 3.11.1 (2023-01-03) +* Update dependencies + +## 3.11.0 (2023-01-03) +* [Pull #614](https://github.com/fluid-player/fluid-player/pull/614) Add check for process being defined +* [Pull #561](https://github.com/fluid-player/fluid-player/pull/561) Allow customizing playbackRates + +## 3.10.0 (2022-12-15) +* [Pull #687](https://github.com/fluid-player/fluid-player/pull/687) LocalStorage not available in Chrome incognito mode is breaking JS +* [Pull #704](https://github.com/fluid-player/fluid-player/pull/704) In ad serving, the skip button when is without time delay it should trigger immediately the "skip ad" + +## 3.9.0 (2022-10-18) +* [Pull #688](https://github.com/fluid-player/fluid-player/pull/688) Selecting subtitle by default + +## 3.8.0 (2022-10-07) +* [Pull #685](https://github.com/fluid-player/fluid-player/pull/685) Fluidplayer > 'autoHide' in Desktop only works when the cursor is on top of the player. +* [Pull #689](https://github.com/fluid-player/fluid-player/pull/689) Mobile view_ Error in console and wrong positioning of the CTA text + +## 3.7.0 (2022-09-28) +* [Pull #650](https://github.com/fluid-player/fluid-player/pull/650) Play main video after preRoll ends not work + +## 3.6.0 (2022-08-24) +* [Pull #666](https://github.com/fluid-player/fluid-player/pull/666) CTA Overlay > cannot be removed by publisher + +## 3.5.0 (2022-07-20) +* [Pull #669](https://github.com/fluid-player/fluid-player/pull/669) Video CTA / Fluid Player +* [Pull #671](https://github.com/fluid-player/fluid-player/pull/671) Video CTA - update FP to support new CTA structure from VAST Tag + +## 3.4.0 (2022-07-05) +* [Pull #652](https://github.com/fluid-player/fluid-player/pull/652) Video on demand and linear VPAID does not work on iOS Safari +* [Pull #664](https://github.com/fluid-player/fluid-player/pull/664) Video CTA / Fluid Player + +## 3.3.0 (2022-06-22) +* [Pull #656](https://github.com/fluid-player/fluid-player/pull/656) Video CTA / Fluid Player + +## 3.2.1 (2022-05-17) +* Update dependencies + +## 3.2.0 +* [Pull #641](https://github.com/fluid-player/fluid-player/pull/641) FluidPlayer > Streaming files compatibility + +## 3.1.0 +* [Pull #619](https://github.com/fluid-player/fluid-player/pull/619) Looping videos show loading spinner for a split second before looping +* [Pull #621](https://github.com/fluid-player/fluid-player/pull/621) FluidPlayer> Mouse disappears +* [Pull #622](https://github.com/fluid-player/fluid-player/pull/622) contextMenu.links labels are not working after play +* [Pull #628](https://github.com/fluid-player/fluid-player/pull/628) Overlay html over video (also in fullscreen) + +## 3.0.4 +* [Pull #489](https://github.com/fluid-player/fluid-player/pull/489) Fix issues with nonLinear ads not closing + +## 3.0.3 +* [Pull #478](https://github.com/fluid-player/fluid-player/pull/478) Ensure options is object, fix dash debug + +## 3.0.2 +* [Pull #473](https://github.com/fluid-player/fluid-player/pull/473) Responsive test case +* [Pull #474](https://github.com/fluid-player/fluid-player/pull/474) Fix issues related to missing ended event +* [Pull #472](https://github.com/fluid-player/fluid-player/pull/472) Small refactor, add auto-hide test case +* [Pull #471](https://github.com/fluid-player/fluid-player/pull/471) Move X seconds forward/back +* [Pull #470](https://github.com/fluid-player/fluid-player/pull/470) Custom context +* [Pull #469](https://github.com/fluid-player/fluid-player/pull/469) Prevent hidden menus after cssmin optimization +* [Pull #468](https://github.com/fluid-player/fluid-player/pull/468) Config callbacks + +## 3.0.1 +* [Pull #457](https://github.com/fluid-player/fluid-player/pull/430) Fix ad skip button not showing properly +* [Pull #450](https://github.com/fluid-player/fluid-player/pull/430) Static thumbnail configuration +* [Pull #458](https://github.com/fluid-player/fluid-player/pull/430) Fix dash.js initialization and swap Vtt.js to Videojs fork + +## 3.0.0 +* [Pull #441](https://github.com/fluid-player/fluid-player/pull/441) Major release - see pull request for full changelist. + +## 2.4.11 +* [Pull #430](https://github.com/fluid-player/fluid-player/pull/430) Add destroy function + +## 2.4.10 +* [Pull #399](https://github.com/fluid-player/fluid-player/pull/399) Adding VR Features to player (experimental) + +## 2.4.9 +* [Pull #398](https://github.com/fluid-player/fluid-player/pull/398) Add support for VPAID (2.0) + +## 2.4.8 +* [Pull #374](https://github.com/fluid-player/fluid-player/pull/374) Skip ad button on VAST preroll opening a new blank tab + +## 2.4.7 +* [Pull #361](https://github.com/fluid-player/fluid-player/pull/361) Adding subtitles, multiple ad-roll, fallback vast ad, fixing dash playback, double click to fullscreen +* [Pull #354](https://github.com/fluid-player/fluid-player/pull/354) VAST Multiple mediafile support, announce proper error codes and some bug fixes +* [Pull #356](https://github.com/fluid-player/fluid-player/pull/356) Seeked and ended html5 event listeners + +## 2.4.6 +* [Pull #358](https://github.com/fluid-player/fluid-player/pull/358) fix bug with dash js api + +## 2.4.5 +* [Pull #325](https://github.com/fluid-player/fluid-player/pull/325) Add poster image size option (posterImageSize) +* [Pull #330](https://github.com/fluid-player/fluid-player/pull/330) Add showPlayButton config to display Play button on ad +* [Pull #306](https://github.com/fluid-player/fluid-player/pull/306) Remove unsupported browser layout parts +* [Pull #331](https://github.com/fluid-player/fluid-player/pull/331) Add ability to change controls titles +* [Pull #332](https://github.com/fluid-player/fluid-player/pull/332) Fix multiple videos play +* [Pull #335](https://github.com/fluid-player/fluid-player/pull/335) Improve timecode +* [Pull #336](https://github.com/fluid-player/fluid-player/pull/336) Add title +* [Pull #334](https://github.com/fluid-player/fluid-player/pull/334) Add ability to set preload value + +## 2.4.4 +* [Pull #289](https://github.com/fluid-player/fluid-player/pull/289) Fix window.getComputedStyle call on null +* [Pull #290](https://github.com/fluid-player/fluid-player/pull/290) Prevent multi click event on download btn +* [Pull #293](https://github.com/fluid-player/fluid-player/pull/293) Check if Hls already exposed in window + +## 2.4.3 +* [Pull #266](https://github.com/fluid-player/fluid-player/pull/266) Fix play pause issue on mobile +* [Pull #268](https://github.com/fluid-player/fluid-player/pull/268) Fix iOS scrubbing bugs +* [Pull #270](https://github.com/fluid-player/fluid-player/pull/270) Fix for iOS switching to unsupported file types + +## 2.4.2 +* [Pull #235](https://github.com/fluid-player/fluid-player/pull/235) [Pull #236](https://github.com/fluid-player/fluid-player/pull/236) Fix the controls randomly disappearing on scrubbing clicks + +## 2.4.1 +* [Pull #228](https://github.com/fluid-player/fluid-player/pull/228) Persistent volume settings from before mute on page navigation +* [Pull #229](https://github.com/fluid-player/fluid-player/pull/229) Link to FP on menu button working correctly +* [Pull #230](https://github.com/fluid-player/fluid-player/pull/230) Fix for right click on initial play button +* [Pull #227](https://github.com/fluid-player/fluid-player/pull/227) Optional parameter to disable clickthrough layer on instream ads +* [Pull #231](https://github.com/fluid-player/fluid-player/pull/231) Fixes for how thumbnails are drawn and mouse event detection + +## 2.4.0 +* [Pull #214](https://github.com/fluid-player/fluid-player/pull/241) Avoid looping VAST Ad +* [Pull #206](https://github.com/fluid-player/fluid-player/pull/206) Fix tracking impression events for nonLinear ads +* [Pull #221](https://github.com/fluid-player/fluid-player/pull/221) Fix VAST loading issue by AdBlock +* [Pull #207](https://github.com/fluid-player/fluid-player/pull/207) Add support for HD icon on quality select +* [Pull #209](https://github.com/fluid-player/fluid-player/pull/209) Advanced theatre mode +* [Pull #208](https://github.com/fluid-player/fluid-player/pull/208) Compress files +* [Pull #179](https://github.com/fluid-player/fluid-player/pull/179) Fix to prevent changing speed during ads +* [Pull #217](https://github.com/fluid-player/fluid-player/pull/217) Prevent video size change on quality switch +* [Pull #213](https://github.com/fluid-player/fluid-player/pull/213) Controls to stay working with adblock and fix for double event on mobile touch +* [Pull #212](https://github.com/fluid-player/fluid-player/pull/212) Poster image to fit player size +* [Pull #186](https://github.com/fluid-player/fluid-player/pull/186) Fix for source switch on Edge +* [Pull #219](https://github.com/fluid-player/fluid-player/pull/219) Play / pause icon fix and progress bar to disappear correctly +* [Pull #218](https://github.com/fluid-player/fluid-player/pull/218) Optional theatre settings + +## 2.3.0 +* [Pull #192](https://github.com/fluid-player/fluid-player/pull/191) Persist user settings across pages for volume, speed, quality and theatre mode +* [Pull #194](https://github.com/fluid-player/fluid-player/pull/194) Fix for play event on video click for certain devices +* [Pull #193](https://github.com/fluid-player/fluid-player/pull/193) Option to set adText and adTextPosition on a per ad basis +* [Pull #184](https://github.com/fluid-player/fluid-player/pull/184) Fix for thumbnails appearing incorrectly on mobile +* [Pull #181](https://github.com/fluid-player/fluid-player/pull/181) Fix for poster image for dash file +* [Pull #195](https://github.com/fluid-player/fluid-player/pull/195) Loading icon while player is waiting +* [Pull #200](https://github.com/fluid-player/fluid-player/pull/200) Ad text positioning fix +* [Pull #196](https://github.com/fluid-player/fluid-player/pull/196) Fix for issue causing controls to hide incorrectly +* [Pull #191](https://github.com/fluid-player/fluid-player/pull/191) Scrubbing to no longer trigger Fluid on.pause event + +## 2.2.2 +* [Pull #175](https://github.com/fluid-player/fluid-player/pull/175) Fullscreen mode variable correct place +* [Pull #177](https://github.com/fluid-player/fluid-player/pull/177) Fix fadeOut/fadeIn opacity to correct values in the end of animation +* [Pull #180](https://github.com/fluid-player/fluid-player/pull/180) Adding VASTAdTagURI support + +## 2.2.1 +* [Pull #153](https://github.com/fluid-player/fluid-player/pull/153) CDATA media file ignores whitespace correctly +* [Pull #154](https://github.com/fluid-player/fluid-player/pull/154) onPauseRoll not showing on source switch +* [Pull #155](https://github.com/fluid-player/fluid-player/pull/155) iOS native fullscreen +* [Pull #156](https://github.com/fluid-player/fluid-player/pull/156) CSS fixes for progress bar and logo +* [Pull #157](https://github.com/fluid-player/fluid-player/pull/157) Fix for onMainVideoEnded firing correctly +* [Pull #158](https://github.com/fluid-player/fluid-player/pull/158) Play / Pause animation to not show when changing source +* [Pull #159](https://github.com/fluid-player/fluid-player/pull/159) Theatre mode to not show in iframe +* [Pull #148](https://github.com/fluid-player/fluid-player/pull/148) Fix for currentTime being set for iOS and safari for ads and source switch +* [Pull #165](https://github.com/fluid-player/fluid-player/pull/165) Fix for video duration if passed as 00:00:00 in the VAST file +* [Pull #167](https://github.com/fluid-player/fluid-player/pull/167) Allow for individual images to be set in .vtt file +* [Pull #169](https://github.com/fluid-player/fluid-player/pull/169) Preview Thumbnail image locations - Ability to set relative path +* [Pull #168](https://github.com/fluid-player/fluid-player/pull/168) Show custom error if XML content-type is wrong +* [Pull #166](https://github.com/fluid-player/fluid-player/pull/166) Bug fix for Error 202 showing up periodically in the console +* [Pull #149](https://github.com/fluid-player/fluid-player/pull/149) Bug fix to remove mainVideoReady eventListener after success + +## 2.2.0 +* [Pull #121](https://github.com/fluid-player/fluid-player/pull/121) 'Browser layout' VAST fixes +* [Pull #122](https://github.com/fluid-player/fluid-player/pull/122) iOS fullscreen improvements, use native player +* [Pull #125](https://github.com/fluid-player/fluid-player/pull/125) Fix for VAST tag: additional checks for CDATA node irregularity +* [Pull #126](https://github.com/fluid-player/fluid-player/pull/126) Pause player when linear ad opens (ad video is clicked) +* [Pull #127](https://github.com/fluid-player/fluid-player/pull/127) OnPause ad showing on source switch fix +* [Pull #128](https://github.com/fluid-player/fluid-player/pull/128) [Pull #139](https://github.com/fluid-player/fluid-player/pull/139) Poster Image as a param +* [Pull #130](https://github.com/fluid-player/fluid-player/pull/130) Create progressbar markers for nonLinear ads +* [Pull #131](https://github.com/fluid-player/fluid-player/pull/131) [Pull #136](https://github.com/fluid-player/fluid-player/pull/136/) Additional logo parameters +* [Pull #138](https://github.com/fluid-player/fluid-player/pull/138) Support for DASH and HLS streaming +* [Pull #143](https://github.com/fluid-player/fluid-player/pull/143) Positioning of ad and cta text elements + +## 2.1.2 +* [Pull #108](https://github.com/fluid-player/fluid-player/pull/108) Fullscreen API call fix +* [Pull #110](https://github.com/fluid-player/fluid-player/pull/110) Improvements for iOs safari (use default skin) and mobile screens +* [Pull #111](https://github.com/fluid-player/fluid-player/pull/111) Adjust how iconClickThrough is gotten + +## 2.1.1 +* [Pull #107](https://github.com/fluid-player/fluid-player/pull/107) Download and Theatre fixes + +## 2.1 +* [Pull #101](https://github.com/fluid-player/fluid-player/pull/101) Quality indicator +* [Pull #102](https://github.com/fluid-player/fluid-player/pull/102) API functions +* [Pull #103](https://github.com/fluid-player/fluid-player/pull/103) Landing page displayed in In-Stream ads +* [Pull #104](https://github.com/fluid-player/fluid-player/pull/104) Theater mode, download & playback rate + +## 2.0 +* [Pull #91](https://github.com/fluid-player/fluid-player/pull/91) Version 2 Changes: + * New default template + * Add play button + * Play pause animations + * Restructuring of optional parameters + * Remove templates + * General fixes + +## 1.2.2 +* [Pull #88](https://github.com/fluid-player/fluid-player/pull/88) Improve nonlinear ads + +## 1.2.1 +* [Pull #86](https://github.com/fluid-player/fluid-player/pull/86) [Pull #87](https://github.com/fluid-player/fluid-player/pull/87) Mid roll current time fix + +## 1.2.0 +* [Pull #68](https://github.com/fluid-player/fluid-player/pull/68) Controls remain fullscreen after escaping fullscreen +* [Pull #66](https://github.com/fluid-player/fluid-player/pull/66) Optional logoUrl for clickable logo +* [Pull #74](https://github.com/fluid-player/fluid-player/pull/74) Add ability to grab and slide the volume slider and timeline scrubber. +* [Pull #75](https://github.com/fluid-player/fluid-player/pull/75) [Pull #77](https://github.com/fluid-player/fluid-player/pull/77) Adding mid/post roll support and initial VAST nonLinear support. +* [Pull #67](https://github.com/fluid-player/fluid-player/pull/67) Adding key controls. +* [Pull #69](https://github.com/fluid-player/fluid-player/pull/69) Adding controls hiding functionality. + +## 1.1.3 +* [Pull #50](https://github.com/fluid-player/fluid-player/pull/50) Fix for double double render of blank video on some browsers + +## 1.1.2 +* [Pull #43](https://github.com/fluid-player/fluid-player/pull/43) Add two new skins. + +## 1.1.1 +* [Pull #38](https://github.com/fluid-player/fluid-player/pull/38) Reset the CSS box-sizing settings. + +## 1.1.0 +* [Pull #34](https://github.com/fluid-player/fluid-player/pull/34) Various Improvements: + * Possibility to allow the user to switch between different video qualities. (Example, 720p, 1080p, etc...) + * Enable/Disable autoplay. + * Possibility to set a logo over the video player, with configurable position and opacity. + * Possibility to show a text when a video ad is being played. (Example : "Advertising") + * Possibility to show a call to action link when a video ad is being played. (Example : "Click here to learn more about this product.") + * Improved CSS management. + * Possibility to show a custom HTML code when the user pauses the video. (For example, a banner ad, or some related video links) + * The video player can be fully responsive. + +## 1.0.2 +* [Pull #18](https://github.com/fluid-player/fluid-player/pull/18) [Pull #19](https://github.com/fluid-player/fluid-player/pull/19) Update file names, add in min file versions + +## 1.0.1 +* [Pull #1](https://github.com/fluid-player/fluid-player/pull/1) Fix a Fluid Player crash when the ad video file is not available. Properly announcing errors if an Error tag is present in the VAST tag. +* [Pull #3](https://github.com/fluid-player/fluid-player/pull/3) Demo layouts. Various bugfixes and improvements. +* [Pull #10](https://github.com/fluid-player/fluid-player/pull/10) Thumbnail previews from vtt file can be overwritten. +* [Pull #11](https://github.com/fluid-player/fluid-player/pull/11) Player shows current play time and video duration in 'default' template. +* [Pull #14](https://github.com/fluid-player/fluid-player/pull/14) Fix a minor issue when playing the video from outside the Fluid Player code. + +## 1.0.0 +* Initial Release diff --git a/client/fluid-player/CONTRIBUTING.md b/client/fluid-player/CONTRIBUTING.md new file mode 100644 index 0000000..0fd8f48 --- /dev/null +++ b/client/fluid-player/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing + +If you are interested in making a contribution there are a few ways you could help out the project. + +## Filing issues + +[GitHub Issues](https://github.com/fluid-player/fluid-player/issues) are used for all discussions around the codebase, including **bugs** and **features**. + +### Reporting a Bug + +Good bug reports can be very helpful. A bug is a demonstrable problem with the code. + +Guidelines for bug reports: + +1. Please use the [GitHub issue search](https://github.com/fluid-player/fluid-player/issues) — check if the issue has already been reported. +1. Check if the issue has already been fixed — try to reproduce it using the uncompressed code from latest `master` branch in the repository. +1. Create a small demo with the live example (reduced test case). You can possibly use [this codepen template](https://codepen.io/exadsleroy/pen/QWmWPeo) as a starting point -- don't forget to update it to the fluid-player version you use. + +A good bug report should be as detailed as possible, so that others won't have to follow up for the essential details. + +**[File a bug report](https://github.com/fluid-player/fluid-player/issues/new)** + +### Requesting a Feature + +1. [Search the issues](https://github.com/fluid-player/fluid-player/issues) for any previous requests for the same feature, and give a thumbs up or +1 on existing requests. +1. If no previous requests exist, create a new issue. Please be as clear as possible about why the feature is needed and the intended use case. + +**[Request a feature](https://github.com/fluid-player/fluid-player/issues/new)** + +## Contributing code + +If you plan to propose code changes it is required you create an +issue [issue](https://github.com/fluid-player/fluid-player/issues/new) with a brief proposal (as described in +Requesting a Feature) and discuss it with us first. + +This is necessary to avoid more than one contributor working on the same feature/change and to avoid someone +spending time on feature/change that would not be merged for some reason. + +For smaller contributions just use this workflow: + +* Create an issue describing the changes. +* Await confirmation from contributors. +* Fork the project. +* Create a branch for your feature or bug fix. +* Add code changes. +* All new features or changes to the player settings or interface have to be documented in the +[docs repo](https://github.com/fluid-player/fluid-player-docs), so that they are displayed +on [https://docs.fluidplayer.com](https://docs.fluidplayer.com). +If you have made changes like this, please fork fluid-player-docs as well and create a branch with the same +name as the feature branch, adding necessary changes to documentation. +* Send a pull request (both for fluid-player and fluid-player-docs) + +After one of the contributors has checked and approved the changes, they will be merged into master branch +and will be included in the next release tag. diff --git a/client/fluid-player/LICENSE b/client/fluid-player/LICENSE new file mode 100644 index 0000000..0d1e312 --- /dev/null +++ b/client/fluid-player/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Fluid Player and contributors + +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/client/fluid-player/README.md b/client/fluid-player/README.md new file mode 100644 index 0000000..87057fe --- /dev/null +++ b/client/fluid-player/README.md @@ -0,0 +1,23 @@ +# Fluid Player +[![Latest version](https://img.shields.io/github/v/release/fluid-player/fluid-player?include_prereleases&label=latest%20release&sort=semver&style=flat-square&logo=GitHub)](https://github.com/fluid-player/fluid-player/releases/latest) +[![npm](https://img.shields.io/npm/v/fluid-player?style=flat-square&logo=npm)](https://www.npmjs.com/package/fluid-player) + +## Version 3 released + +A [new major version](https://github.com/fluid-player/fluid-player/pull/441) of Fluid Player has been released on May 20, 2020. Existing version 2 users are recommended to upgrade. [See quick setup guide](https://docs.fluidplayer.com/docs/integration/quick-setup/). + +## Overview + +Fluid Player is a free HTML5 video player. It is lightweight, easy to integrate and has advanced VAST capabilities. +The idea behind VAST, as well as the full VAST specification, can be found here: [VAST 4.0](https://www.iab.com/guidelines/digital-video-ad-serving-template-vast-4-0/). + +## Documentation +The integration and configuration of Fluid Player is fully outlined in [Fluid Player Documentation](http://docs.fluidplayer.com) + +## License + +Fluid Player is licensed under the MIT License. View the [License File](LICENSE). + +## Changelog + +A full list of changes and updates can be found in the project [CHANGELOG](CHANGELOG.md). diff --git a/client/fluid-player/e2e/ads_linear.spec.ts b/client/fluid-player/e2e/ads_linear.spec.ts new file mode 100644 index 0000000..03bc050 --- /dev/null +++ b/client/fluid-player/e2e/ads_linear.spec.ts @@ -0,0 +1,181 @@ +import { test, expect } from '@playwright/test'; +import { waitForVideoToPlay, setVideoCurrentTime, getVideoCurrentTime } from './functions/video'; +import { waitForSpecificNetworkCall } from './functions/network'; + +test.describe('desktop ads', () => { + + test.beforeEach(async ({ page }) => { + console.log(`Running ${test.info().title}`); + await page.goto('/ads_linear.html'); + }); + + test('should navigate to the publishers advertised website on click', async ({ page }) => { + const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case'); + const video = page.locator('video'); + + // start the video + fullPlayer.click(); + + await waitForVideoToPlay(video); + + // Set up a listener for the 'popup' event + // This listener listens for a new _blank tab to open + const [popupPromise] = await Promise.all([ + page.waitForEvent('popup'), // Listen for the popup event + fullPlayer.click() // click ad to open advertisers link + ]); + + // Prevent the tab from fully opening + const popup = popupPromise; + + // Verify the URL of the popup + const popupUrl = popup.url(); + console.log(`Popup URL: ${popupUrl}`); + expect(popupUrl).toBe('http://www.example.com/'); + + // Close the popup to prevent extra tabs, in case the above failed to prevent the opening of the new tab + await popup.close(); + }); + + test('should fire pre-, mid- and postRoll based on time', async ({ page }) => { + const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case'); + const skipButton = page.locator('.skip_button'); + const video = page.locator('video'); + + // Start the video + fullPlayer.click(); + await waitForVideoToPlay(video); + + /** + * PREROLL + */ + await expect(skipButton).toHaveText(/Skip ad in 2/); + // Wait for skip ad timer + await page.waitForTimeout(2500); + await expect(skipButton).toHaveText(/Skip Ad /); + + // Skip the ad + await skipButton.click(); + + /** + * MIDROLL + */ + await page.waitForFunction(() => { + const videoElement = document.querySelector('video') as HTMLVideoElement; + // 15 is the length of the ad + return videoElement && Math.floor(videoElement.duration) !== 15; + }); + + // Midrolls don't trigger if you seek less then 5 seconds before their time + await setVideoCurrentTime(video, 35); + await page.waitForTimeout(5500); + await expect(skipButton).toHaveText(/Skip ad in 2/); + // Wait for skip ad timer + await page.waitForTimeout(2500); + await expect(skipButton).toHaveText(/Skip Ad /); + + // Skip the ad + await skipButton.click(); + + await page.waitForTimeout(500); + + await waitForVideoToPlay(video); + + const currentTime = await getVideoCurrentTime(video); + + // Check if the video resumes after the midroll at the correct time + expect(Math.floor(currentTime)).toEqual(39); + + /** + * POSTROLL + */ + + // Skip to the end + await page.waitForTimeout(500); + await video.evaluate((videoEl) => { + const vid = (videoEl as HTMLVideoElement); + vid.currentTime = Math.max(0, vid.duration) - 1; + }); + await page.waitForTimeout(1000); + await expect(skipButton).toHaveText(/Skip ad in 2/); + // Wait for skip ad timer + await page.waitForTimeout(2500); + await expect(skipButton).toHaveText(/Skip Ad /); + + await skipButton.waitFor({ state: 'visible', timeout: 5000 }); + // Skip the ad + await skipButton.click(); + + // Check if video is marked as ended + await page.waitForFunction(() => { + const video = document.querySelector('video'); + return video && !video.ended; + }); + }); + + test('ad should not be skipped when the ad countdown is not done', async ({ page }) => { + const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case'); + const skipButton = page.locator('.skip_button'); + const video = page.locator('video'); + + // Start the video + fullPlayer.click(); + + await page.waitForFunction(() => { + const videoElement = document.querySelector('video'); + return videoElement && videoElement.duration > 0; + }, { timeout: 5000 }); + + const adDuration = await video.evaluate((vid) => { + const videoElement = vid as HTMLVideoElement; + return videoElement.duration; + }); + + // Click the button but it should not be skipped + // NOTE: don't add 'await' because it will wait until it can skip + skipButton.click(); + + // If the ad still has the same video duration, that means the video is not skipped + const videoDurationAfterClick = await video.evaluate((vid) => { + const videoElement = vid as HTMLVideoElement; + return videoElement.duration; + }); + + expect(videoDurationAfterClick).not.toBeFalsy(); + expect(adDuration).not.toBeFalsy(); + + expect(videoDurationAfterClick).toEqual(adDuration); + + await page.waitForTimeout(2000); + // Skip Ad + await skipButton.click(); + await page.waitForTimeout(500); + + const videoDuration = await video.evaluate((vid) => { + const videoElement = vid as HTMLVideoElement; + return videoElement.duration; + }); + + expect(videoDuration).not.toBeFalsy(); + expect(adDuration).not.toBeFalsy(); + + expect(videoDuration).not.toEqual(adDuration); + }); + + test('impression url should be called', async ({ page }) => { + const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case'); + + // start the video + fullPlayer.click(); + + const request = await waitForSpecificNetworkCall( + page, + 'http://www.example.com/impression', + 'GET' + ); + + expect(request.url()).toBe('http://www.example.com/impression'); + }); + +}); + diff --git a/client/fluid-player/e2e/controls.spec.ts b/client/fluid-player/e2e/controls.spec.ts new file mode 100644 index 0000000..348e9c6 --- /dev/null +++ b/client/fluid-player/e2e/controls.spec.ts @@ -0,0 +1,69 @@ +import { test, expect } from '@playwright/test'; + +test.describe('desktop controls', () => { + + test.beforeEach(async ({ page }) => { + console.log(`Running ${test.info().title}`); + await page.goto('/controls.html'); + }); + + test('should toggle play/pause when clicking the player', async ({ page }) => { + // Selectors + const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case'); + const playButton = page.locator('.fluid_button_play'); + const pauseButton = page.locator('.fluid_button_pause'); + + // Initial state checks + await expect(playButton).toBeVisible(); + await expect(pauseButton).not.toBeVisible(); + + // Video player should start playing + fullPlayer.click(); + + // Wait for video to start playing + await page.waitForFunction(() => { + const video = document.querySelector('video'); + return video && !video.paused; + }); + + // Verify playing state + await expect(playButton).not.toBeVisible(); + await expect(pauseButton).toBeVisible(); + + // Wait for 500ms so the browser doesn't reject the click + await page.waitForTimeout(1000); + // Video player should pause + fullPlayer.click(); + + // Wait for video to pause + await page.waitForFunction(() => { + const video = document.querySelector('video'); + return video && video.paused; + }); + + // Verify paused state + await expect(playButton).toBeVisible(); + await expect(pauseButton).not.toBeVisible(); + }); + + test('mouse should disappear when hovering the video', async ({ page }) => { + const video = page.locator('video'); + const playButton = page.locator('.fluid_button_play'); + + await playButton.click(); + + // Hover over the video + await video.hover(); + + await page.waitForTimeout(1500); + + // Evaluate the cursor CSS property of the video element or its parent + const isCursorHidden = await video.evaluate((vid) => { + const computedStyle = window.getComputedStyle(vid); + return computedStyle.cursor === 'none'; + }); + + expect(isCursorHidden).toBeTruthy(); // Assert that the cursor is hidden + }); +}); + diff --git a/client/fluid-player/e2e/functions/network.ts b/client/fluid-player/e2e/functions/network.ts new file mode 100644 index 0000000..4e84326 --- /dev/null +++ b/client/fluid-player/e2e/functions/network.ts @@ -0,0 +1,21 @@ +import { Page, Request } from 'playwright'; + +/** + * Wait for a specific network request and log it. + * + * @param page - The Playwright page instance. + * @param url - The URL of the request to wait for. + * @param method - The HTTP method of the request (default is 'GET'). + * @returns The intercepted request object. + */ +export async function waitForSpecificNetworkCall( + page: Page, + url: string, + method: string = 'GET' +): Promise { + const request = await page.waitForRequest((req) => + req.url() === url && req.method() === method + ); + + return request; +} \ No newline at end of file diff --git a/client/fluid-player/e2e/functions/video.ts b/client/fluid-player/e2e/functions/video.ts new file mode 100644 index 0000000..b3d8250 --- /dev/null +++ b/client/fluid-player/e2e/functions/video.ts @@ -0,0 +1,86 @@ +import { Locator, Page } from 'playwright'; + +/** + * Seek to a given time in the video + * + * @param video - Playwright video locator + * @param time - The time you want to seek to + */ +export async function setVideoCurrentTime(video: Locator, time: number): Promise { + await video.page().waitForFunction( + (vid) => { + const videoElement = vid as HTMLVideoElement | null; + return videoElement && videoElement.readyState >= 2; + }, + await video.elementHandle(), + { timeout: 10000 } + ); + + // Seek to the specified time + await video.evaluate((vid, t) => { + const videoElement = vid as HTMLVideoElement; + videoElement.currentTime = t; + }, time); +} + +/** + * Wait until the video duration has changed + * This way you can detect if the ad or content is loaded in + * + * @param page - The Playwright page instance + * @param initialDuration - The initial duration of the video element + * @param timeout + */ +export async function waitForVideoDurationChange( + page: Page, + initialDuration: number, + timeout: number = 10000 +): Promise { + await page.waitForFunction( + (initialDur) => { + const videoElement = document.querySelector('video') as HTMLVideoElement; + return videoElement.duration !== initialDur; + }, + initialDuration, + { timeout } + ); +} + +/** + * Get the current duration of the video + * + * @param video - Playwright video locator + * @returns video duration time + */ +export async function getVideoDuration(video: Locator): Promise { + return await video.evaluate((vid) => { + const videoElement = vid as HTMLVideoElement; + return videoElement.duration; + }); +} + +/** + * Get the current time of the video + * + * @param video - Playwright video locator + * @returns video current time + */ +export async function getVideoCurrentTime(video: Locator): Promise { + return await video.evaluate((vid) => { + const videoElement = vid as HTMLVideoElement; + return videoElement.currentTime; + }); +} + +/** + * Waits until the given video element starts playing. + * + * @param video - The Playwright Locator for the video element. + */ +export async function waitForVideoToPlay(video: Locator): Promise { + await video.evaluate((vid) => { + return new Promise((resolve) => { + vid.addEventListener('playing', () => resolve(), { once: true }); + }); + }); +} \ No newline at end of file diff --git a/client/fluid-player/e2e/snapshots/baseline-sv-grid.png b/client/fluid-player/e2e/snapshots/baseline-sv-grid.png new file mode 100644 index 0000000..1c42404 Binary files /dev/null and b/client/fluid-player/e2e/snapshots/baseline-sv-grid.png differ diff --git a/client/fluid-player/e2e/suggested_videos.spec.ts b/client/fluid-player/e2e/suggested_videos.spec.ts new file mode 100644 index 0000000..47d365a --- /dev/null +++ b/client/fluid-player/e2e/suggested_videos.spec.ts @@ -0,0 +1,37 @@ +import { test, expect } from '@playwright/test'; +import { waitForVideoToPlay, setVideoCurrentTime, getVideoDuration } from './functions/video'; + +test.describe('suggested videos', () => { + + test.beforeEach(async ({ page }) => { + console.log(`Running ${test.info().title}`); + }); + + test('should show up in 4x3 grid at the end of the video if it is configured and after postRoll', async ({ page }) => { + await page.goto('/suggested_videos_e2e.html'); + + const fullPlayer = page.locator('#fluid_video_wrapper_fluid-player-e2e-case'); + const video = page.locator('video'); + + fullPlayer.click(); + await waitForVideoToPlay(video); + + const videoDuration = await getVideoDuration(video); + await setVideoCurrentTime(video, videoDuration - 5); + + await waitForVideoToPlay(video); + await page.waitForTimeout(5500); + + const suggestedVideosGrid = page.locator('.suggested_tile_grid'); + await expect(suggestedVideosGrid).not.toBeVisible(); + + await page.waitForTimeout(3000); + const skipButton = page.locator('.skip_button'); + skipButton.click(); + + await expect(suggestedVideosGrid).toBeVisible(); + expect(await suggestedVideosGrid.screenshot()).toMatchSnapshot('baseline-sv-grid.png', { threshold: 0.02 }); + }); + +}); + diff --git a/client/fluid-player/package-lock.json b/client/fluid-player/package-lock.json new file mode 100644 index 0000000..97a0f14 --- /dev/null +++ b/client/fluid-player/package-lock.json @@ -0,0 +1,7233 @@ +{ + "name": "fluid-player", + "version": "3.46.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fluid-player", + "version": "3.46.0", + "license": "MIT", + "dependencies": { + "dashjs": "^4.5.2", + "es6-promise": "^4.2.8", + "hls.js": "^1.5.13", + "panolens": "^0.12.1", + "videojs-vtt.js": "^0.15.4" + }, + "devDependencies": { + "@babel/core": "^7.20.12", + "@babel/preset-env": "^7.20.2", + "@playwright/test": "^1.49.0", + "@types/node": "^22.9.1", + "babel-loader": "^9.1.2", + "cheerio": "^1.0.0-rc.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.3", + "html-webpack-plugin": "^5.5.0", + "semver": "^7.3.2", + "style-loader": "^3.3.1", + "webpack": "^5.75.0", + "webpack-cli": "^5.1.1", + "webpack-dev-server": "^4.11.1" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz", + "integrity": "sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.20.14", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz", + "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7", + "@jridgewell/gen-mapping": "^0.3.2", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", + "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-explode-assignable-expression": "^7.18.6", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz", + "integrity": "sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz", + "integrity": "sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "regexpu-core": "^5.2.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", + "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-plugin-utils": "^7.16.7", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0-0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-explode-assignable-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", + "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", + "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.18.10", + "@babel/types": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz", + "integrity": "sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", + "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-wrap-function": "^7.18.9", + "@babel/types": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz", + "integrity": "sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-function-name": "^7.19.0", + "@babel/template": "^7.18.10", + "@babel/traverse": "^7.20.5", + "@babel/types": "^7.20.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.13.tgz", + "integrity": "sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.13", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.20.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz", + "integrity": "sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", + "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-static-block": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz", + "integrity": "sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-proposal-dynamic-import": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", + "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-json-strings": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", + "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz", + "integrity": "sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz", + "integrity": "sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz", + "integrity": "sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", + "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.20.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.15.tgz", + "integrity": "sha512-Vv4DMZ6MiNOhu/LdaZsT/bsLRxgL94d269Mv4R/9sp6+Mp++X/JqypZYypJXLlM4mlL352/Egzbzr98iABH1CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", + "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-split-export-declaration": "^7.18.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", + "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", + "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", + "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", + "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.18.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", + "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", + "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.18.9", + "@babel/helper-function-name": "^7.18.9", + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", + "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", + "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz", + "integrity": "sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", + "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-simple-access": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz", + "integrity": "sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-identifier": "^7.19.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", + "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", + "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", + "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-replace-supers": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", + "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", + "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", + "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", + "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", + "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", + "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", + "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.18.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", + "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", + "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", + "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.1", + "@babel/helper-compilation-targets": "^7.20.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-dynamic-import": "^7.18.6", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-proposal-json-strings": "^7.18.6", + "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-numeric-separator": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.20.0", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-transform-arrow-functions": "^7.18.6", + "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-block-scoped-functions": "^7.18.6", + "@babel/plugin-transform-block-scoping": "^7.20.2", + "@babel/plugin-transform-classes": "^7.20.2", + "@babel/plugin-transform-computed-properties": "^7.18.9", + "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-dotall-regex": "^7.18.6", + "@babel/plugin-transform-duplicate-keys": "^7.18.9", + "@babel/plugin-transform-exponentiation-operator": "^7.18.6", + "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-function-name": "^7.18.9", + "@babel/plugin-transform-literals": "^7.18.9", + "@babel/plugin-transform-member-expression-literals": "^7.18.6", + "@babel/plugin-transform-modules-amd": "^7.19.6", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-umd": "^7.18.6", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-new-target": "^7.18.6", + "@babel/plugin-transform-object-super": "^7.18.6", + "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-property-literals": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-reserved-words": "^7.18.6", + "@babel/plugin-transform-shorthand-properties": "^7.18.6", + "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-sticky-regex": "^7.18.6", + "@babel/plugin-transform-template-literals": "^7.18.9", + "@babel/plugin-transform-typeof-symbol": "^7.18.9", + "@babel/plugin-transform-unicode-escapes": "^7.18.10", + "@babel/plugin-transform-unicode-regex": "^7.18.6", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.20.2", + "babel-plugin-polyfill-corejs2": "^0.3.3", + "babel-plugin-polyfill-corejs3": "^0.6.0", + "babel-plugin-polyfill-regenerator": "^0.4.1", + "core-js-compat": "^3.25.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", + "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz", + "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.20.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz", + "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.20.7", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.19.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.20.13", + "@babel/types": "^7.20.7", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", + "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@playwright/test": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", + "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", + "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz", + "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz", + "integrity": "sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.16.tgz", + "integrity": "sha512-LkKpqRZ7zqXJuvoELakaFYuETHjZkSol8EV6cNnyishutDBCCdv6+dsKPbKkCcIk57qRphOLY5sEgClw1bO3gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.31", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.33", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz", + "integrity": "sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.9", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", + "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", + "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.33", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", + "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz", + "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz", + "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "deprecated": "package has been renamed to acorn-import-attributes", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ajv-keywords/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", + "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.17.7", + "@babel/helper-define-polyfill-provider": "^0.3.3", + "semver": "^6.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", + "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3", + "core-js-compat": "^3.25.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", + "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.3.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", + "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", + "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", + "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", + "license": "MIT", + "dependencies": { + "bcp-47": "^1.0.0", + "bcp-47-match": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", + "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/bonjour-service/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001450", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz", + "integrity": "sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.10", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", + "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^1.5.0", + "dom-serializer": "^1.3.2", + "domhandler": "^4.2.0", + "htmlparser2": "^6.1.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", + "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "css-select": "^4.3.0", + "css-what": "^6.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.3.1", + "domutils": "^2.8.0" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-css": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", + "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/codem-isoboxer": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.6.tgz", + "integrity": "sha512-LuO8/7LW6XuR5ERn1yavXAfodGRhuY2yP60JTZIw5yNYMCE5lUVbk3NFUCJxjnphQH+Xemp5hOGb1LgUXm00Xw==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.27.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.2.tgz", + "integrity": "sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.19", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dashjs": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-4.5.2.tgz", + "integrity": "sha512-WXPk0lPDSaHjiSVoVRh2jQPiMmB1alKUH8hV2CVmaI0vPUeT1wIY7madVE38SthfOmwS9IJViv1RrxrxdGjElg==", + "license": "BSD-3-Clause", + "dependencies": { + "bcp-47-match": "^1.0.3", + "bcp-47-normalize": "^1.1.1", + "codem-isoboxer": "0.3.6", + "es6-promise": "^4.2.8", + "fast-deep-equal": "2.0.1", + "html-entities": "^1.2.1", + "imsc": "^1.0.2", + "localforage": "^1.7.1", + "ua-parser-js": "^1.0.2" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dns-packet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", + "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.285", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.285.tgz", + "integrity": "sha512-47o4PPgxfU1KMNejz+Dgaodf7YTcg48uOfV1oM6cs3adrl2+7R+dHkt3Jpxqo0LRCbGJEzTKMUt0RdvByb/leg==", + "dev": true, + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz", + "integrity": "sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", + "integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", + "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hls.js": { + "version": "1.5.15", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.15.tgz", + "integrity": "sha512-6cD7xN6bycBHaXz2WyPIaHn/iXFizE5au2yvY5q9aC4wfihxAr16C9fUy4nxh2a3wOw0fEgLRa9dN6wsYjlpNg==", + "license": "Apache-2.0" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "webpack": "^5.20.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz", + "integrity": "sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imsc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.3.tgz", + "integrity": "sha512-IY0hMkVTNoqoYwKEp5UvNNKp/A5jeJUOrIO7judgOyhHT+xC6PA4VBOMAOhdtAYbMRHx9DTgI8p6Z6jhYQPFDA==", + "license": "BSD-2-Clause", + "dependencies": { + "sax": "1.2.1" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz", + "integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", + "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.3" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz", + "integrity": "sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/panolens": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/panolens/-/panolens-0.12.1.tgz", + "integrity": "sha512-2hpjm+rRnDdaLD5Bak49K0Y9/X6vOr1OcyJx5piSA6sCOs1tsgchMgKIwpSGCMpBMHWZ10E/Cz4BIwyXYebt5g==", + "license": "MIT", + "dependencies": { + "three": "^0.105.2" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", + "integrity": "sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsgen": "^0.7.1", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", + "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "license": "ISC" + }, + "node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", + "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.16.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz", + "integrity": "sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/three": { + "version": "0.105.2", + "resolved": "https://registry.npmjs.org/three/-/three-0.105.2.tgz", + "integrity": "sha512-L3Al37k4g3hVbgFFS251UVtIc25chhyN0/RvXzR0C+uIBToV6EKDG+MZzEXm9L2miGUVMK27W46/VkP6WUZXMg==", + "license": "MIT" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.33", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", + "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", + "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/videojs-vtt.js": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz", + "integrity": "sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA==", + "license": "Apache-2.0", + "dependencies": { + "global": "^4.3.1" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz", + "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.0", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.4", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", + "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.1", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.4.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz", + "integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/client/fluid-player/package.json b/client/fluid-player/package.json new file mode 100644 index 0000000..d966999 --- /dev/null +++ b/client/fluid-player/package.json @@ -0,0 +1,60 @@ +{ + "name": "fluid-player", + "version": "3.46.0", + "description": "Fluid Player is a free HTML5 video player", + "main": "src/index.js", + "scripts": { + "build": "webpack --mode=production", + "build-cdn": "webpack --mode=production --env dist='current' && webpack --mode=production --env dist='versioned'", + "build-dev": "webpack --mode=development", + "build-dev-debug": "webpack --mode=development --debug", + "dev-server": "webpack serve --mode=development --host 0.0.0.0", + "dev-server-debug": "webpack serve --mode=development --env debug --host 0.0.0.0", + "dev-server-test": "webpack serve --mode=production --no-live-reload --host 0.0.0.0", + "start": "npm run dev-server-debug", + "e2e-ui": "npx playwright test --ui", + "e2e-report": "npx playwright show-report", + "e2e": "npx playwright test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/fluid-player/fluid-player.git" + }, + "author": "EXOGROUP ", + "license": "MIT", + "bugs": { + "url": "https://github.com/fluid-player/fluid-player/issues" + }, + "homepage": "https://fluidplayer.com", + "com_fluidplayer": { + "cdn": "https://cdn.fluidplayer.com" + }, + "browserslist": [ + "> 0.25%", + "not dead", + "IE 11" + ], + "dependencies": { + "dashjs": "^4.5.2", + "es6-promise": "^4.2.8", + "hls.js": "^1.5.13", + "panolens": "^0.12.1", + "videojs-vtt.js": "^0.15.4" + }, + "devDependencies": { + "@babel/core": "^7.20.12", + "@babel/preset-env": "^7.20.2", + "@playwright/test": "^1.49.0", + "@types/node": "^22.9.1", + "babel-loader": "^9.1.2", + "cheerio": "^1.0.0-rc.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.3", + "html-webpack-plugin": "^5.5.0", + "semver": "^7.3.2", + "style-loader": "^3.3.1", + "webpack": "^5.75.0", + "webpack-cli": "^5.1.1", + "webpack-dev-server": "^4.11.1" + } +} diff --git a/client/fluid-player/playwright.config.ts b/client/fluid-player/playwright.config.ts new file mode 100644 index 0000000..e71cea4 --- /dev/null +++ b/client/fluid-player/playwright.config.ts @@ -0,0 +1,73 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:8080', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + // { + // name: 'chromium', + // use: { ...devices['Desktop Chrome'] }, + // }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + snapshotPathTemplate: '{testDir}/snapshots/{arg}{ext}', // Use the specified filename + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/client/fluid-player/src/browser.js b/client/fluid-player/src/browser.js new file mode 100644 index 0000000..9628126 --- /dev/null +++ b/client/fluid-player/src/browser.js @@ -0,0 +1,19 @@ +/** + * Build entry point for CDN builds. + * You SHOULD NOT import this file except if you plan to build browser distribution of Fluid Player. + */ + +import fluidPlayerInitializer from './index'; + +// Import CSS automatically in browser builds. +import './css/fluidplayer.css'; + +if (window) { + /** + * Register public interface. + */ + if (!window.fluidPlayer) { + window.fluidPlayer = fluidPlayerInitializer; + } +} + diff --git a/client/fluid-player/src/css/fluidplayer.css b/client/fluid-player/src/css/fluidplayer.css new file mode 100644 index 0000000..7ed8fb6 --- /dev/null +++ b/client/fluid-player/src/css/fluidplayer.css @@ -0,0 +1,1590 @@ +@import "./suggestedVideos.css"; + +.fluid_video_wrapper { + animation: none; + animation-delay: 0; + animation-direction: normal; + animation-duration: 0; + animation-fill-mode: none; + animation-iteration-count: 1; + animation-name: none; + animation-play-state: running; + animation-timing-function: ease; + backface-visibility: visible; + background: 0; + background-attachment: scroll; + background-clip: border-box; + background-color: transparent; + background-image: none; + background-origin: padding-box; + background-position: 0 0; + background-position-x: 0; + background-position-y: 0; + background-repeat: repeat; + background-size: auto auto; + border: 0; + border-style: none; + border-width: medium; + border-color: inherit; + border-bottom: 0; + border-bottom-color: inherit; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-style: none; + border-bottom-width: medium; + border-collapse: separate; + border-image: none; + border-left: 0; + border-left-color: inherit; + border-left-style: none; + border-left-width: medium; + border-radius: 0; + border-right: 0; + border-right-color: inherit; + border-right-style: none; + border-right-width: medium; + border-spacing: 0; + border-top: 0; + border-top-color: inherit; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-top-style: none; + border-top-width: medium; + bottom: auto; + box-shadow: none; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + caption-side: top; + clear: none; + clip: auto; + color: inherit; + columns: auto; + column-count: auto; + column-fill: balance; + column-gap: normal; + column-rule: medium none currentColor; + column-rule-color: currentColor; + column-rule-style: none; + column-rule-width: none; + column-span: 1; + column-width: auto; + content: normal; + counter-increment: none; + counter-reset: none; + cursor: auto; + direction: ltr; + display: inline; + empty-cells: show; + float: none; + font: normal; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-size: medium; + font-style: normal; + font-variant: normal; + font-weight: normal; + height: auto; + hyphens: none; + left: auto; + letter-spacing: normal; + line-height: normal; + list-style: none; + list-style-image: none; + list-style-position: outside; + list-style-type: disc; + margin: 0; + margin-bottom: 0; + margin-left: 0; + margin-right: 0; + margin-top: 0; + max-height: none; + max-width: none; + min-height: 0; + min-width: 0; + opacity: 1; + orphans: 0; + outline: 0; + outline-color: invert; + outline-style: none; + outline-width: medium; + overflow: visible; + overflow-x: visible; + overflow-y: visible; + padding: 0; + padding-bottom: 0; + padding-left: 0; + padding-right: 0; + padding-top: 0; + page-break-after: auto; + page-break-before: auto; + page-break-inside: auto; + perspective: none; + perspective-origin: 50% 50%; + position: static; + /* May need to alter quotes for different locales (e.g fr) */ + quotes: '\201C' '\201D' '\2018' '\2019'; + right: auto; + tab-size: 8; + table-layout: auto; + text-align: inherit; + text-align-last: auto; + text-decoration: none; + text-decoration-color: inherit; + text-decoration-line: none; + text-decoration-style: solid; + text-indent: 0; + text-shadow: none; + text-transform: none; + top: auto; + transform: none; + transform-style: flat; + transition: none; + transition-delay: 0s; + transition-duration: 0s; + transition-property: none; + transition-timing-function: ease; + unicode-bidi: normal; + vertical-align: baseline; + visibility: visible; + white-space: normal; + widows: 0; + width: auto; + word-spacing: normal; + z-index: auto; + -webkit-tap-highlight-color: transparent; +} + +.fluid_video_wrapper canvas { + pointer-events: none; +} + +.fluid_video_wrapper, +.fluid_video_wrapper * { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.fluid_video_wrapper:after, .fluid_video_wrapper:before { + content: none; +} + +.fluid_video_wrapper { + position: relative; + display: inline-block; +} + +.fluid_video_wrapper video { + position: relative; + background-color: #000000; + display: block; +} + +.fluid_video_wrapper .vast_video_loading { + display: table; + text-align: center; + width: 100%; + height: 100%; + top: 0; + left: 0; + pointer-events: auto; + z-index: 1; +} + +.fluid_video_wrapper .vast_video_loading:before { + background-image: url("../static/fluid-spinner.svg"); + background-position: center, center; + background-repeat: no-repeat, repeat; + background-color: rgba(0, 0, 0, 0.2); + content: ''; + position: absolute; + height: 100%; + width: 100%; + top: 0; + left: 0; +} + +.skip_button { + position: absolute; + bottom: 50px; + right: 0; + background-color: rgba(0, 0, 0, 0.7); + padding: 13px 21px 13px 21px; +} + +.skip_button, .skip_button a { + color: #ffffff; + text-decoration: none; + cursor: pointer; + z-index: 10; + font-size: 13px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + white-space: nowrap; + text-align: start; +} + +.skip_button a span.skip_button_icon { + display: inline-block; + text-align: left; + width: 21px; + position: relative; + bottom: 20px; +} + +.skip_button a span.skip_button_icon:before { + background: url('../static/fluid-icons.svg') no-repeat; + position: absolute; + height: 18px; + width: 18px; + top: 6px; + content: ""; + opacity: 0.8; + -webkit-transition: opacity 0.3s ease-in-out; + -moz-transition: opacity 0.3s ease-in-out; + -ms-transition: opacity 0.3s ease-in-out; + -o-transition: opacity 0.3s ease-in-out; + transition: opacity 0.3s ease-in-out; + background-position: -122px -57px; +} + +.skip_button a span.skip_button_icon:before:hover { + opacity: 1; +} + +.skip_button_disabled { + cursor: default !important; + padding: 13px 21px 13px 21px; +} + +.close_button { + position: absolute; + background: #000000 url("../static/close-icon.svg") no-repeat scroll center center; + height: 16px; + width: 16px; + top: 0; + right: 0; + background-size: 18px 18px; + cursor: pointer; + padding: 1px; + z-index: 31; +} + +.close_button:hover { + background-color: #000000; + border: 1px solid #ffffff; +} + +.vast_clickthrough_layer { + /*IE Fix*/ + background-color: white; + opacity: 0; +} + +.fluid_ad_playing { + position: absolute; + background-color: black; + opacity: 0.8; + border-radius: 1px; + color: #ffffff; + font-size: 13px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + white-space: nowrap; + text-align: start; + line-height: 18px; + z-index: 10; + padding: 13px 21px 13px 21px; +} + +.fluid_ad_cta { + position: absolute; + background-color: rgba(0, 0, 0, 0.7); + color: #ffffff; + font-size: 13px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + text-align: right; + cursor: pointer; + z-index: 10; + padding: 13px 21px 13px 13px; + max-width: 50%; +} + +.fluid_ad_cta.left { + text-align: left; +} + +.fluid_ad_cta a { + text-decoration: none; + color: #ffffff; + line-height: 18px; +} + +.fluid_ad_cta:hover, +.skip_button:not(.skip_button_disabled):hover { + background-color: rgba(0, 0, 0, 1); +} + +.fluid_html_on_pause, +.fluid_html_on_pause_container, +.fluid_pseudo_poster { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; + z-index: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + pointer-events: none; +} + +.fluid_html_on_pause_container *, +.fluid_html_on_pause * { + pointer-events: auto; +} + +/*Mobile Layout*/ +.fluid_video_wrapper.mobile .skip_button { + bottom: 50px; +} + +/* +.fluid_video_wrapper.mobile .fluid_ad_cta { + bottom: 125px; +} +*/ +.fluid_initial_play { + width: 60px; + height: 60px; + border-radius: 50px; + cursor: pointer; +} + +.fluid_initial_play_button { + margin-top: 15px; + margin-left: 23px; + border-style: solid; + border-width: 15px 0 15px 21px; + border-color: transparent transparent transparent #ffffff; +} + +.fluid_initial_pause_button { + margin-top: 15px; + margin-left: 17px; + width: 8px; + height: 31px; + border: 9px solid white; + border-top: 0; + border-bottom: 0; +} + +.fluid_timeline_preview { + bottom: 11px; + color: #ffffff; + font-size: 13px; + line-height: 18px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + text-align: start; + padding: 13px 21px 13px 21px; + background-color: rgba(0, 0, 0, 0.85); + border-radius: 1px; +} + +/* Duration */ +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_fluid_control_duration { + display: inline-block; + white-space: nowrap; + height: 24px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-size: 13px; + font-weight: normal; + font-style: normal; + text-align: left; + text-decoration: none; + line-height: 21px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_fluid_control_duration.cardboard_time { + left: 13px; + top: -15px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_fluid_control_duration.cardboard_time .ad_timer_prefix { + color: #F2C94C; +} + +.fluid_video_wrapper.fluid_player_layout_default .ad_countdown .ad_timer_prefix { + color: #F2C94C; +} + +.fluid_video_wrapper.fluid_player_layout_default .ad_countdown { + /*display: none;*/ + position: absolute; + right: 0; + width: 75px; + bottom: 5px; + height: 24px; + color: red; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-size: 13px; + font-weight: normal; + font-style: normal; + text-align: left; + text-decoration: none; + line-height: 21px; +} + +.initial_controls_show { + visibility: visible !important; + opacity: 1 !important; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container { + color: white; + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: -moz-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#ad000000', GradientType=0); /* IE6-9 */ + height: 100%; + width: 100%; + z-index: 0; + pointer-events: none; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel { + height: 96px; + width: 72px; + left: 10px; + top: 10px; + position: absolute; + background: rgba(0, 0, 0, 0.7); + text-align: center; + border-radius: 6px; + overflow: hidden; + pointer-events: auto; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_button { + cursor: pointer; + display: inline-block; + text-align: left; + height: 24px; + width: 24px; + position: relative; + background: url(../static/fluid-icons.svg) no-repeat; + opacity: 0.8; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_button:hover { + opacity: 1; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_up { + background-position: -336px -55px; + -webkit-transform: rotate(270deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(270deg); /* IE 9 */ + transform: rotate(270deg); /* Firefox 16+, IE 10+, Opera */ + display: block; + left: calc(50% - 12px); + top: 0; + position: absolute; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_left { + background-position: -336px -55px; + -webkit-transform: rotate(180deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(1890deg); /* IE 9 */ + transform: rotate(180deg); /* Firefox 16+, IE 10+, Opera */ + display: block; + left: 0; + top: 24px; + position: absolute; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_right { + background-position: -336px -55px; + -webkit-transform: rotate(0deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(0deg); /* IE 9 */ + transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ + display: block; + right: 0; + top: 24px; + position: absolute; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_down { + background-position: -336px -55px; + -webkit-transform: rotate(90deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(90deg); /* IE 9 */ + transform: rotate(90deg); /* Firefox 16+, IE 10+, Opera */ + display: block; + left: calc(50% - 12px); + top: 48px; + position: absolute; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_zoomdefault { + background-position: -336px -17px; + top: 72px; + -webkit-transform: rotate(0deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(0deg); /* IE 9 */ + transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ + position: absolute; + left: calc(50% - 12px); +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_zoomin { + background-position: -305px -55px; + top: 72px; + -webkit-transform: rotate(0deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(0deg); /* IE 9 */ + transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ + position: absolute; + right: 0; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vr_container .fluid_vr_joystick_panel .fluid_vr_joystick_zoomout { + background-position: -305px -17px; + top: 72px; + -webkit-transform: rotate(0deg); /* Chrome, Opera 15+, Safari 3.1+ */ + -ms-transform: rotate(0deg); /* IE 9 */ + transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ + position: absolute; + left: 0; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container.fluid_vr_controls_container { + width: 50% !important; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container.fluid_vr2_controls_container { + width: 50% !important; + left: 50%; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container { + color: white; + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: -moz-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#ad000000', GradientType=0); /* IE6-9 */ + height: 53px; + z-index: 1; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vpaid_iframe { + position: absolute; + top: 0; + width: 100%; + height: 100%; + left: 0; + z-index: -10; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vpaid_nonlinear_slot_iframe { + z-index: 30; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_vpaid_slot { + position: absolute !important; + top: 0 !important; + width: 100% !important; + height: 100% !important; + left: 0 !important; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_subtitles_container { + color: white; + position: absolute; + bottom: 46px; + left: 0; + right: 0; + height: auto; + z-index: 1; + text-align: center; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_subtitles_container div { + display: inline; + background: black; + color: white; + font-size: 1em; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + padding: 0.25em; + border-radius: 4px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fade_out { + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 0.5s, opacity 0.5s; /* Safari */ + transition: visibility 0.5s, opacity 0.5s; +} + +.fluid_video_wrapper.fluid_player_layout_default .fade_in { + visibility: visible; + opacity: 1; + -webkit-transition: visibility 0.5s, opacity 0.5s; /* Safari */ + transition: visibility 0.5s, opacity 0.5s; +} + +.fluid_video_wrapper.fluid_player_layout_default.pseudo_fullscreen { + width: 100% !important; + height: 100% !important; + top: 0; + left: 0; + position: fixed; + z-index: 99999; +} + +.fluid_video_wrapper.fluid_player_layout_default:-webkit-full-screen { + width: 100% !important; + height: 100% !important; + position: absolute; + top: 0; + left: 0; +} + +.fluid_video_wrapper.fluid_player_layout_default:-ms-fullscreen { + width: 100% !important; + height: 100% !important; + position: absolute; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_context_menu { + background-color: #000000; + color: #ffffff; + font-size: 13px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + white-space: nowrap; + text-align: start; + z-index: 11; + opacity: 0.8; + border-radius: 1px; +} + +/* IE 10+ */ +_:-ms-lang(x), +.fluid_video_wrapper.fluid_player_layout_default .fluid_context_menu { + text-align: left; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_context_menu ul { + list-style: none; + padding: 0; + margin: 0; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_context_menu ul li { + padding: 13px 71px 13px 21px; + cursor: pointer; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_context_menu ul li + li { + border-top: 1px solid #000000; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_context_menu ul li:hover { + background-color: #1e1e1e; + color: #fbfaff; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_left { + width: 24px; + left: 20px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container.skip_controls .fluid_controls_left { + width: 80px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button { + width: 24px; + height: 24px; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_duration { + display: flex; + align-items: center; +} + +.fluid_button_live_indicator { + margin-right: 10px; + display: inline-flex; + align-items: center; + padding: 2px 5px; + background-color: red; + color: white; + border-radius: 4px; + font-weight: bold; + font-size: 11px; + margin-bottom: 3px; +} + +.live_circle { + position: relative; + display: inline-block; + width: 6px; + height: 6px; + background-color: transparent; + border: 1px solid #ffffff; + border-radius: 50%; + margin-left: 3px; +} + +.live_circle::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 4px; + height: 4px; + background-color: #ffffff; + border-radius: 50%; +} + +/* On smaller devices (mobile) */ +@media (max-width: 768px) { + .fluid_button_live_indicator { + position: absolute; + font-size: 8px; + top: -30px; + left: -40px; + padding: 2px 3px !important; + } +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right { + left: 60px; + right: 20px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container.skip_controls .fluid_controls_right { + left: 110px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_left, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right { + position: absolute; + height: 24px; + top: 23px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container { + height: 14px; + position: absolute; + left: 13px; + right: 13px; + z-index: 1; + top: 8px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_progress { + position: absolute; + top: 5px; + width: 100%; + height: 4px; + background-color: rgba(255, 255, 255, 0.25); +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_buffered { + position: absolute; + top: 5px; + width: 0; + height: 3px; + background-color: rgba(255, 255, 255, 0.5); + z-index: -1; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_progress, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_progress .fluid_controls_currentprogress { + position: absolute; + height: 3px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container:hover .fluid_controls_progress, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container:hover .fluid_controls_buffered, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container:hover .fluid_controls_ad_markers_holder { + margin-top: -1px; + height: 5px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container:hover .fluid_controls_progress .fluid_controls_currentprogress { + height: 5px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_timeline_preview_container { + border: 1px solid #262626; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_timeline_preview_container, .fluid_timeline_preview_container_shadow { + bottom: 14px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container.fluid_slider .fluid_controls_progress .fluid_controls_currentprogress .fluid_controls_currentpos { + background-color: white; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container.fluid_slider .fluid_controls_progress .fluid_controls_currentprogress .fluid_controls_currentpos, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container.fluid_ad_slider .fluid_controls_progress .fluid_controls_currentprogress .fluid_controls_currentpos { + opacity: 0; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container.fluid_slider:hover .fluid_controls_progress .fluid_controls_currentprogress .fluid_controls_currentpos { + opacity: 1; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container.fluid_slider .fluid_controls_progress .fluid_controls_currentprogress .fluid_controls_currentpos { + -webkit-transition: opacity 0.3s; /* Safari */ + transition: opacity 0.3s; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_ad_markers_holder { + position: absolute; + top: 5px; + width: 100%; + height: 3px; + z-index: 2; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_ad_marker { + position: absolute; + background-color: #FFCC00; + height: 100%; + width: 6px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container { + height: 24px; + width: 56px; + left: 25px; + top: -1px; + z-index: 2; + opacity: 0.8; + -webkit-transition: opacity 0.3s ease-in-out; + -moz-transition: opacity 0.3s ease-in-out; + -ms-transition: opacity 0.3s ease-in-out; + -o-transition: opacity 0.3s ease-in-out; + transition: opacity 0.3s ease-in-out; + display: none; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container:hover { + opacity: 1; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container .fluid_control_volume { + position: relative; + height: 3px; + width: 100%; + margin-top: 10px; + background-color: rgba(171, 172, 172, 0.68); + z-index: 3; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container .fluid_control_volume .fluid_control_currentvolume { + float: left; + background-color: white; + height: 3px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container .fluid_control_volume .fluid_control_currentvolume .fluid_control_volume_currentpos { + background-color: white; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_progress .fluid_controls_currentpos { + right: -4px; + z-index: 3; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container .fluid_control_volume .fluid_control_currentvolume .fluid_control_volume_currentpos, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_progress .fluid_controls_currentpos { + width: 11px; + height: 11px; + position: absolute; + top: -4px; + border-radius: 6px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_progress_container .fluid_controls_progress .fluid_controls_currentpos { + width: 13px; + height: 13px; + position: absolute; + top: -4px; + border-radius: 6px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container.no_volume_bar .fluid_controls_right .fluid_control_volume_container { + display: none; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_slider { + cursor: pointer; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container div div { + display: block; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_fullscreen, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_fullscreen_exit, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_mini_player { + float: right; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_video_source, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_subtitles, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_cardboard { + font-size: 13px; + height: 24px; + line-height: 24px; + float: right; + cursor: pointer; + position: relative; + text-align: right; + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_video_source .fluid_video_sources_title, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_subtitles .fluid_subtitles_title { + width: 80px; + overflow: hidden; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_subtitles .fluid_subtitles_list, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_video_source .fluid_video_sources_list, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_video_playback_rates { + position: absolute; + bottom: 25px; + right: 3px; + z-index: 888888; + opacity: 99%; + background-color: rgba(0, 0, 0, 1); + border-radius: 2px; + color: #ffffff; + font-size: 13px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + white-space: nowrap; + text-align: start; + width: max-content; + padding: 0.5em; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_subtitles .fluid_subtitles_list .fluid_subtitle_list_item, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_video_source .fluid_video_sources_list .fluid_video_source_list_item { + padding: 12px 34px 12px 24px; + line-height: 15px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_video_source .fluid_video_sources_list .fluid_video_source_list_item:hover, +.fluid_video_playback_rates_item:hover, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_subtitles .fluid_subtitles_list .fluid_subtitle_list_item:hover { + background-color: #3a3a3a; +} + + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_volume, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_mute { + position: absolute; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_volume, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button.fluid_button_mute { + left: -10px; +} + +/* Button Icons */ +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_live_indicator, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_play, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_pause, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_back, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_forward, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_volume, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mute, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_video_source, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen_exit, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_playback_rate, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_download, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_theatre, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_subtitles, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_cardboard, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mini_player { + display: inline-block; + text-align: left; + height: 24px; + width: 24px; + position: relative; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_play:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_pause:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_back:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_forward:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_volume:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mute:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_video_source:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen_exit:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_playback_rate:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_download:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_theatre:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_subtitles:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_cardboard:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mini_player:before{ + background: url('../static/fluid-icons.svg') no-repeat; + position: absolute; + height: 24px; + width: 24px; + top: 1px; + left: 5px; + content: ""; + opacity: 0.8; + -webkit-transition: opacity 0.3s ease-in-out; + -moz-transition: opacity 0.3s ease-in-out; + -ms-transition: opacity 0.3s ease-in-out; + -o-transition: opacity 0.3s ease-in-out; + transition: opacity 0.3s ease-in-out; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_play:before { + background-position: -15px -19px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_pause:before { + background-position: -15px -57px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_volume:before { + background-position: -52px -19px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mute:before { + background-position: -52px -57px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen:before { + background-position: -88px -19px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen_exit:before { + background-position: -88px -57px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_video_source:before { + background-position: -122px -19px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_playback_rate:before { + background-position: -232px -19px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_download:before { + background-position: -194px -18px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_theatre:before { + background-position: -195px -56px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_subtitles:before { + background-position: -269px -19px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_cardboard:before { + background-position: -269px -56px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_back:before { + background: url('../static/skip-backward.svg') no-repeat; + background-position: -2px -2px; +} + + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_forward:before { + background: url('../static/skip-forward.svg') no-repeat; + background-position: -2px -2px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mini_player:before { + background: url('../static/miniplayer-toggle-on.svg') no-repeat 0 0; + background-size: 20px; +} + +.fluid_video_wrapper.fluid_mini_player_mode .fluid_controls_container .fluid_button.fluid_button_mini_player:before { + background: url('../static/miniplayer-toggle-off.svg') no-repeat 0 0; + background-size: 20px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_back { + margin-left: 5px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_video_source:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen_exit:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_fullscreen:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mute:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_volume:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_pause:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_play:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_back:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_skip_forward:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_playback_rate:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_download:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_theatre:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_subtitles:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_cardboard:hover:before, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_mini_player:hover:before { + opacity: 1; +} + +.fp_title { + position: absolute; + top: 10px; + left: 10px; + color: #ffffff; + font-size: 15px; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + font-weight: normal; + white-space: nowrap; +} + +/* Pulse class and keyframe animation */ +.transform-active { + animation: flash 1s infinite; + display: inline-block !important; + opacity: 0; +} + +@-webkit-keyframes flash { + 0% { + opacity: 0.6; + -webkit-box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5); + } + 70% { + -webkit-box-shadow: 0 0 0 20px rgba(255, 255, 255, 0); + } + 100% { + opacity: 0; + display: none; + -webkit-box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); + } +} + +@keyframes flash { + 0% { + opacity: 0.6; + -moz-box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5); + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.6); + } + 70% { + -moz-box-shadow: 0 0 0 20px rgba(255, 255, 255, 0); + box-shadow: 0 0 0 20px rgba(255, 255, 255, 0); + } + 100% { + opacity: 0; + display: none; + -moz-box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); + } +} + +.fluid_nonLinear_top, .fluid_nonLinear_middle, .fluid_nonLinear_bottom { + flex-direction: column; + align-items: center; + cursor: pointer; + display: flex; + vertical-align: middle; + align-content: center; + border: 1px solid #777777; + position: absolute; + left: 50%; + margin-right: -50%; + background-color: rgba(0, 0, 0, 0.7); +} + +.fluid_nonLinear_top { + top: 20px; + transform: translate(-50%); +} + +.fluid_nonLinear_middle { + top: 50%; + transform: translate(-50%, -50%); +} + +.fluid_nonLinear_bottom { + bottom: 50px; + transform: translate(-50%); +} + +.fluid_vpaidNonLinear_top, .fluid_vpaidNonLinear_middle, .fluid_vpaidNonLinear_bottom { + flex-direction: column; + align-items: center; + cursor: pointer; + vertical-align: middle; + align-content: center; + position: absolute; + display: flex; +} + +.fluid_vpaidNonLinear_frame { + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.fluid_vpaidNonLinear_top { + top: 20px; +} + +.fluid_vpaidNonLinear_middle { + top: 50%; +} + +.fluid_vpaidNonLinear_bottom { + bottom: 50px; +} + +.add_icon_clickthrough { + color: #F2C94C; + line-height: 18px; + text-overflow: ellipsis; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + display: inline-block; +} + +.add_icon_clickthrough:before { + background: url('../static/fluid-icons.svg') no-repeat; + height: 18px; + width: 18px; + top: 30px; + padding: 3px 22px 0 0; + content: ""; + background-position: -162px -57px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_theatre, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_playback_rate, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_video_source, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_download, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_subtitles, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_cardboard { + float: right; + padding-right: 5px; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_theatre, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_playback_rate, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_video_source, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_download, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_subtitles, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_button.fluid_button_cardboard { + display: none; +} + +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_subtitles .fluid_subtitles_list, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_button_video_source .fluid_video_sources_list, +.fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_video_playback_rates { + z-index: 888888 !important; + opacity: 0.9 !important; +} + +.fluid_video_playback_rates_item { + padding: 9px 25px 9px 25px; + line-height: 15px; + text-align: center; +} + +.fluid_theatre_mode { + position: fixed; + float: left; + top: 0; + z-index: 10; + box-shadow: 0px 15px 25px rgba(0, 0, 0, 0.8); +} + +.fluid_mini_player_mode { + position: fixed; + bottom: 10px; + right: 10px; + z-index: 10; +} + +.source_button_icon { + background: url('../static/fluid-icons.svg') no-repeat; + float: left; + cursor: pointer; + height: 18px; + width: 18px; + background-position: -164px -21px; + opacity: 0; +} + +.subtitle_button_icon { + background: url('../static/fluid-icons.svg') no-repeat; + float: left; + cursor: pointer; + height: 18px; + width: 18px; + background-position: -164px -21px; + opacity: 0; +} + +.source_selected { + opacity: 1 !important; +} + +.subtitle_selected { + opacity: 1 !important; +} + +@media only screen and (min-device-width: 375px) { + .fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_duration { + padding-left: 95px; + } + + .fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_duration.no_volume_bar { + padding-left: 32px; + } + + .fluid_video_wrapper.fluid_player_layout_default .fluid_controls_container .fluid_controls_right .fluid_control_volume_container { + display: block; + } +} + +.fp_logo { + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: visibility 0.3s ease-in-out, opacity 0.3s ease-in-out; + -ms-transition: visibility 0.3s ease-in-out, opacity 0.3s ease-in-out; + -o-transition: visibility 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: visibility 0.3s ease-in-out, opacity 0.3s ease-in-out; +} + +.fp_hd_source::before { + font-weight: bolder; + font-size: 6pt; + content: 'HD'; + padding-left: 3px; +} + +/** MiniPlayer */ + +.fluid_video_wrapper.fluid_player_layout_default .mini-player-close-button-wrapper { + display: none; +} + +.fluid_video_wrapper.fluid_mini_player_mode .mini-player-close-button-wrapper { + position: absolute; + background: rgb(0,0,0); + background: linear-gradient(45deg, rgba(0,0,0,0) 90%, rgba(0,0,0,0.6) 110%); + height: 100%; + width: 100%; + top: 0; + right: 0; + z-index: 31; + display: block; + opacity: 0; + -webkit-transition: opacity 0.3s ease-in-out; + -moz-transition: opacity 0.3s ease-in-out; + -ms-transition: opacity 0.3s ease-in-out; + -o-transition: opacity 0.3s ease-in-out; + transition: opacity 0.3s ease-in-out; + pointer-events: none; +} + +.fluid_video_wrapper.fluid_mini_player_mode .mini-player-close-button { + position: absolute; + background: transparent url("../static/close-icon.svg") no-repeat scroll center center; + height: 22px; + width: 22px; + top: 6px; + right: 6px; + background-size: 22px; + cursor: pointer; + z-index: 32; + display: block; + pointer-events: all; +} + +.fluid_video_wrapper.fluid_mini_player_mode:hover .mini-player-close-button-wrapper { + opacity: 1; +} + +.fluid_video_wrapper.fluid_mini_player_mode .disable-mini-player-mobile { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.fluidplayer-miniplayer-player-placeholder { + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 1.5rem; + font-family: -apple-system, BlinkMacSystemFont, 'segoe ui', roboto, oxygen-sans, ubuntu, cantarell, 'helvetica neue', 'arial', sans-serif, 'apple color emoji', 'segoe ui emoji', 'segoe ui symbol'; + background: #000 url('../static/miniplayer-toggle-on.svg') no-repeat 50% calc(50% - 48px); + background-size: 48px; + cursor: pointer; +} + +.fluid_video_wrapper.fluid_mini_player_mode.fluid_video_wrapper.fluid_mini_player_mode--top-left { + top: 10px; + left: 10px; +} + +.fluid_video_wrapper.fluid_mini_player_mode.fluid_video_wrapper.fluid_mini_player_mode--top-right { + top: 10px; + right: 10px; +} + +.fluid_video_wrapper.fluid_mini_player_mode.fluid_video_wrapper.fluid_mini_player_mode--bottom-left { + bottom: 10px; + left: 10px; +} + +.fluid_video_wrapper.fluid_mini_player_mode.fluid_video_wrapper.fluid_mini_player_mode--bottom-right { + bottom: 10px; + right: 10px; +} + +@media screen and (max-width: 768px) { + .fluid_video_wrapper.fluid_mini_player_mode > *:not(video, .ad_countdown, .fluid_nonLinear_ad, .disable-mini-player-mobile) { + display: none; + } + + .fluid_video_wrapper.fluid_mini_player_mode .fluid_nonLinear_ad { + z-index: 100; + } + + .fluid_video_wrapper.fluid_mini_player_mode .fluid_nonLinear_bottom { + bottom: 16px; + } + + .fluid_video_wrapper.fluid_mini_player_mode .fluid_nonLinear_top { + top: 16px; + } + + .fluid_video_wrapper.fluid_mini_player_mode .ad_countdown { + display: inline-block !important; + } + + .fluid_video_wrapper.fluid_mini_player_mode .disable-mini-player-mobile { + display: block; + touch-action: none; + } + + .fluidplayer-miniplayer-player-placeholder { + font-size: 1.25rem !important; + background-size: 32px !important; + background-position-y: calc(50% - 32px) !important; + } +} + +.fluid_video_wrapper .fluid_player_skip_offset { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + display: grid; + grid-template-columns: 1fr 1fr; + pointer-events: none; +} + +.fluid_video_wrapper .fluid_player_skip_offset__backward { + display: flex; + align-items: center; + margin: 10% 10% 10% 0; +} + +.fluid_video_wrapper .fluid_player_skip_offset__backward-icon { + background: rgba(0, 0, 0, .5) url('../static/skip-backward.svg') no-repeat -2px 3px; + width: 150px; + height: 150px; + background-size: contain; + opacity: 0; + transition: opacity 400ms ease-in; + border-radius: 150px; + margin-left: 20%; + pointer-events: none; + background-origin: content-box; + padding: 10px; +} + +.fluid_video_wrapper .fluid_player_skip_offset__forward { + display: flex; + align-items: center; + flex-direction: row-reverse; + margin: 10% 0 10% 10%; +} + +.fluid_video_wrapper .fluid_player_skip_offset__forward-icon { + background: rgba(0, 0, 0, .5) url('../static/skip-forward.svg') no-repeat -2px 3px; + width: 150px; + height: 150px; + background-size: cover; + opacity: 0; + transition: opacity 400ms ease-in; + border-radius: 150px; + margin-right: 20%; + pointer-events: none; + background-origin: content-box; + padding: 10px; +} + +.fluid_video_wrapper .fluid_player_skip_offset__backward-icon.animate, +.fluid_video_wrapper .fluid_player_skip_offset__forward-icon.animate { + opacity: 1; + transition: opacity 150ms ease-out; +} + +@media screen and (max-width: 768px) { + .fluid_video_wrapper .fluid_player_skip_offset__backward-icon, + .fluid_video_wrapper .fluid_player_skip_offset__forward-icon { + width: 50px; + height: 50px; + border-radius: 50px; + background-position-x: 0; + background-position-y: 0; + padding: 5px; + } +} diff --git a/client/fluid-player/src/css/suggestedVideos.css b/client/fluid-player/src/css/suggestedVideos.css new file mode 100644 index 0000000..18c1126 --- /dev/null +++ b/client/fluid-player/src/css/suggestedVideos.css @@ -0,0 +1,143 @@ +.suggested_tile_grid { + --thumbnail-height: 120px; + } + +.suggested_tile_grid { + position: absolute; + left: 0; + width: 100%; + z-index: 100; + display: flex; + height: var(--thumbnail-height); + overflow-x: auto; + overflow-y: hidden; + justify-content: flex-start; + bottom: 53px; + top: initial; + column-gap: 10px; + row-gap: 10px; + white-space: nowrap; +} + +.suggested_tile { + aspect-ratio: 16/9; + display: flex; + justify-content: center; + align-items: center; + position: relative; + border-radius: 2px; + flex-shrink: 0; + width: calc(var(--thumbnail-height) * (16/9)); + height: var(--thumbnail-height); + + img { + height: -webkit-fill-available; + } +} + +.suggested_tile_image { + max-width: 100%; + max-height: 100%; +} + +.suggested_tile:first-child { + margin-left: 20px; +} + +.suggested_tile:last-child { + margin-right: 20px; +} + +.suggested_tile_overlay { + position: absolute; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + color: #ffffff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.25s; +} + +.suggested_tile_overlay .suggested_tile_title { + position: absolute; + bottom: 0px; + left: 10px; +} + +.suggested_tile_overlay--blur { + backdrop-filter: blur(5px); +} + +.suggested_tile_overlay>* { + transform: translateY(20px); + transition: transform 0.25s; +} + +.suggested_tile_overlay:hover { + opacity: 1; +} + +.suggested_tile_overlay:hover>* { + transform: translateY(0); +} + +.suggested_tile:hover { + cursor: pointer; +} + +@media only screen and (max-width: 600px) and (orientation: portrait) { + .suggested_tile_grid { + display: none; + } +} + +/* Medium devices like tablet portrait */ +@media only screen and (min-width: 992px) { + .suggested_tile_grid { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: calc(100% - 53px); + display: grid; + grid-template-columns: repeat(3, 20%); + grid-template-rows: min-content min-content; + column-gap: 40px; + row-gap: 10px; + z-index: 100; + align-content: center; + justify-content: center; + } + + /* hide the last 6 video tiles */ + .suggested_tile:nth-child(n+7) { + display: none; + } + + .suggested_tile:first-child { + margin-left: 0px; + } + + .suggested_tile:last-child { + margin-right: 0px; + } +} + +/* Desktop */ +@media only screen and (min-width: 1200px) { + .suggested_tile_grid { + grid-template-columns: repeat(4, 20%); + column-gap: 10px; + } + .suggested_tile { + width: initial; + height: initial; + } + .suggested_tile:nth-child(n+7) { + display: flex; + } +} diff --git a/client/fluid-player/src/fluidplayer.js b/client/fluid-player/src/fluidplayer.js new file mode 100644 index 0000000..2b334f8 --- /dev/null +++ b/client/fluid-player/src/fluidplayer.js @@ -0,0 +1,3469 @@ +// @ts-check +'use strict'; + +// Player modules +import VPAIDModule from './modules/vpaid'; +import VASTModule from './modules/vast'; +import CardboardModule from './modules/cardboard'; +import SubtitleModule from './modules/subtitles'; +import TimelineModule from './modules/timeline'; +import AdSupportModule from './modules/adsupport'; +import StreamingModule from './modules/streaming'; +import UtilsModule from './modules/utils'; +import SuggestedVideosModule from './modules/suggestedVideos'; +import MiniPlayerModule from './modules/miniplayer'; + +const FP_MODULES = [ + VPAIDModule, + VASTModule, + CardboardModule, + SubtitleModule, + TimelineModule, + AdSupportModule, + StreamingModule, + UtilsModule, + SuggestedVideosModule, + MiniPlayerModule +]; + +// Determine build mode +// noinspection JSUnresolvedVariable +const FP_DEVELOPMENT_MODE = typeof FP_ENV !== 'undefined' && FP_ENV === 'development'; + +// Are we running in debug mode? +// noinspection JSUnresolvedVariable +const FP_RUNTIME_DEBUG = typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true; + +let playerInstances = 0; + +/** + * @this {import("./types").IFluidPlayer} + */ +const fluidPlayerClass = function () { + // "self" always points to current instance of the player within the scope of the instance + // This should help readability and context awareness slightly... + const self = this; + + self.domRef = { + player: null + }; + + // noinspection JSUnresolvedVariable + self.version = typeof FP_BUILD_VERSION !== 'undefined' ? FP_BUILD_VERSION : ''; + // noinspection JSUnresolvedVariable + self.homepage = typeof FP_HOMEPAGE !== 'undefined' + ? FP_HOMEPAGE + '/?utm_source=player&utm_medium=context_menu&utm_campaign=organic' + : ''; + self.destructors = []; + + self.init = (playerTarget, options) => { + // Install player modules and features + const moduleOptions = { + development: FP_DEVELOPMENT_MODE, + debug: FP_RUNTIME_DEBUG, + }; + + for (const playerModule of FP_MODULES) { + playerModule(self, moduleOptions); + } + /** + * @type {HTMLVideoElement} + */ + let playerNode; + if (playerTarget instanceof HTMLVideoElement) { + playerNode = playerTarget; + + // Automatically assign ID if none exists + if (!playerTarget.id) { + playerTarget.id = 'fluid_player_instance_' + (playerInstances++).toString(); + } + } else if (typeof playerTarget === 'string' || playerTarget instanceof String) { + // @ts-expect-error + playerNode = document.getElementById(playerTarget); + } else { + throw 'Invalid initializer - player target must be HTMLVideoElement or ID'; + } + + if (!playerNode) { + throw 'Could not find a HTML node to attach to for target ' + playerTarget + '"'; + } + + if (playerNode.classList.contains('js-fluid-player')) { + throw 'Invalid initializer - player target already is initialized'; + } + + playerNode.setAttribute('playsinline', ''); + playerNode.setAttribute('webkit-playsinline', ''); + playerNode.classList.add('js-fluid-player'); + + self.domRef.player = playerNode; + self.vrROTATION_POSITION = 0.1; + self.vrROTATION_SPEED = 80; + self.vrMode = false; + self.vrPanorama = null; + self.vrViewer = null; + self.vpaidTimer = null; + self.vpaidAdUnit = null; + self.vastOptions = null; + /** + * Don't use this as a way to change the DOM. DOM manipulation should be done with domRef. + */ + self.videoPlayerId = playerNode.id; + self.originalSrc = self.getCurrentSrc(); + self.isCurrentlyPlayingAd = false; + self.recentWaiting = false; + self.latestVolume = 1; + self.currentVideoDuration = 0; + self.firstPlayLaunched = false; + self.suppressClickthrough = false; + self.timelinePreviewData = []; + self.mainVideoCurrentTime = 0; + self.mainVideoDuration = 0; + self.isTimer = false; + self.timer = null; + self.timerPool = {}; + self.rollsById = {}; + self.adPool = {}; + self.adGroupedByRolls = {}; + self.onPauseRollAdPods = []; + self.currentOnPauseRollAd = ''; + self.preRollAdsResolved = false; + self.preRollAdPods = []; + self.preRollAdPodsLength = 0; + self.preRollVastResolved = 0; + self.temporaryAdPods = []; + self.availableRolls = ['preRoll', 'midRoll', 'postRoll', 'onPauseRoll']; + self.supportedNonLinearAd = ['300x250', '468x60', '728x90']; + self.autoplayAfterAd = true; + self.nonLinearDuration = 15; + self.supportedStaticTypes = ['image/gif', 'image/jpeg', 'image/png']; + self.inactivityTimeout = null; + self.isUserActive = null; + self.nonLinearVerticalAlign = 'bottom'; + self.vpaidNonLinearCloseButton = true; + self.showTimeOnHover = true; + self.initialAnimationSet = true; + self.theatreMode = false; + self.theatreModeAdvanced = false; + self.fullscreenMode = false; + self.originalWidth = playerNode.offsetWidth; + self.originalHeight = playerNode.offsetHeight; + self.dashPlayer = false; + self.hlsPlayer = false; + self.dashScriptLoaded = false; + self.hlsScriptLoaded = false; + self.isPlayingMedia = false; + self.isSwitchingSource = false; + self.isLoading = false; + self.isInIframe = self.inIframe(); + self.mainVideoReadyState = false; + self.xmlCollection = []; + self.inLineFound = null; + self.fluidStorage = {}; + self.fluidPseudoPause = false; + self.mobileInfo = self.getMobileOs(); + self.events = {}; + self.timeSkipOffsetAmount = 10; + // Only for linear ads, non linear are not taken into account + self.currentMediaSourceType = 'source'; + + //Default options + self.displayOptions = { + layoutControls: { + mediaType: self.getCurrentSrcType(), + primaryColor: false, + posterImage: false, + posterImageSize: 'contain', + adProgressColor: '#f9d300', + playButtonShowing: true, + playPauseAnimation: true, + closeButtonCaption: 'Close', // Remove? + fillToContainer: false, + autoPlay: false, + preload: 'auto', + mute: false, + loop: null, + keyboardControl: true, + allowDownload: false, + playbackRateEnabled: false, + subtitlesEnabled: false, + subtitlesOnByDefault: true, + showCardBoardView: false, + showCardBoardJoystick: false, + allowTheatre: true, + doubleclickFullscreen: true, + autoRotateFullScreen: false, + theatreSettings: { + width: '100%', + height: '60%', + marginTop: 0, + horizontalAlign: 'center', + keepPosition: false + }, + theatreAdvanced: { + theatreElement: null, + }, + title: null, + logo: { + imageUrl: null, + position: 'top left', + clickUrl: null, + opacity: 1, + mouseOverImageUrl: null, + imageMargin: '2px', + hideWithControls: false, + showOverAds: false + }, + controlBar: { + autoHide: false, + autoHideTimeout: 3, + animated: true, + playbackRates: ['x2', 'x1.5', 'x1', 'x0.5'] + }, + timelinePreview: { + spriteImage: false, + spriteRelativePath: false + }, + htmlOnPauseBlock: { + html: null, + height: null, + width: null + }, + layout: 'default', //options: 'default', '' + playerInitCallback: (function () { + }), + persistentSettings: { + volume: true, + quality: true, + speed: true, + theatre: true + }, + controlForwardBackward: { + show: false, + doubleTapMobile: true + }, + contextMenu: { + controls: true, + links: [] + }, + miniPlayer: { + enabled: true, + width: 400, + height: 225, + widthMobile: 50, + placeholderText: 'Playing in Miniplayer', + position: 'bottom right', + autoToggle: false, + }, + roundedCorners: 0 + }, + suggestedVideos: { + configUrl: null + }, + vastOptions: { + adList: {}, + skipButtonCaption: 'Skip ad in [seconds]', + skipButtonClickCaption: 'Skip Ad ', + adText: null, + adTextPosition: 'top left', + adCTAText: 'Visit now!', + adCTATextPosition: 'bottom right', + adCTATextVast: false, + adClickable: true, + vastTimeout: 5000, + showProgressbarMarkers: false, + allowVPAID: false, + showPlayButton: false, + maxAllowedVastTagRedirects: 3, + vpaidTimeout: 3000, + + vastAdvanced: { + vastLoadedCallback: (function () { + }), + noVastVideoCallback: (function () { + }), + vastVideoSkippedCallback: (function () { + }), + vastVideoEndedCallback: (function () { + }) + } + }, + hls: { + overrideNative: false + }, + captions: { + play: 'Play', + pause: 'Pause', + mute: 'Mute', + unmute: 'Unmute', + fullscreen: 'Fullscreen', + subtitles: 'Subtitles', + exitFullscreen: 'Exit Fullscreen', + }, + debug: FP_RUNTIME_DEBUG, + modules: { + configureHls: (options) => { + return options; + }, + onBeforeInitHls: (hls) => { + }, + onAfterInitHls: (hls) => { + }, + configureDash: (options) => { + return options; + }, + onBeforeInitDash: (dash) => { + }, + onAfterInitDash: (dash) => { + } + }, + onBeforeXMLHttpRequestOpen: (request) => { + }, + onBeforeXMLHttpRequest: (request) => { + if (FP_RUNTIME_DEBUG || FP_DEVELOPMENT_MODE) { + console.debug('[FP_DEBUG] Request made', request); + } + } + }; + + if (!!options.hlsjsConfig) { + console.error('[FP_ERROR] player option hlsjsConfig is removed and has no effect. ' + + 'Use module callbacks instead!') + } + + /** + * Replaces values from objects without replacing the default object + * + * @param defaults + * @param options + * @returns {object} + */ + function overrideDefaults(defaults, options) { + Object.keys(options).forEach(defaultKey => { + if ( + typeof options[defaultKey] === 'object' && + options[defaultKey] !== null && + !Array.isArray(options[defaultKey]) + ) { + overrideDefaults(defaults[defaultKey], options[defaultKey]); + } else if (typeof options[defaultKey] !== 'undefined') { + defaults[defaultKey] = options[defaultKey]; + } + }); + + return defaults; + } + + overrideDefaults(self.displayOptions, options); + + self.domRef.wrapper = self.setupPlayerWrapper(); + + playerNode.addEventListener('webkitfullscreenchange', self.recalculateAdDimensions); + playerNode.addEventListener('fullscreenchange', self.recalculateAdDimensions); + playerNode.addEventListener('waiting', self.onRecentWaiting); + playerNode.addEventListener('pause', self.onFluidPlayerPause); + playerNode.addEventListener('error', self.onErrorDetection); + playerNode.addEventListener('ended', self.onMainVideoEnded); + playerNode.addEventListener('durationchange', () => { + self.currentVideoDuration = self.getCurrentVideoDuration(); + }); + + // 'loadedmetadata' inconsistently fires because the audio can already be loaded when the listener is added. + // Here we use readystate to see if metadata has already loaded + if (playerNode.readyState > 0) { + self.mainVideoReady(); + } else { + playerNode.addEventListener('loadedmetadata', self.mainVideoReady); + } + + if (self.displayOptions.layoutControls.showCardBoardView) { + // This fixes cross origin errors on three.js + playerNode.setAttribute('crossOrigin', 'anonymous'); + } + + //Manually load the video duration if the video was loaded before adding the event listener + self.currentVideoDuration = self.getCurrentVideoDuration(); + + if (isNaN(self.currentVideoDuration) || !isFinite(self.currentVideoDuration)) { + self.currentVideoDuration = 0; + } + + self.setLayout(); + + //Set the volume control state + self.latestVolume = playerNode.volume; + + // Set the default animation setting + self.initialAnimationSet = self.displayOptions.layoutControls.playPauseAnimation; + + //Set the custom fullscreen behaviour + self.handleFullscreen(); + + self.initLogo(); + + self.initTitle(); + + self.initMute(); + + self.initLoop(); + + self.displayOptions.layoutControls.playerInitCallback(); + + self.createVideoSourceSwitch(); + + self.createSubtitles(); + + self.createCardboard(); + + self.userActivityChecker(); + + self.setVastList(); + + self.setPersistentSettings(); + + self.generateSuggestedVideoList(); + + // Previously prevented to be initialized if preRolls were set up + // but now the streamers support reinitialization + self.initialiseStreamers(); + self.detectLiveStream(); + self.showLiveIndicator(); + + const _play_videoPlayer = playerNode.play; + + playerNode.play = function () { + let promise = null; + + if (self.displayOptions.layoutControls.showCardBoardView) { + if (typeof DeviceOrientationEvent !== 'undefined' && typeof DeviceOrientationEvent.requestPermission === 'function') { + DeviceOrientationEvent.requestPermission() + .then(function (response) { + if (response === 'granted') { + self.debugMessage('DeviceOrientationEvent permission granted!'); + } + }) + .catch(console.error); + } + } + + try { + promise = _play_videoPlayer.apply(this, arguments); + + if (promise !== undefined && promise !== null) { + promise.then(() => { + self.isPlayingMedia = true; + clearTimeout(self.promiseTimeout); + }).catch(error => { + console.error('[FP_ERROR] Playback error', error); + const isAbortError = (typeof error.name !== 'undefined' && error.name === 'AbortError'); + // Ignore abort errors which caused for example Safari or autoplay functions + // (example: interrupted by a new load request) + // (example: interrupted by a new load request) + if (isAbortError) { + // Ignore AbortError error reporting + } else { + self.announceLocalError(202, 'Failed to play video.'); + } + + clearTimeout(self.promiseTimeout); + }); + + self.promiseTimeout = setTimeout(function () { + if (self.isPlayingMedia === false) { + self.announceLocalError(204, '[FP_ERROR] Timeout error. Failed to play video?'); + } + }, 5000); + + } + + return promise; + } catch (error) { + console.error('[FP_ERROR] Playback error', error); + self.announceLocalError(201, 'Failed to play video.'); + } + }; + + const videoPauseOriginal = playerNode.pause; + playerNode.pause = function () { + if (self.isPlayingMedia === true) { + self.isPlayingMedia = false; + return videoPauseOriginal.apply(this, arguments); + } + + // just in case + if (self.isCurrentlyPlayingVideo(self.domRef.player)) { + try { + self.isPlayingMedia = false; + return videoPauseOriginal.apply(this, arguments); + } catch (e) { + self.announceLocalError(203, 'Failed to play video.'); + } + } + }; + + if (!!self.displayOptions.layoutControls.autoPlay && !self.dashScriptLoaded && !self.hlsScriptLoaded) { + //There is known issue with Safari 11+, will prevent autoPlay, so we wont try + const browserVersion = self.getBrowserVersion(); + + if ('Safari' === browserVersion.browserName) { + return; + } + + playerNode.play(); + } + + if (!self.mobileInfo.userOs) { + if (!self.displayOptions.layoutControls.controlBar.autoHide) { + self.domRef.wrapper.addEventListener('mouseleave', self.handleMouseleave, false); + } + self.domRef.wrapper.addEventListener('mouseenter', self.showControlBar, false); + self.domRef.wrapper.addEventListener('mouseenter', self.showTitle, false); + } else { + //On mobile mouseleave behavior does not make sense, so it's better to keep controls, once the playback starts + //Autohide behavior on timer is a separate functionality + self.hideControlBar(); + self.domRef.wrapper.addEventListener('touchstart', self.showControlBar, { passive: true }); + } + + //Keyboard Controls + if (self.displayOptions.layoutControls.keyboardControl) { + self.keyboardControl(); + } + + if (self.displayOptions.layoutControls.controlBar.autoHide) { + self.linkControlBarUserActivity(); + } + + // Hide the captions on init if user added subtitles track. + // We are taking captions track kind of as metadata + try { + if (!!self.domRef.player.textTracks) { + for (const textTrack of self.domRef.player.textTracks) { + textTrack.mode = 'hidden'; + } + } + } catch (_ignored) { + } + }; + + self.getCurrentVideoDuration = () => { + if (self.domRef.player) { + return self.domRef.player.duration; + } + + return 0; + }; + + self.toggleLoader = (showLoader) => { + self.isLoading = !!showLoader; + + const loaderDiv = self.domRef.wrapper.querySelector('.vast_video_loading'); + + if (loaderDiv) { + loaderDiv.style.display = showLoader ? 'table' : 'none'; + } + }; + + self.sendRequest = (url, withCredentials, timeout, functionReadyStateChange) => { + const xmlHttpReq = new XMLHttpRequest(); + + xmlHttpReq.onreadystatechange = functionReadyStateChange; + + self.displayOptions.onBeforeXMLHttpRequestOpen(xmlHttpReq); + + xmlHttpReq.open('GET', url, true); + xmlHttpReq.withCredentials = withCredentials; + xmlHttpReq.timeout = timeout; + + self.displayOptions.onBeforeXMLHttpRequest(xmlHttpReq); + + xmlHttpReq.send(); + }; + + /** + * Makes a XMLHttpRequest encapsulated by a Promise + * + * @param url + * @param withCredentials + * @param timeout + * @returns {Promise} + */ + self.sendRequestAsync = async (url, withCredentials, timeout) => { + return await new Promise((resolve, reject) => { + const xmlHttpReq = new XMLHttpRequest(); + + xmlHttpReq.onreadystatechange = (event) => { + const response = event.target; + + if (response.readyState === 4 && response.status >= 200 && response.status < 300) { + resolve(response); + } else if (response.readyState === 4) { + reject(response); + } + }; + + self.displayOptions.onBeforeXMLHttpRequestOpen(xmlHttpReq); + + xmlHttpReq.open('GET', url, true); + xmlHttpReq.withCredentials = withCredentials; + xmlHttpReq.timeout = timeout; + + self.displayOptions.onBeforeXMLHttpRequest(xmlHttpReq); + + xmlHttpReq.send(); + }) + }; + + // TODO: rename + self.announceLocalError = (code, msg) => { + const parsedCode = typeof (code) !== 'undefined' ? parseInt(code) : 900; + let message = '[Error] (' + parsedCode + '): '; + message += !msg ? 'Failed to load Vast' : msg; + console.warn(message); + }; + + // TODO: move this somewhere else and refactor + self.debugMessage = (...msg) => { + const style = 'color: #fff; font-weight: bold; background-color: #1a5e87; padding: 3px 6px; border-radius: 3px;'; + + if (self.displayOptions.debug) { + console.log('%cFP DEBUG', style, ...msg); + } + }; + + self.onMainVideoEnded = (event) => { + self.debugMessage('onMainVideoEnded is called'); + + if (self.isCurrentlyPlayingAd && self.autoplayAfterAd) { // It may be in-stream ending, and if it's not postroll then we don't execute anything + return; + } + + //we can remove timer as no more ad will be shown + if (Math.floor(self.getCurrentTime()) >= Math.floor(self.mainVideoDuration)) { + + // play pre-roll ad + // sometime pre-roll ad will be missed because we are clearing the timer + self.adKeytimePlay(Math.floor(self.mainVideoDuration)); + + clearInterval(self.timer); + } + + if (!!self.displayOptions.layoutControls.loop) { + self.switchToMainVideo(); + self.playPauseToggle(); + } + + // Event listener doesn't wait on flags to be flipped from post roll ads, needs small time out to compensate + setTimeout(() => { + if (!self.isCurrentlyPlayingAd && self.displayOptions.suggestedVideos.configUrl) { + self.displaySuggestedVideos(); + } + }, 100); + }; + + self.getCurrentTime = () => { + return self.isCurrentlyPlayingAd + ? self.mainVideoCurrentTime + : self.domRef.player.currentTime; + }; + + /** + * Gets the src value of the first source element of the video tag. + * + * @returns string|null + */ + self.getCurrentSrc = () => { + const sources = self.domRef.player.getElementsByTagName('source'); + + if (sources.length) { + return sources[0].getAttribute('src'); + } + + return null; + }; + + /** + * Src types required for streaming elements + */ + self.getCurrentSrcType = () => { + const sources = self.domRef.player.getElementsByTagName('source'); + + if (!sources.length) { + return null; + } + + for (let i = 0; i < sources.length; i++) { + if (sources[i].getAttribute('src') === self.originalSrc) { + return sources[i].getAttribute('type').toLowerCase(); + } + } + + return null; + }; + + self.onRecentWaiting = () => { + self.recentWaiting = true; + + setTimeout(function () { + self.recentWaiting = false; + }, 1000); + }; + + /** + * Dispatches a custom pause event which is not present when seeking. + */ + self.onFluidPlayerPause = () => { + setTimeout(function () { + if (self.recentWaiting) { + return; + } + + const event = document.createEvent('CustomEvent'); + event.initEvent('fluidplayerpause', false, true); + self.domRef.player.dispatchEvent(event); + }, 100); + }; + + self.checkShouldDisplayVolumeBar = () => { + return 'iOS' !== self.getMobileOs().userOs; + }; + + self.generateCustomControlTags = (options) => { + /** + * @type {CustomControls} + */ + const controls = {}; + + // Loader + controls.loader = document.createElement('div'); + controls.loader.className = 'vast_video_loading'; + controls.loader.style.display = 'none'; + + // Root element + controls.root = document.createElement('div'); + controls.root.className = 'fluid_controls_container'; + + if (!options.displayVolumeBar) { + controls.root.className = controls.root.className + ' no_volume_bar'; + } + + if (options.controlForwardBackward) { + controls.root.className = controls.root.className + ' skip_controls'; + } + + // Left container + controls.leftContainer = document.createElement('div'); + controls.leftContainer.className = 'fluid_controls_left'; + controls.root.appendChild(controls.leftContainer); + + // Left container -> Play/Pause + controls.playPause = document.createElement('div'); + controls.playPause.className = 'fluid_button fluid_button_play fluid_control_playpause'; + controls.leftContainer.appendChild(controls.playPause); + + if (options.controlForwardBackward) { + // Left container -> Skip backwards + controls.skipBack = document.createElement('div'); + controls.skipBack.className = 'fluid_button fluid_button_skip_back'; + controls.leftContainer.appendChild(controls.skipBack); + + // Left container -> Skip forward + controls.skipForward = document.createElement('div'); + controls.skipForward.className = 'fluid_button fluid_button_skip_forward'; + controls.leftContainer.appendChild(controls.skipForward); + } + + // Progress container + controls.progressContainer = document.createElement('div'); + controls.progressContainer.className = 'fluid_controls_progress_container fluid_slider'; + controls.root.appendChild(controls.progressContainer); + + // Progress container -> Progress wrapper + controls.progressWrapper = document.createElement('div'); + controls.progressWrapper.className = 'fluid_controls_progress'; + controls.progressContainer.appendChild(controls.progressWrapper); + + // Progress container -> Progress wrapper -> Current progress + controls.progressCurrent = document.createElement('div'); + controls.progressCurrent.className = 'fluid_controls_currentprogress'; + controls.progressCurrent.style.backgroundColor = options.primaryColor; + controls.progressWrapper.appendChild(controls.progressCurrent); + + // Progress container -> Progress wrapper -> Current progress -> Marker + controls.progress_current_marker = document.createElement('div'); + controls.progress_current_marker.className = 'fluid_controls_currentpos'; + controls.progressCurrent.appendChild(controls.progress_current_marker); + + // Progress container -> Buffered indicator + controls.bufferedIndicator = document.createElement('div'); + controls.bufferedIndicator.className = 'fluid_controls_buffered'; + controls.progressContainer.appendChild(controls.bufferedIndicator); + + // Progress container -> Ad markers + controls.adMarkers = document.createElement('div'); + controls.adMarkers.className = 'fluid_controls_ad_markers_holder'; + controls.progressContainer.appendChild(controls.adMarkers); + + // Right container + controls.rightContainer = document.createElement('div'); + controls.rightContainer.className = 'fluid_controls_right'; + controls.root.appendChild(controls.rightContainer); + + // Right container -> Fullscreen + controls.fullscreen = document.createElement('div'); + controls.fullscreen.className = 'fluid_button fluid_control_fullscreen fluid_button_fullscreen'; + controls.rightContainer.appendChild(controls.fullscreen); + + if (options.miniPlayer.enabled) { + // Right container -> MiniPlayer + controls.miniPlayer = document.createElement('div'); + controls.miniPlayer.className = 'fluid_button fluid_control_mini_player fluid_button_mini_player'; + controls.rightContainer.appendChild(controls.miniPlayer); + } + + // Right container -> Theatre + controls.theatre = document.createElement('div'); + controls.theatre.className = 'fluid_button fluid_control_theatre fluid_button_theatre'; + controls.rightContainer.appendChild(controls.theatre); + + // Right container -> Cardboard + controls.cardboard = document.createElement('div'); + controls.cardboard.className = 'fluid_button fluid_control_cardboard fluid_button_cardboard'; + controls.rightContainer.appendChild(controls.cardboard); + + // Right container -> Subtitles + controls.subtitles = document.createElement('div'); + controls.subtitles.className = 'fluid_button fluid_control_subtitles fluid_button_subtitles'; + controls.rightContainer.appendChild(controls.subtitles); + + // Right container -> Video source + controls.videoSource = document.createElement('div'); + controls.videoSource.className = 'fluid_button fluid_control_video_source fluid_button_video_source'; + controls.rightContainer.appendChild(controls.videoSource); + + // Right container -> Playback rate + controls.playbackRate = document.createElement('div'); + controls.playbackRate.className = 'fluid_button fluid_control_playback_rate fluid_button_playback_rate'; + controls.rightContainer.appendChild(controls.playbackRate); + + // Right container -> Download + controls.download = document.createElement('div'); + controls.download.className = 'fluid_button fluid_control_download fluid_button_download'; + controls.rightContainer.appendChild(controls.download); + + // Right container -> Volume container + controls.volumeContainer = document.createElement('div'); + controls.volumeContainer.className = 'fluid_control_volume_container fluid_slider'; + controls.rightContainer.appendChild(controls.volumeContainer); + + // Right container -> Volume container -> Volume + controls.volume = document.createElement('div'); + controls.volume.className = 'fluid_control_volume'; + controls.volumeContainer.appendChild(controls.volume); + + // Right container -> Volume container -> Volume -> Current + controls.volumeCurrent = document.createElement('div'); + controls.volumeCurrent.className = 'fluid_control_currentvolume'; + controls.volume.appendChild(controls.volumeCurrent); + + // Right container -> Volume container -> Volume -> Current -> position + controls.volumeCurrentPos = document.createElement('div'); + controls.volumeCurrentPos.className = 'fluid_control_volume_currentpos'; + controls.volumeCurrent.appendChild(controls.volumeCurrentPos); + + // Right container -> Volume container + controls.mute = document.createElement('div'); + controls.mute.className = 'fluid_button fluid_button_volume fluid_control_mute'; + controls.rightContainer.appendChild(controls.mute); + + // Right container -> Volume Control + Live Steam Button + const durationContainer = document.createElement('div'); + durationContainer.className = 'fluid_control_duration'; + + controls.duration = document.createElement('div'); + controls.duration.className = 'fluid_fluid_control_duration'; + controls.duration.innerText = '00:00 / 00:00'; + + if (!options.displayVolumeBar) { + durationContainer.className = durationContainer.className + ' no_volume_bar'; + } + + controls.live_indicator = document.createElement('div'); + controls.live_indicator.className = 'fluid_control_live_indicator'; + durationContainer.append(controls.live_indicator, controls.duration); + controls.rightContainer.appendChild(durationContainer); + + return controls; + }; + + self.detectLiveStream = () => { + const sourceElement = this.domRef.player.querySelector('source'); + const sourceUrl = sourceElement?.src || ''; + const isLiveAttribute = sourceElement?.getAttribute('data-live') === 'true'; + const isHLSorDASH = sourceUrl.includes('.m3u8') || sourceUrl.includes('.mpd'); + this.isLiveStream = isLiveAttribute || isHLSorDASH; + }; + + self.showLiveIndicator = () => { + const isLiveStream = this.isLiveStream || false; + if (isLiveStream) { + const liveIndicator = self.domRef.player.parentNode.getElementsByClassName('fluid_control_live_indicator'); + const liveIndicatorButton = document.createElement('span'); + liveIndicatorButton.className = 'fluid_button_live_indicator'; + liveIndicatorButton.innerHTML = `LIVE`; + + liveIndicatorButton.addEventListener('click', () => { + self.domRef.player.currentTime = self.currentVideoDuration; + }); + + for (let i = 0; i < liveIndicator.length; i++) { + liveIndicator[i].appendChild(liveIndicatorButton); + } + + } + }; + + self.controlPlayPauseToggle = () => { + const playPauseButton = self.domRef.player.parentNode.getElementsByClassName('fluid_control_playpause'); + const menuOptionPlay = self.domRef.wrapper.querySelector('.context_option_play'); + const controlsDisplay = self.domRef.player.parentNode.getElementsByClassName('fluid_controls_container'); + const fpLogo = self.domRef.wrapper.querySelector('.logo_holder'); + + const initialPlay = self.domRef.wrapper.querySelector('.fluid_initial_play'); + if (initialPlay) { + self.domRef.wrapper.querySelector('.fluid_initial_play').style.display = "none"; + self.domRef.wrapper.querySelector('.fluid_initial_play_button_container').style.opacity = "1"; + } + + if (!self.domRef.player.paused) { + for (let i = 0; i < playPauseButton.length; i++) { + playPauseButton[i].className = playPauseButton[i].className.replace(/\bfluid_button_play\b/g, 'fluid_button_pause'); + } + + for (let i = 0; i < controlsDisplay.length; i++) { + controlsDisplay[i].classList.remove('initial_controls_show'); + } + + if (fpLogo) { + fpLogo.classList.remove('initial_controls_show'); + } + + if (menuOptionPlay !== null) { + menuOptionPlay.innerHTML = self.displayOptions.captions.pause; + } + + return; + } + + for (let i = 0; i < playPauseButton.length; i++) { + playPauseButton[i].className = playPauseButton[i].className.replace(/\bfluid_button_pause\b/g, 'fluid_button_play'); + } + + for (let i = 0; i < controlsDisplay.length; i++) { + controlsDisplay[i].classList.add('initial_controls_show'); + } + + if (self.isCurrentlyPlayingAd && self.displayOptions.vastOptions.showPlayButton) { + self.domRef.wrapper.querySelector('.fluid_initial_play').style.display = "block"; + self.domRef.wrapper.querySelector('.fluid_initial_play_button_container').style.opacity = "1"; + } + + if (fpLogo) { + fpLogo.classList.add('initial_controls_show'); + } + + if (menuOptionPlay !== null) { + menuOptionPlay.innerHTML = self.displayOptions.captions.play; + } + }; + + self.playPauseAnimationToggle = (play) => { + if (self.isCurrentlyPlayingAd || !self.displayOptions.layoutControls.playPauseAnimation || self.isSwitchingSource) { + return; + } + + const playButtonElement = self.domRef.wrapper.querySelector('.fluid_initial_play_button, .fluid_initial_pause_button'); + + if (play) { + playButtonElement.classList.remove('fluid_initial_pause_button'); + playButtonElement.classList.add('fluid_initial_play_button'); + } else { + playButtonElement.classList.remove('fluid_initial_play_button'); + playButtonElement.classList.add('fluid_initial_pause_button'); + } + + self.domRef.wrapper.querySelector('.fluid_initial_play').classList.add('transform-active'); + setTimeout( + function () { + self.domRef.wrapper.querySelector('.fluid_initial_play').classList.remove('transform-active'); + }, + 800 + ); + }; + + self.contolProgressbarUpdate = () => { + const currentProgressTag = self.domRef.player.parentNode.getElementsByClassName('fluid_controls_currentprogress'); + + for (let i = 0; i < currentProgressTag.length; i++) { + currentProgressTag[i].style.width = (self.domRef.player.currentTime / self.currentVideoDuration * 100) + '%'; + } + }; + + self.controlDurationUpdate = () => { + const currentPlayTime = self.formatTime(self.domRef.player.currentTime); + + let isLiveHls = false; + if (self.hlsPlayer) { + isLiveHls = self.hlsPlayer.levels && + self.hlsPlayer.levels[self.hlsPlayer.currentLevel] && + self.hlsPlayer.levels[self.hlsPlayer.currentLevel].details.live; + } + + let durationText; + if (isNaN(self.currentVideoDuration) || !isFinite(self.currentVideoDuration) || isLiveHls) { + durationText = currentPlayTime; + } else { + const totalTime = self.formatTime(self.currentVideoDuration); + durationText = currentPlayTime + ' / ' + totalTime; + } + + const timePlaceholder = self.domRef.player.parentNode.getElementsByClassName('fluid_control_duration'); + + self.detectLiveStream(); + + for (let i = 0; i < timePlaceholder.length; i++) { + timePlaceholder[i].innerHTML = ''; + + if (this.isLiveStream) { + const liveIndicatorButton = document.createElement('span'); + liveIndicatorButton.className = 'fluid_button_live_indicator'; + liveIndicatorButton.innerHTML = `LIVE`; + liveIndicatorButton.addEventListener('pointerdown', () => { + self.domRef.player.currentTime = self.currentVideoDuration; + }); + timePlaceholder[i].appendChild(liveIndicatorButton); + } + + const durationTextElement = document.createElement('span'); + durationTextElement.className = 'fluid_fluid_control_duration'; + durationTextElement.innerText = durationText; + timePlaceholder[i].appendChild(durationTextElement); + } + }; + + self.contolVolumebarUpdate = () => { + const currentVolumeTag = self.domRef.wrapper.querySelector('.fluid_control_currentvolume'); + const volumeposTag = self.domRef.wrapper.querySelector('.fluid_control_volume_currentpos'); + const volumebarTotalWidth = self.domRef.wrapper.querySelector('.fluid_control_volume').clientWidth; + const volumeposTagWidth = volumeposTag.clientWidth; + const muteButtonTag = self.domRef.player.parentNode.getElementsByClassName('fluid_control_mute'); + const menuOptionMute = self.domRef.wrapper.querySelector('.context_option_mute'); + + if (0 !== self.domRef.player.volume) { + self.latestVolume = self.domRef.player.volume; + self.fluidStorage.fluidMute = false; + } else { + self.fluidStorage.fluidMute = true; + } + + if (self.domRef.player.volume && !self.domRef.player.muted) { + for (let i = 0; i < muteButtonTag.length; i++) { + muteButtonTag[i].className = muteButtonTag[i].className.replace(/\bfluid_button_mute\b/g, 'fluid_button_volume'); + } + + if (menuOptionMute !== null) { + menuOptionMute.innerHTML = self.displayOptions.captions.mute; + } + + } else { + for (let i = 0; i < muteButtonTag.length; i++) { + muteButtonTag[i].className = muteButtonTag[i].className.replace(/\bfluid_button_volume\b/g, 'fluid_button_mute'); + } + + if (menuOptionMute !== null) { + menuOptionMute.innerHTML = self.displayOptions.captions.unmute; + } + } + currentVolumeTag.style.width = (self.domRef.player.volume * volumebarTotalWidth) + 'px'; + volumeposTag.style.left = (self.domRef.player.volume * volumebarTotalWidth - (volumeposTagWidth / 2)) + 'px'; + }; + + self.muteToggle = () => { + if (0 !== self.domRef.player.volume && !self.domRef.player.muted) { + self.domRef.player.volume = 0; + self.domRef.player.muted = true; + } else { + self.domRef.player.volume = self.latestVolume; + self.domRef.player.muted = false; + } + + // Persistent settings + self.fluidStorage.fluidVolume = self.latestVolume; + self.fluidStorage.fluidMute = self.domRef.player.muted; + }; + + self.checkFullscreenSupport = () => { + const videoPlayerWrapper = self.domRef.wrapper; + + if (videoPlayerWrapper.mozRequestFullScreen) { + return { + goFullscreen: 'mozRequestFullScreen', + exitFullscreen: 'mozCancelFullScreen', + isFullscreen: 'mozFullScreenElement' + }; + + } else if (videoPlayerWrapper.webkitRequestFullscreen) { + return { + goFullscreen: 'webkitRequestFullscreen', + exitFullscreen: 'webkitExitFullscreen', + isFullscreen: 'webkitFullscreenElement' + }; + + } else if (videoPlayerWrapper.msRequestFullscreen) { + return { + goFullscreen: 'msRequestFullscreen', + exitFullscreen: 'msExitFullscreen', + isFullscreen: 'msFullscreenElement' + }; + + } else if (videoPlayerWrapper.requestFullscreen) { + return { + goFullscreen: 'requestFullscreen', + exitFullscreen: 'exitFullscreen', + isFullscreen: 'fullscreenElement' + }; + + } else if (self.domRef.player.webkitSupportsFullscreen) { + return { + goFullscreen: 'webkitEnterFullscreen', + exitFullscreen: 'webkitExitFullscreen', + isFullscreen: 'webkitDisplayingFullscreen' + }; + } + + return false; + }; + + self.fullscreenOff = (fullscreenButton, menuOptionFullscreen) => { + for (let i = 0; i < fullscreenButton.length; i++) { + fullscreenButton[i].className = fullscreenButton[i].className.replace(/\bfluid_button_fullscreen_exit\b/g, 'fluid_button_fullscreen'); + } + if (menuOptionFullscreen !== null) { + menuOptionFullscreen.innerHTML = 'Fullscreen'; + } + self.fullscreenMode = false; + }; + + self.fullscreenOn = (fullscreenButton, menuOptionFullscreen) => { + for (let i = 0; i < fullscreenButton.length; i++) { + fullscreenButton[i].className = fullscreenButton[i].className.replace(/\bfluid_button_fullscreen\b/g, 'fluid_button_fullscreen_exit'); + } + + if (menuOptionFullscreen !== null) { + menuOptionFullscreen.innerHTML = self.displayOptions.captions.exitFullscreen; + } + self.fullscreenMode = true; + }; + + self.fullscreenToggle = () => { + self.debugMessage(`Toggling Full Screen`); + const videoPlayerTag = self.domRef.player; + const fullscreenTag = self.domRef.wrapper; + const requestFullscreenFunctionNames = self.checkFullscreenSupport(); + const fullscreenButton = videoPlayerTag.parentNode.getElementsByClassName('fluid_control_fullscreen'); + const menuOptionFullscreen = fullscreenTag.querySelector('.context_option_fullscreen'); + self.resetDisplayMode('fullScreen'); + + let functionNameToExecute; + + if (requestFullscreenFunctionNames) { + // iOS fullscreen elements are different and so need to be treated separately + if (requestFullscreenFunctionNames.goFullscreen === 'webkitEnterFullscreen') { + if (!videoPlayerTag[requestFullscreenFunctionNames.isFullscreen]) { + functionNameToExecute = 'videoPlayerTag.' + requestFullscreenFunctionNames.goFullscreen + '();'; + self.fullscreenOn(fullscreenButton, menuOptionFullscreen); + new Function('videoPlayerTag', functionNameToExecute)(videoPlayerTag); + } + } else { + if (document[requestFullscreenFunctionNames.isFullscreen] === null) { + //Go fullscreen + functionNameToExecute = 'videoPlayerTag.' + requestFullscreenFunctionNames.goFullscreen + '();'; + self.fullscreenOn(fullscreenButton, menuOptionFullscreen); + } else { + //Exit fullscreen + functionNameToExecute = 'document.' + requestFullscreenFunctionNames.exitFullscreen + '();'; + self.fullscreenOff(fullscreenButton, menuOptionFullscreen); + } + new Function('videoPlayerTag', functionNameToExecute)(fullscreenTag); + } + } else { + //The browser does not support the Fullscreen API, so a pseudo-fullscreen implementation is used + if (fullscreenTag.className.search(/\bpseudo_fullscreen\b/g) !== -1) { + fullscreenTag.className = fullscreenTag.className.replace(/\bpseudo_fullscreen\b/g, ''); + self.fullscreenOff(fullscreenButton, menuOptionFullscreen); + } else { + fullscreenTag.className += ' pseudo_fullscreen'; + self.fullscreenOn(fullscreenButton, menuOptionFullscreen); + } + } + + self.resizeVpaidAuto(); + + // Listen for fullscreen exit event on safari, as the fullscreen mode uses the native UI in iOS + self.domRef.player.addEventListener('webkitendfullscreen', () => { + self.fullscreenOff(fullscreenButton, menuOptionFullscreen); + }); + }; + + self.findClosestParent = (el, selector) => { + let matchesFn = null; + + // find vendor prefix + ['matches', 'webkitMatchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector'].some(function (fn) { + if (typeof document.body[fn] == 'function') { + matchesFn = fn; + return true; + } + return false; + }); + + let parent; + + // Check if the current element matches the selector + if (el[matchesFn](selector)) { + return el; + } + + // traverse parents + while (el) { + parent = el.parentElement; + if (parent && parent[matchesFn](selector)) { + return parent; + } + el = parent; + } + + return null; + }; + + self.getTranslateX = (el) => { + let coordinates = null; + + try { + const results = el.style.transform.match(/translate3d\((-?\d+px,\s?){2}-?\d+px\)/); + + if (results && results.length) { + coordinates = results[0] + .replace('translate3d(', '') + .replace(')', '') + .replace(/\s/g, '') + .replace(/px/g, '') + .split(',') + ; + } + } catch (e) { + coordinates = null; + } + + return (coordinates && (coordinates.length === 3)) ? parseInt(coordinates[0]) : 0; + }; + + self.getEventOffsetX = (evt, el) => { + let x = 0; + let translateX = 0; + + while (el && !isNaN(el.offsetLeft)) { + translateX = self.getTranslateX(el); + + if (el.tagName === 'BODY') { + x += el.offsetLeft + el.clientLeft + translateX - (el.scrollLeft || document.documentElement.scrollLeft); + } else { + x += el.offsetLeft + el.clientLeft + translateX - el.scrollLeft; + } + + el = el.offsetParent; + } + + let eventX; + if (typeof evt.touches !== 'undefined' && typeof evt.touches[0] !== 'undefined') { + eventX = evt.touches[0].clientX; + } else { + eventX = evt.clientX + } + + return eventX - x; + }; + + self.getEventOffsetY = (evt, el) => { + let fullscreenMultiplier = 1; + + const requestFullscreenFunctionNames = self.checkFullscreenSupport(); + if (requestFullscreenFunctionNames && document[requestFullscreenFunctionNames.isFullscreen]) { + fullscreenMultiplier = 0; + } + + let y = 0; + + while (el && !isNaN(el.offsetTop)) { + if (el.tagName === 'BODY') { + y += el.offsetTop - ((el.scrollTop || document.documentElement.scrollTop) * fullscreenMultiplier); + + } else { + y += el.offsetTop - (el.scrollTop * fullscreenMultiplier); + } + + el = el.offsetParent; + } + + return evt.clientY - y; + }; + + self.onProgressbarMouseDown = (event) => { + self.displayOptions.layoutControls.playPauseAnimation = false; + // we need an initial position for touchstart events, as mouse up has no offset x for iOS + let initialPosition; + + if (self.displayOptions.layoutControls.showCardBoardView) { + initialPosition = self.getEventOffsetX(event, event.target.parentNode); + } else { + initialPosition = self.getEventOffsetX(event, self.domRef.wrapper.querySelector('.fluid_controls_progress_container')); + } + + if (self.isCurrentlyPlayingAd) { + return; + } + + self.fluidPseudoPause = true; + + const initiallyPaused = self.domRef.player.paused; + if (!initiallyPaused) { + self.domRef.player.pause(); + } + + const shiftTime = timeBarX => { + const totalWidth = self.domRef.wrapper.querySelector('.fluid_controls_progress_container').clientWidth; + if (totalWidth) { + self.domRef.player.currentTime = self.currentVideoDuration * timeBarX / totalWidth; + } + + self.hideSuggestedVideos(); + }; + + const onProgressbarMouseMove = event => { + const currentX = self.getEventOffsetX(event, event.target.parentNode); + initialPosition = NaN; // mouse up will fire after the move, we don't want to trigger the initial position in the event of iOS + shiftTime(currentX); + self.contolProgressbarUpdate(); + self.controlDurationUpdate(); + }; + + const onProgressbarMouseUp = event => { + document.removeEventListener('mousemove', onProgressbarMouseMove); + document.removeEventListener('touchmove', onProgressbarMouseMove); + document.removeEventListener('mouseup', onProgressbarMouseUp); + document.removeEventListener('touchend', onProgressbarMouseUp); + + let clickedX = self.getEventOffsetX(event, event.target.parentNode); + + if (isNaN(clickedX) && !isNaN(initialPosition)) { + clickedX = initialPosition; + } + + if (!isNaN(clickedX)) { + shiftTime(clickedX); + } + + if (!initiallyPaused) { + self.play(); + } + + // Wait till video played then re-enable the animations + if (self.initialAnimationSet) { + setTimeout(() => { + self.displayOptions.layoutControls.playPauseAnimation = self.initialAnimationSet; + }, 200); + } + self.fluidPseudoPause = false; + }; + + document.addEventListener('mouseup', onProgressbarMouseUp); + document.addEventListener('touchend', onProgressbarMouseUp, { passive: true }); + document.addEventListener('mousemove', onProgressbarMouseMove); + document.addEventListener('touchmove', onProgressbarMouseMove, { passive: true }); + }; + + self.onVolumeBarMouseDown = () => { + const shiftVolume = volumeBarX => { + const totalWidth = self.domRef.controls.volumeContainer.clientWidth; + + if (totalWidth) { + let newVolume = volumeBarX / totalWidth; + + if (newVolume < 0.05) { + newVolume = 0; + self.domRef.player.muted = true; + } else if (newVolume > 0.95) { + newVolume = 1; + } + + if (self.domRef.player.muted && newVolume > 0) { + self.domRef.player.muted = false; + } + + self.setVolume(newVolume); + } + } + + const onVolumeBarMouseMove = event => { + const currentX = self.getEventOffsetX(event, self.domRef.controls.volumeContainer); + shiftVolume(currentX); + } + + const onVolumeBarMouseUp = event => { + document.removeEventListener('mousemove', onVolumeBarMouseMove); + document.removeEventListener('touchmove', onVolumeBarMouseMove); + document.removeEventListener('mouseup', onVolumeBarMouseUp); + document.removeEventListener('touchend', onVolumeBarMouseUp); + + const currentX = self.getEventOffsetX(event, self.domRef.controls.volumeContainer); + + if (!isNaN(currentX)) { + shiftVolume(currentX); + } + } + + document.addEventListener('mouseup', onVolumeBarMouseUp); + document.addEventListener('touchend', onVolumeBarMouseUp, { passive: true }); + document.addEventListener('mousemove', onVolumeBarMouseMove); + document.addEventListener('touchmove', onVolumeBarMouseMove, { passive: true }); + }; + + self.findRoll = (roll) => { + const ids = []; + ids.length = 0; + + if (!roll || !self.hasOwnProperty('rollsById')) { + return; + } + + for (let key in self.rollsById) { + if (!self.rollsById.hasOwnProperty(key)) { + continue; + } + + if (self.rollsById[key].roll === roll) { + ids.push(key); + } + } + + return ids; + }; + + self.onKeyboardVolumeChange = (direction) => { + let volume = self.domRef.player.volume; + + if ('asc' === direction) { + volume += 0.05; + } else if ('desc' === direction) { + volume -= 0.05; + } + + if (volume < 0.05) { + volume = 0; + } else if (volume > 0.95) { + volume = 1; + } + + self.setVolume(volume); + }; + + self.onKeyboardSeekPosition = (keyCode) => { + if (self.isCurrentlyPlayingAd) { + return; + } + + self.domRef.player.currentTime = self.getNewCurrentTimeValueByKeyCode( + keyCode, + self.domRef.player.currentTime, + self.domRef.player.duration + ); + }; + + self.getNewCurrentTimeValueByKeyCode = (keyCode, currentTime, duration) => { + let newCurrentTime = currentTime; + + switch (keyCode) { + case 35://End + newCurrentTime = duration; + break; + case 36://Home + newCurrentTime = 0; + break; + case 48://0 + case 49://1 + case 50://2 + case 51://3 + case 52://4 + case 53://5 + case 54://6 + case 55://7 + case 56://8 + case 57://9 + if (keyCode < 58 && keyCode > 47) { + const percent = (keyCode - 48) * 10; + newCurrentTime = duration * percent / 100; + } + break; + } + + return newCurrentTime; + }; + + self.handleMouseleave = (event) => { + if (typeof event.clientX !== 'undefined' + && self.domRef.wrapper.contains(document.elementFromPoint(event.clientX, event.clientY))) { + //false positive; we didn't actually leave the player + return; + } + + self.hideControlBar(); + self.hideTitle(); + }; + + self.handleMouseenterForKeyboard = () => { + if (self.captureKey) { + return; + } + + self.captureKey = event => { + event.stopPropagation(); + const keyCode = event.keyCode; + + switch (keyCode) { + case 70://f + self.fullscreenToggle(); + event.preventDefault(); + break; + case 13://Enter + case 32://Space + self.playPauseToggle(); + event.preventDefault(); + break; + case 77://m + self.muteToggle(); + event.preventDefault(); + break; + case 38://up arrow + self.onKeyboardVolumeChange('asc'); + event.preventDefault(); + break; + case 40://down arrow + self.onKeyboardVolumeChange('desc'); + event.preventDefault(); + break; + case 37://left arrow + self.skipRelative(-self.timeSkipOffsetAmount); + break; + case 39://right arrow + self.skipRelative(self.timeSkipOffsetAmount); + break; + case 35://End + case 36://Home + case 48://0 + case 49://1 + case 50://2 + case 51://3 + case 52://4 + case 53://5 + case 54://6 + case 55://7 + case 56://8 + case 57://9 + self.onKeyboardSeekPosition(keyCode); + event.preventDefault(); + break; + case 73: // i + self.toggleMiniPlayer(undefined, true); + break; + } + + return false; + + }; + + document.addEventListener('keydown', self.captureKey, true); + }; + + self.keyboardControl = () => { + self.domRef.wrapper.addEventListener('click', self.handleMouseenterForKeyboard, false); + + // When we click outside player, we stop registering keyboard events + const clickHandler = self.handleWindowClick.bind(self); + + self.destructors.push(() => { + window.removeEventListener('click', clickHandler); + }); + + window.addEventListener('click', clickHandler); + }; + + self.handleWindowClick = (e) => { + if (!self.domRef.wrapper) { + console.warn('Dangling click event listener should be collected for unknown wrapper.' + + 'Did you forget to call destroy on player instance?'); + return; + } + + const inScopeClick = self.domRef.wrapper.contains(e.target) || e.target.classList.contains('.js-skipHref'); + + if (inScopeClick) { + return; + } + + document.removeEventListener('keydown', self.captureKey, true); + delete self['captureKey']; + + if (self.theatreMode && !self.theatreModeAdvanced) { + self.theatreToggle(); + } + }; + + self.initialPlay = () => { + self.domRef.player.addEventListener('playing', () => { + self.toggleLoader(false); + }); + + self.domRef.player.addEventListener('timeupdate', () => { + // some places we are manually displaying toggleLoader + // user experience toggleLoader being displayed even when content is playing in background + self.toggleLoader(false); + }); + + self.domRef.player.addEventListener('waiting', () => { + self.toggleLoader(true); + }); + + if (!self.displayOptions.layoutControls.playButtonShowing) { + // Controls always showing until the video is first played + const initialControlsDisplay = self.domRef.wrapper.querySelector('.fluid_controls_container'); + initialControlsDisplay.classList.remove('initial_controls_show'); + // The logo shows before playing but may need to be removed + const fpPlayer = self.domRef.wrapper.querySelector('.logo_holder'); + if (fpPlayer) { + fpPlayer.classList.remove('initial_controls_show'); + } + } + + if (!self.firstPlayLaunched) { + self.playPauseToggle(); + self.domRef.player.removeEventListener('play', self.initialPlay); + } + }; + + self.playPauseToggle = () => { + self.hideSuggestedVideos(); + const isFirstStart = !self.firstPlayLaunched; + const preRolls = self.findRoll('preRoll'); + + if (!isFirstStart || preRolls.length === 0) { + if (isFirstStart && preRolls.length === 0) { + self.firstPlayLaunched = true; + self.displayOptions.vastOptions.vastAdvanced.noVastVideoCallback(); + } + + if (self.domRef.player.paused) { + if (self.isCurrentlyPlayingAd && self.vastOptions !== null && self.vastOptions.vpaid) { + // resume the vpaid linear ad + self.resumeVpaidAd(); + } else { + // Check if video has ended. If so, replay + if (Math.floor(self.currentVideoDuration) === Math.floor(self.domRef.player.currentTime)) { + self.initialiseStreamers(); + self.domRef.player.currentTime = 0; + } + + // resume the regular linear vast or content video player + if (self.dashPlayer) { + self.dashPlayer.play(); + } else { + self.domRef.player.play(); + } + } + + self.playPauseAnimationToggle(true); + + } else if (!isFirstStart) { + if (self.isCurrentlyPlayingAd && self.vastOptions !== null && self.vastOptions.vpaid) { + // pause the vpaid linear ad + self.pauseVpaidAd(); + } else { + // pause the regular linear vast or content video player + self.domRef.player.pause(); + } + + self.playPauseAnimationToggle(false); + } + + self.toggleOnPauseAd(); + } else { + self.isCurrentlyPlayingAd = true; + + // Workaround for Safari or Mobile Chrome - otherwise it blocks the subsequent + // play() command, because it considers it not being triggered by the user. + // The URL is hardcoded here to cover widest possible use cases. + // If you know of an alternative workaround for this issue - let us know! + const browserVersion = self.getBrowserVersion(); + const isChromeAndroid = self.mobileInfo.userOs !== false + && self.mobileInfo.userOs === 'Android' + && browserVersion.browserName === 'Google Chrome'; + + if ('Safari' === browserVersion.browserName || isChromeAndroid) { + self.domRef.player.src = 'https://cdn.fluidplayer.com/static/blank.mp4'; + self.domRef.player.play(); + self.playPauseAnimationToggle(true); + } + + self.firstPlayLaunched = true; + + //trigger the loading of the VAST Tag + self.prepareVast('preRoll'); + self.preRollAdPodsLength = preRolls.length; + } + + const prepareVastAdsThatKnowDuration = () => { + self.prepareVast('onPauseRoll'); + self.scheduleOnDemandRolls(); + }; + + if (isFirstStart) { + // Remove the div that was placed as a fix for poster image and DASH streaming, if it exists + const pseudoPoster = self.domRef.wrapper.querySelector('.fluid_pseudo_poster'); + if (pseudoPoster) { + pseudoPoster.parentNode.removeChild(pseudoPoster); + } + + if (self.mainVideoDuration > 0) { + prepareVastAdsThatKnowDuration(); + } else { + self.domRef.player.addEventListener('mainVideoDurationSet', prepareVastAdsThatKnowDuration); + } + } + + self.adTimer(); + + const blockOnPause = self.domRef.wrapper.querySelector('.fluid_html_on_pause_container'); + + if (blockOnPause && !self.isCurrentlyPlayingAd) { + if (self.domRef.player.paused) { + blockOnPause.style.display = 'flex'; + } else { + blockOnPause.style.display = 'none'; + } + } + }; + + self.setCustomControls = () => { + //Set the Play/Pause behaviour + self.trackEvent(self.domRef.player.parentNode, 'click', '.fluid_control_playpause', () => { + if (!self.firstPlayLaunched) { + self.domRef.player.removeEventListener('play', self.initialPlay); + } + + self.playPauseToggle(); + }, false); + + self.domRef.player.addEventListener('play', () => { + self.controlPlayPauseToggle(); + self.contolVolumebarUpdate(); + }, false); + + self.domRef.player.addEventListener('fluidplayerpause', () => { + self.controlPlayPauseToggle(); + }, false); + + //Set the progressbar + self.domRef.player.addEventListener('timeupdate', () => { + self.contolProgressbarUpdate(); + self.controlDurationUpdate(); + }); + + const isMobileChecks = self.getMobileOs(); + const eventOn = (isMobileChecks.userOs) ? 'touchstart' : 'mousedown'; + + if (self.displayOptions.layoutControls.showCardBoardView) { + self.trackEvent( + self.domRef.player.parentNode, + eventOn, + '.fluid_controls_progress_container', + event => self.onProgressbarMouseDown(event), + false + ); + } else { + self.domRef.wrapper.querySelector('.fluid_controls_progress_container') + .addEventListener(eventOn, event => self.onProgressbarMouseDown(event), { passive: true }); + } + + //Set the volume controls + self.domRef.wrapper.querySelector('.fluid_control_volume_container') + .addEventListener(eventOn, event => self.onVolumeBarMouseDown(), { passive: true }); + + self.domRef.player.addEventListener('volumechange', () => self.contolVolumebarUpdate()); + + self.trackEvent(self.domRef.player.parentNode, 'click', '.fluid_control_mute', () => self.muteToggle()); + + self.setBuffering(); + + //Set the fullscreen control + self.trackEvent(self.domRef.player.parentNode, 'click', '.fluid_control_fullscreen', () => self.fullscreenToggle()); + + // Theatre mode + if (self.displayOptions.layoutControls.allowTheatre && !self.isInIframe) { + self.domRef.wrapper.querySelector('.fluid_control_theatre').style.display = 'inline-block'; + self.trackEvent(self.domRef.player.parentNode, 'click', '.fluid_control_theatre', () => self.theatreToggle()); + } else { + self.domRef.wrapper.querySelector('.fluid_control_theatre').style.display = 'none'; + } + + // Mini Player + if (self.displayOptions.layoutControls.miniPlayer.enabled && !self.isInIframe) { + self.trackEvent(self.domRef.player.parentNode, 'click', '.fluid_control_mini_player', () => self.toggleMiniPlayer(undefined, true)); + } + + self.domRef.player.addEventListener('ratechange', () => { + if (self.isCurrentlyPlayingAd) { + self.playbackRate = 1; + } + }); + }; + + // Create the time position preview only if the vtt previews aren't enabled + self.createTimePositionPreview = () => { + if (!self.showTimeOnHover) { + return; + } + + const progressContainer = self.domRef.wrapper.querySelector('.fluid_controls_progress_container'); + const previewContainer = document.createElement('div'); + + previewContainer.className = 'fluid_timeline_preview'; + previewContainer.style.display = 'none'; + previewContainer.style.position = 'absolute'; + + progressContainer.appendChild(previewContainer); + + // Set up hover for time position preview display + self.domRef.wrapper.querySelector('.fluid_controls_progress_container') + .addEventListener('mousemove', event => { + const progressContainer = self.domRef.wrapper.querySelector('.fluid_controls_progress_container'); + const totalWidth = progressContainer.clientWidth; + const hoverTimeItem = self.domRef.wrapper.querySelector('.fluid_timeline_preview'); + const hoverQ = self.getEventOffsetX(event, progressContainer); + + const hoverSecondQ = self.currentVideoDuration * hoverQ / totalWidth; + hoverTimeItem.innerText = self.formatTime(hoverSecondQ); + + hoverTimeItem.style.display = 'block'; + hoverTimeItem.style.left = (hoverSecondQ / self.domRef.player.duration * 100) + "%"; + }, false); + + // Hide timeline preview on mouseout + self.domRef.wrapper.querySelector('.fluid_controls_progress_container') + .addEventListener('mouseout', () => { + const hoverTimeItem = self.domRef.wrapper.querySelector('.fluid_timeline_preview'); + hoverTimeItem.style.display = 'none'; + }, false); + }; + + self.setCustomContextMenu = () => { + const playerWrapper = self.domRef.wrapper; + + const showDefaultControls = self.displayOptions.layoutControls.contextMenu.controls; + const extraLinks = self.displayOptions.layoutControls.contextMenu.links; + + //Create own context menu + const divContextMenu = document.createElement('div'); + divContextMenu.className = 'fluid_context_menu'; + divContextMenu.style.display = 'none'; + divContextMenu.style.position = 'absolute'; + + const contextMenuList = document.createElement('ul'); + divContextMenu.appendChild(contextMenuList); + + if (Array.isArray(extraLinks)) { + extraLinks.forEach(function appendExtraLinks(link, index) { + const linkItem = document.createElement('li'); + linkItem.innerHTML = link.label; + linkItem.addEventListener('click', () => window.open(link.href, '_blank'), false); + contextMenuList.appendChild(linkItem); + }); + } + + if (showDefaultControls) { + const menuItemPlay = document.createElement('li'); + menuItemPlay.className = 'context_option_play'; + menuItemPlay.innerHTML = self.displayOptions.captions.play; + menuItemPlay.addEventListener('click', () => self.playPauseToggle(), false); + contextMenuList.appendChild(menuItemPlay); + + const menuItemMute = document.createElement('li'); + menuItemMute.className = 'context_option_mute'; + menuItemMute.innerHTML = self.displayOptions.captions.mute; + menuItemMute.addEventListener('click', () => self.muteToggle(), false); + contextMenuList.appendChild(menuItemMute); + + const menuItemFullscreen = document.createElement('li'); + menuItemFullscreen.className = 'context_option_fullscreen'; + menuItemFullscreen.innerHTML = self.displayOptions.captions.fullscreen; + menuItemFullscreen.addEventListener('click', () => self.fullscreenToggle(), false); + contextMenuList.appendChild(menuItemFullscreen); + } + + const menuItemVersion = document.createElement('li'); + menuItemVersion.innerHTML = 'Fluid Player ' + self.version; + menuItemVersion.addEventListener('click', () => window.open(self.homepage, '_blank'), false) + contextMenuList.appendChild(menuItemVersion); + + self.domRef.player.parentNode.insertBefore(divContextMenu, self.domRef.player.nextSibling); + + //Disable the default context menu + playerWrapper.addEventListener('contextmenu', e => { + e.preventDefault(); + + divContextMenu.style.left = self.getEventOffsetX(e, self.domRef.player) + 'px'; + divContextMenu.style.top = self.getEventOffsetY(e, self.domRef.player) + 'px'; + divContextMenu.style.display = 'block'; + }, false); + + //Hide the context menu on clicking elsewhere + document.addEventListener('click', e => { + if ((e.target !== self.domRef.player) || e.button !== 2) { + divContextMenu.style.display = 'none'; + } + }, false); + }; + + self.setDefaultLayout = () => { + self.domRef.wrapper.className += ' fluid_player_layout_' + self.displayOptions.layoutControls.layout; + + self.setCustomContextMenu(); + + const controls = self.generateCustomControlTags({ + displayVolumeBar: self.checkShouldDisplayVolumeBar(), + primaryColor: self.displayOptions.layoutControls.primaryColor + ? self.displayOptions.layoutControls.primaryColor + : 'red', + controlForwardBackward: !!self.displayOptions.layoutControls.controlForwardBackward.show, + miniPlayer: self.displayOptions.layoutControls.miniPlayer, + }); + + // Remove the default controls + self.domRef.player.removeAttribute('controls'); + + // Insert custom controls and append loader + self.domRef.player.parentNode.insertBefore(controls.root, self.domRef.player.nextSibling); + self.domRef.player.parentNode.insertBefore(controls.loader, self.domRef.player.nextSibling); + + // Register controls locally + self.domRef.controls = controls; + + /** + * Set the volumebar after its elements are properly rendered. + */ + let remainingAttemptsToInitiateVolumeBar = 100; + + const initiateVolumebar = function () { + if (!remainingAttemptsToInitiateVolumeBar) { + clearInterval(initiateVolumebarTimerId); + } else if (self.checkIfVolumebarIsRendered()) { + clearInterval(initiateVolumebarTimerId); + self.contolVolumebarUpdate(); + } else { + remainingAttemptsToInitiateVolumeBar--; + } + }; + let initiateVolumebarTimerId = setInterval(initiateVolumebar, 100); + self.destructors.push(() => clearInterval(initiateVolumebarTimerId)); + + if (self.displayOptions.layoutControls.doubleclickFullscreen && !(self.isTouchDevice() || !self.displayOptions.layoutControls.controlForwardBackward.doubleTapMobile)) { + self.domRef.player.addEventListener('dblclick', self.fullscreenToggle); + } + + if (self.getMobileOs().userOs === 'iOS') { + let orientationListenerAdded = false; + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + self.domRef.player.inView = true; + if (self.displayOptions.layoutControls.autoRotateFullScreen && self.isTouchDevice() && !orientationListenerAdded) { + window.matchMedia("(orientation: landscape)").addEventListener('change', self.handleOrientationChange); + orientationListenerAdded = true; + } + } else { + self.domRef.player.inView = false; + } + }); + }); + + observer.observe(self.domRef.player); + } + + self.initHtmlOnPauseBlock(); + + self.setCustomControls(); + + self.setupThumbnailPreview(); + + self.createTimePositionPreview(); + + self.posterImage(); + + self.initPlayButton(); + + self.setVideoPreload(); + + self.createPlaybackList(); + + self.createDownload(); + + self.toggleMiniPlayerScreenDetection(); + + if (!!self.displayOptions.layoutControls.controlForwardBackward.show) { + self.initSkipControls(); + } + + if (!!self.displayOptions.layoutControls.controlForwardBackward.doubleTapMobile) { + self.initDoubleTapSkip(); + } + + self.initSkipAnimationElements(); + }; + + self.initSkipControls = () => { + self.domRef.controls.skipBack.addEventListener('click', self.skipRelative.bind(this, -self.timeSkipOffsetAmount)); + self.domRef.controls.skipForward.addEventListener('click', self.skipRelative.bind(this, self.timeSkipOffsetAmount)); + }; + + // Function to handle fullscreen toggle based on orientation + self.handleOrientationChange = () => { + const isLandscape = window.matchMedia("(orientation: landscape)").matches; + const videoPlayerTag = self.domRef.player; + const fullscreenTag = self.domRef.wrapper; + const requestFullscreenFunctionNames = self.checkFullscreenSupport(); + const fullscreenButton = videoPlayerTag.parentNode.getElementsByClassName('fluid_control_fullscreen'); + const menuOptionFullscreen = fullscreenTag.querySelector('.context_option_fullscreen'); + let functionNameToExecute; + if (isLandscape && self.domRef.player.inView) { + if (requestFullscreenFunctionNames) { + if (requestFullscreenFunctionNames.goFullscreen === 'webkitEnterFullscreen') { + functionNameToExecute = 'videoPlayerTag.' + requestFullscreenFunctionNames.goFullscreen + '();'; + self.fullscreenOn(fullscreenButton, menuOptionFullscreen); + new Function('videoPlayerTag', functionNameToExecute)(videoPlayerTag); + } else { + if (document[requestFullscreenFunctionNames.isFullscreen] === null) { + functionNameToExecute = 'videoPlayerTag.' + requestFullscreenFunctionNames.goFullscreen + '();'; + self.fullscreenOn(fullscreenButton, menuOptionFullscreen); + } + new Function('videoPlayerTag', functionNameToExecute)(fullscreenTag); + } + } else { + fullscreenTag.className += ' pseudo_fullscreen'; + self.fullscreenOn(fullscreenButton, menuOptionFullscreen); + } + } else { + fullscreenTag.className = fullscreenTag.className.replace(/\bpseudo_fullscreen\b/g, ''); + if (requestFullscreenFunctionNames) { + functionNameToExecute = 'document.' + requestFullscreenFunctionNames.exitFullscreen + '();'; + self.fullscreenOff(fullscreenButton, menuOptionFullscreen); + new Function('videoPlayerTag', functionNameToExecute)(fullscreenTag); + } else { + if (fullscreenTag.className.search(/\bpseudo_fullscreen\b/g) !== -1) { + fullscreenTag.className = fullscreenTag.className.replace(/\bpseudo_fullscreen\b/g, ''); + self.fullscreenOff(fullscreenButton, menuOptionFullscreen); + } + } + } + self.resizeVpaidAuto(); + } + + /** + * Creates the skip animation elements and appends them to the player + * + * @returns {void} + */ + self.initSkipAnimationElements = function initSkipAnimationElements() { + const skipAnimationWrapper = document.createElement('div'); + skipAnimationWrapper.classList.add('fluid_player_skip_offset'); + + const skipAnimationBackward = document.createElement('div'); + skipAnimationBackward.classList.add('fluid_player_skip_offset__backward'); + skipAnimationWrapper.appendChild(skipAnimationBackward); + + const skipAnimationBackwardIcon = document.createElement('div'); + skipAnimationBackwardIcon.classList.add('fluid_player_skip_offset__backward-icon'); + skipAnimationBackwardIcon.ontransitionend = () => skipAnimationBackwardIcon.classList.remove('animate'); + skipAnimationBackward.appendChild(skipAnimationBackwardIcon); + + const skipAnimationForward = document.createElement('div'); + skipAnimationForward.classList.add('fluid_player_skip_offset__forward'); + skipAnimationWrapper.appendChild(skipAnimationForward); + + const skipAnimationForwardIcon = document.createElement('div'); + skipAnimationForwardIcon.classList.add('fluid_player_skip_offset__forward-icon'); + skipAnimationForwardIcon.ontransitionend = () => skipAnimationForwardIcon.classList.remove('animate'); + skipAnimationForward.appendChild(skipAnimationForwardIcon); + + self.domRef.player.parentNode.insertBefore(skipAnimationWrapper, self.domRef.player.nextSibling); + } + + /** + * Initialises the double tap skip functionality + */ + self.initDoubleTapSkip = () => { + let hasDoubleClicked = false; + let timeouts = []; + + function clearTimeouts() { + timeouts.forEach(timeout => clearTimeout(timeout)); + timeouts = []; + } + + self.domRef.player.addEventListener('pointerdown', (event) => { + // Check if it's mobile on the fly and prevent double click skip if it is + if (!self.isTouchDevice()) { + return; + } + + if (!self.isControlBarVisible()) { + return; + } + + const { offsetX } = event + const { clientWidth } = self.domRef.player; + + // Simulates default behaviour if it's a single click + timeouts.push(setTimeout(() => { + hasDoubleClicked = false; + self.playPauseToggle(); + }, 300)); + + // Skips video time if it's a double click + if (hasDoubleClicked) { + clearTimeouts(); + hasDoubleClicked = false; + return self.skipRelative(offsetX < clientWidth / 2 ? -self.timeSkipOffsetAmount : self.timeSkipOffsetAmount); + } + + hasDoubleClicked = true; + }); + } + + /** + * Skips the video time by timeOffset relative to the current video time + * + * @param {number} timeOffset + */ + self.skipRelative = function skipRelative(timeOffset) { + self.debugMessage('skipping video time by ', timeOffset); + if (self.isCurrentlyPlayingAd) { + return; + } + + let skipTo = self.domRef.player.currentTime + timeOffset; + if (skipTo < 0) { + skipTo = 0; + } + self.domRef.player.currentTime = skipTo; + + // Trigger animation + if (timeOffset >= 0) { + const forwardElement = self.domRef.wrapper.querySelector(`.fluid_player_skip_offset__forward-icon`); + forwardElement.classList.add('animate'); + } else { + const backwardElement = self.domRef.wrapper.querySelector(`.fluid_player_skip_offset__backward-icon`); + backwardElement.classList.add('animate'); + } + } + + /** + * Checks if the volumebar is rendered and the styling applied by comparing + * the width of 2 elements that should look different. + * + * @returns Boolean + */ + self.checkIfVolumebarIsRendered = () => { + const volumeposTag = self.domRef.wrapper.querySelector('.fluid_control_volume_currentpos'); + const volumebarTotalWidth = self.domRef.wrapper.querySelector('.fluid_control_volume').clientWidth; + const volumeposTagWidth = volumeposTag.clientWidth; + + return volumeposTagWidth !== volumebarTotalWidth; + }; + + self.setLayout = () => { + //All other browsers + if (!self.isTouchDevice()) { + self.domRef.player.addEventListener('click', () => self.playPauseToggle(), false); + } + //Mobile Safari - because it does not emit a click event on initial click of the video + self.domRef.player.addEventListener('play', self.initialPlay, false); + self.setDefaultLayout(); + }; + + self.handleFullscreen = () => { + if (typeof document.vastFullsreenChangeEventListenersAdded !== 'undefined') { + return; + } + + ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'msfullscreenchange'].forEach(eventType => { + if (typeof (document['on' + eventType]) === 'object') { + document.addEventListener(eventType, function (ev) { + self.recalculateAdDimensions(); + }, false); + } + }); + + document.vastFullsreenChangeEventListenersAdded = true; + }; + + self.setupPlayerWrapper = () => { + const wrapper = document.createElement('div'); + + wrapper.id = 'fluid_video_wrapper_' + self.videoPlayerId; + wrapper.className = self.isTouchDevice() + ? 'fluid_video_wrapper mobile' + : 'fluid_video_wrapper'; + + //Assign the height/width dimensions to the wrapper + if (self.displayOptions.layoutControls.fillToContainer) { + wrapper.style.width = '100%'; + wrapper.style.height = '100%'; + } else { + wrapper.style.height = self.domRef.player.clientHeight + 'px'; + wrapper.style.width = self.domRef.player.clientWidth + 'px'; + } + + const parseBorderRadius = () => { + const roundedCorners = self.displayOptions.layoutControls.roundedCorners; + const parsedValue = Number(roundedCorners); + + return !isNaN(parsedValue) && parsedValue !== 0 + ? `${parsedValue}px` + : roundedCorners; + } + + wrapper.style.borderRadius = self.domRef.player.style.borderRadius = parseBorderRadius(); + wrapper.style.overflow = 'hidden'; + + self.domRef.player.style.height = '100%'; + self.domRef.player.style.width = '100%'; + + self.domRef.player.parentNode.insertBefore(wrapper, self.domRef.player); + wrapper.appendChild(self.domRef.player); + + return wrapper; + }; + + self.onErrorDetection = () => { + if (self.domRef.player.networkState === self.domRef.player.NETWORK_NO_SOURCE && self.isCurrentlyPlayingAd) { + //Probably the video ad file was not loaded successfully + self.playMainVideoWhenVastFails(401); + } + }; + + self.createVideoSourceSwitch = (initialLoad = true) => { + const sources = []; + const sourcesList = self.domRef.player.querySelectorAll('source'); + [].forEach.call(sourcesList, source => { + if (source.title && source.src) { + sources.push({ + 'title': source.title, + 'url': source.src, + 'isHD': (source.getAttribute('data-fluid-hd') != null) + }); + } + }); + + const sourceChangeButton = self.domRef.wrapper.querySelector('.fluid_control_video_source'); + self.videoSources = sources; + + if (self.videoSources.length > 1) { + sourceChangeButton.style.display = 'inline-block'; + } else { + sourceChangeButton.style.display = 'none'; + } + + if (self.videoSources.length <= 1) { + return; + } + + let appendSourceChange = false; + + const sourceChangeList = document.createElement('div'); + sourceChangeList.className = 'fluid_video_sources_list'; + sourceChangeList.style.display = 'none'; + + let firstSource = true; + for (const source of self.videoSources) { + // Fix for issues occurring on iOS with mkv files + const getTheType = source.url.split(".").pop(); + if (self.mobileInfo.userOs === 'iOS' && getTheType === 'mkv') { + continue; + } + + // On suggested videos, if the resolution doesn't exist in the new source list, use the first one in the list + // This gets overwritten if it's needed by setPersistentSettings() + if(firstSource && !initialLoad) { + self.domRef.player.src = source.url; + } + + const sourceSelected = (firstSource) ? "source_selected" : ""; + const hdElement = (source.isHD) ? '' : ''; + firstSource = false; + const sourceChangeDiv = document.createElement('div'); + sourceChangeDiv.className = 'fluid_video_source_list_item js-source_' + source.title; + sourceChangeDiv.innerHTML = '' + source.title + hdElement; + + sourceChangeDiv.addEventListener('click', function (event) { + event.stopPropagation(); + // While changing source the player size can flash, we want to set the pixel dimensions then back to 100% afterwards + self.domRef.player.style.width = self.domRef.player.clientWidth + 'px'; + self.domRef.player.style.height = self.domRef.player.clientHeight + 'px'; + + const videoChangedTo = this; + const sourceIcons = self.domRef.wrapper.getElementsByClassName('source_button_icon'); + for (let i = 0; i < sourceIcons.length; i++) { + sourceIcons[i].className = sourceIcons[i].className.replace('source_selected', ''); + } + videoChangedTo.firstChild.className += ' source_selected'; + + self.videoSources.forEach(source => { + if (source.title === videoChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, '')) { + self.setBuffering(); + self.setVideoSource(source.url); + self.fluidStorage.fluidQuality = source.title; + } + }); + + self.openCloseVideoSourceSwitch(); + }); + + sourceChangeList.appendChild(sourceChangeDiv); + appendSourceChange = true; + } + + if (appendSourceChange) { + sourceChangeButton.appendChild(sourceChangeList); + // To reset player for suggested videos, in case the event listener already exists + sourceChangeButton.removeEventListener('click', self.openCloseVideoSourceSwitch); + sourceChangeButton.addEventListener('click', self.openCloseVideoSourceSwitch); + } else { + // Didn't give any source options + self.domRef.wrapper.querySelector('.fluid_control_video_source').style.display = 'none'; + } + }; + + self.openCloseVideoSourceSwitch = () => { + const sourceChangeList = self.domRef.wrapper.querySelector('.fluid_video_sources_list'); + + if (self.isCurrentlyPlayingAd || self.isShowingSuggestedVideos()) { + sourceChangeList.style.display = 'none'; + return; + } + + if (sourceChangeList.style.display !== 'none') { + sourceChangeList.style.display = 'none'; + return; + } + + sourceChangeList.style.display = 'block'; + const mouseOut = () => { + sourceChangeList.removeEventListener('mouseleave', mouseOut); + sourceChangeList.style.display = 'none'; + }; + sourceChangeList.addEventListener('mouseleave', mouseOut); + }; + + self.setVideoSource = (url) => { + if (self.mobileInfo.userOs === 'iOS' && url.indexOf('.mkv') > 0) { + console.log('[FP_ERROR] .mkv files not supported by iOS devices.'); + return false; + } + + if (self.isCurrentlyPlayingAd) { + self.originalSrc = url; + return; + } + + self.isSwitchingSource = true; + let play = false; + if (!self.domRef.player.paused) { + self.domRef.player.pause(); + play = true; + } + + const currentTime = self.domRef.player.currentTime; + self.setCurrentTimeAndPlay(currentTime, play); + + self.domRef.player.src = url; + self.originalSrc = url; + self.displayOptions.layoutControls.mediaType = self.getCurrentSrcType(); + self.initialiseStreamers(); + }; + + self.setCurrentTimeAndPlay = (newCurrentTime, shouldPlay) => { + const loadedMetadata = () => { + self.domRef.player.currentTime = newCurrentTime; + self.domRef.player.removeEventListener('loadedmetadata', loadedMetadata); + // Safari ios and mac fix to set currentTime + if (self.mobileInfo.userOs === 'iOS' || self.getBrowserVersion().browserName.toLowerCase() === 'safari') { + self.domRef.player.addEventListener('playing', videoPlayStart); + } + + if (shouldPlay) { + self.domRef.player.play(); + } else { + self.domRef.player.pause(); + self.controlPlayPauseToggle(); + } + + self.isSwitchingSource = false; + self.domRef.player.style.width = '100%'; + self.domRef.player.style.height = '100%'; + }; + + let videoPlayStart = () => { + self.currentTime = newCurrentTime; + self.domRef.player.removeEventListener('playing', videoPlayStart); + }; + + self.domRef.player.addEventListener('loadedmetadata', loadedMetadata, false); + self.domRef.player.load(); + }; + + self.initTitle = () => { + if (!self.displayOptions.layoutControls.title) { + return; + } + + const titleHolder = document.createElement('div'); + self.domRef.player.parentNode.insertBefore(titleHolder, null); + titleHolder.innerHTML += self.displayOptions.layoutControls.title; + titleHolder.classList.add('fp_title'); + }; + + self.hasTitle = () => { + const title = self.domRef.wrapper.querySelector('.fp_title'); + const titleOption = self.displayOptions.layoutControls.title; + return title && titleOption != null; + }; + + self.hideTitle = () => { + const titleHolder = self.domRef.wrapper.querySelector('.fp_title'); + + if (!self.hasTitle()) { + return; + } + + titleHolder.classList.add('fade_out'); + }; + + self.showTitle = () => { + const titleHolder = self.domRef.wrapper.querySelector('.fp_title'); + + if (!self.hasTitle()) { + return; + } + + titleHolder.classList.remove('fade_out'); + }; + + self.initLogo = () => { + if (!self.displayOptions.layoutControls.logo.imageUrl) { + return; + } + + // Container div for the logo + // This is to allow for fade in and out logo_maintain_display + const logoHolder = document.createElement('div'); + logoHolder.className = 'logo_holder'; + if (self.displayOptions.layoutControls.logo.hideWithControls) { + logoHolder.classList.add('initial_controls_show', 'fp_logo'); + } else { + logoHolder.classList.add('logo_maintain_display'); + } + // The logo itself + const logoImage = document.createElement('img'); + if (self.displayOptions.layoutControls.logo.imageUrl) { + logoImage.src = self.displayOptions.layoutControls.logo.imageUrl; + } + + logoImage.style.position = 'absolute'; + logoImage.style.margin = self.displayOptions.layoutControls.logo.imageMargin; + const logoPosition = self.displayOptions.layoutControls.logo.position.toLowerCase(); + + if (logoPosition.indexOf('bottom') !== -1) { + logoImage.style.bottom = 0; + } else { + logoImage.style.top = 0; + } + if (logoPosition.indexOf('right') !== -1) { + logoImage.style.right = 0; + } else { + logoImage.style.left = 0; + } + if (self.displayOptions.layoutControls.logo.opacity) { + logoImage.style.opacity = self.displayOptions.layoutControls.logo.opacity; + } + + if (self.displayOptions.layoutControls.logo.clickUrl !== null) { + logoImage.style.cursor = 'pointer'; + logoImage.addEventListener('click', function () { + const win = window.open(self.displayOptions.layoutControls.logo.clickUrl, '_blank'); + win.focus(); + }); + } + + // If a mouseOverImage is provided then we must set up the listeners for it + if (self.displayOptions.layoutControls.logo.mouseOverImageUrl) { + logoImage.addEventListener('mouseover', function () { + logoImage.src = self.displayOptions.layoutControls.logo.mouseOverImageUrl; + }, false); + logoImage.addEventListener('mouseout', function () { + logoImage.src = self.displayOptions.layoutControls.logo.imageUrl; + }, false); + } + + self.domRef.player.parentNode.insertBefore(logoHolder, null); + logoHolder.appendChild(logoImage, null); + }; + + self.initHtmlOnPauseBlock = () => { + //If onPauseRoll is defined than HtmlOnPauseBlock won't be shown + if (self.hasValidOnPauseAd()) { + return; + } + + if (!self.displayOptions.layoutControls.htmlOnPauseBlock.html) { + return; + } + + const containerDiv = document.createElement('div'); + containerDiv.className = 'fluid_html_on_pause_container'; + containerDiv.style.display = 'none'; + containerDiv.innerHTML = self.displayOptions.layoutControls.htmlOnPauseBlock.html; + containerDiv.onclick = function (event) { + self.playPauseToggle(); + }; + + if (self.displayOptions.layoutControls.htmlOnPauseBlock.width) { + containerDiv.style.width = self.displayOptions.layoutControls.htmlOnPauseBlock.width + 'px'; + } + + if (self.displayOptions.layoutControls.htmlOnPauseBlock.height) { + containerDiv.style.height = self.displayOptions.layoutControls.htmlOnPauseBlock.height + 'px'; + } + + self.domRef.player.parentNode.insertBefore(containerDiv, null); + }; + + /** + * Play button in the middle when the video loads + */ + self.initPlayButton = () => { + // Create the html for the play button + const containerDiv = document.createElement('div'); + containerDiv.className = 'fluid_html_on_pause fluid_initial_play_button_container'; + const backgroundColor = (self.displayOptions.layoutControls.primaryColor) ? self.displayOptions.layoutControls.primaryColor : "#333333"; + containerDiv.innerHTML = '
'; + const initPlayEventTypes = ['click', 'touchend']; + const initPlayFunction = function () { + self.playPauseToggle(); + initPlayEventTypes.forEach(eventType => containerDiv.removeEventListener(eventType, initPlayFunction)) + }; + initPlayEventTypes.forEach(eventType => containerDiv.addEventListener(eventType, initPlayFunction)) + + // If the user has chosen to not show the play button we'll make it invisible + // We don't hide altogether because animations might still be used + if (!self.displayOptions.layoutControls.playButtonShowing) { + const initialControlsDisplay = self.domRef.wrapper.querySelector('.fluid_controls_container'); + initialControlsDisplay.classList.add('initial_controls_show'); + containerDiv.style.opacity = '0'; + } + + self.domRef.player.parentNode.insertBefore(containerDiv, null); + }; + + /** + * Set the mainVideoDuration property one the video is loaded + */ + self.mainVideoReady = () => { + if (!(self.mainVideoDuration === 0 && !self.isCurrentlyPlayingAd && self.mainVideoReadyState === false)) { + return; + } + const event = new CustomEvent('mainVideoDurationSet'); + + self.mainVideoDuration = self.domRef.player.duration; + self.mainVideoReadyState = true; + self.domRef.player.dispatchEvent(event); + self.domRef.player.removeEventListener('loadedmetadata', self.mainVideoReady); + }; + + self.userActivityChecker = () => { + const videoPlayer = self.domRef.wrapper; + self.newActivity = null; + + let isMouseStillDown = false; + + const activity = event => { + if (event.type === 'touchstart' || event.type === 'mousedown') { + isMouseStillDown = true; + } + if (event.type === 'touchend' || event.type === 'mouseup') { + isMouseStillDown = false; + } + self.newActivity = true; + }; + + const intervalId = setInterval(() => { + if (self.newActivity !== true) { + return; + } + + if (!isMouseStillDown && !self.isLoading) { + self.newActivity = false; + } + + if (self.isUserActive === false || !self.isControlBarVisible()) { + let event = new CustomEvent('userActive'); + self.domRef.player.dispatchEvent(event); + self.isUserActive = true; + } + + clearTimeout(self.inactivityTimeout); + + self.inactivityTimeout = setTimeout(() => { + if (self.newActivity === true) { + clearTimeout(self.inactivityTimeout); + return; + } + + self.isUserActive = false; + + let event = new CustomEvent('userInactive'); + self.domRef.player.dispatchEvent(event); + }, self.displayOptions.layoutControls.controlBar.autoHideTimeout * 1000); + }, 300); + + self.destructors.push(() => clearInterval(intervalId)); + + const listenTo = (self.isTouchDevice()) + ? ['touchstart', 'touchmove', 'touchend'] + : ['mousemove', 'mousedown', 'mouseup']; + + for (let i = 0; i < listenTo.length; i++) { + videoPlayer.addEventListener(listenTo[i], activity, { passive: true }); + } + }; + + self.hasControlBar = () => { + return !!self.domRef.wrapper.querySelector('.fluid_controls_container'); + }; + + self.isControlBarVisible = () => { + if (self.hasControlBar() === false) { + return false; + } + + const controlBar = self.domRef.wrapper.querySelector('.fluid_controls_container'); + const style = window.getComputedStyle(controlBar, null); + return !(style.opacity === 0 || style.visibility === 'hidden'); + }; + + self.setVideoPreload = () => { + self.domRef.player.setAttribute('preload', self.displayOptions.layoutControls.preload); + }; + + self.hideControlBar = () => { + if (self.isCurrentlyPlayingAd && !self.domRef.player.paused) { + self.toggleAdCountdown(true); + } + + self.domRef.player.style.cursor = 'none'; + + // handles both VR and Normal condition + if (!self.hasControlBar()) { + return; + } + + const divVastControls = self.domRef.player.parentNode.getElementsByClassName('fluid_controls_container'); + const fpLogo = self.domRef.player.parentNode.getElementsByClassName('fp_logo'); + + for (let i = 0; i < divVastControls.length; i++) { + if (self.displayOptions.layoutControls.controlBar.animated) { + divVastControls[i].classList.remove('fade_in'); + divVastControls[i].classList.add('fade_out'); + } else { + divVastControls[i].style.display = 'none'; + } + } + + if (self.displayOptions.layoutControls.logo.hideWithControls) { + for (let i = 0; i < fpLogo.length; i++) { + if (self.displayOptions.layoutControls.controlBar.animated) { + if (fpLogo[i]) { + fpLogo[i].classList.remove('fade_in'); + fpLogo[i].classList.add('fade_out'); + } + } else { + if (fpLogo[i]) { + fpLogo[i].style.display = 'none'; + } + } + } + } + }; + + self.showControlBar = (event) => { + if (self.isCurrentlyPlayingAd && !self.domRef.player.paused) { + self.toggleAdCountdown(false); + } + + if (event.type === 'mouseenter' || event.type === 'userActive') { + self.domRef.player.style.cursor = 'default'; + } + + if (!self.hasControlBar()) { + return; + } + + const divVastControls = self.domRef.player.parentNode.getElementsByClassName('fluid_controls_container'); + const fpLogo = self.domRef.player.parentNode.getElementsByClassName('fp_logo'); + for (let i = 0; i < divVastControls.length; i++) { + if (self.displayOptions.layoutControls.controlBar.animated) { + divVastControls[i].classList.remove('fade_out'); + divVastControls[i].classList.add('fade_in'); + } else { + divVastControls[i].style.display = 'block'; + } + } + if (self.displayOptions.layoutControls.logo.hideWithControls) { + for (let i = 0; i < fpLogo.length; i++) { + if (self.displayOptions.layoutControls.controlBar.animated) { + if (fpLogo[i]) { + fpLogo[i].classList.remove('fade_out'); + fpLogo[i].classList.add('fade_in'); + } + } else { + if (fpLogo[i]) { + fpLogo[i].style.display = 'block'; + } + } + } + } + }; + + self.linkControlBarUserActivity = () => { + self.domRef.player.addEventListener('userInactive', self.hideControlBar); + self.domRef.player.addEventListener('userInactive', self.hideTitle); + + self.domRef.player.addEventListener('userActive', self.showControlBar); + self.domRef.player.addEventListener('userActive', self.showTitle); + }; + + self.initMute = () => { + if (self.displayOptions.layoutControls.mute !== true) { + return; + } + + self.domRef.player.volume = 0; + }; + + self.initLoop = () => { + self.domRef.player.loop = !!self.displayOptions.layoutControls.loop; + }; + + self.setBuffering = () => { + let progressInterval; + const bufferBar = self.domRef.player.parentNode.getElementsByClassName('fluid_controls_buffered'); + + for (let j = 0; j < bufferBar.length; j++) { + bufferBar[j].style.width = 0; + } + + // Buffering + const logProgress = () => { + const duration = self.domRef.player.duration; + if (duration <= 0) { + return; + } + + for (let i = 0; i < self.domRef.player.buffered.length; i++) { + if (self.domRef.player.buffered.start(self.domRef.player.buffered.length - 1 - i) >= self.domRef.player.currentTime) { + continue; + } + + const newBufferLength = (self.domRef.player.buffered.end(self.domRef.player.buffered.length - 1 - i) / duration) * 100 + "%"; + + for (let j = 0; j < bufferBar.length; j++) { + bufferBar[j].style.width = newBufferLength; + } + + // Stop checking for buffering if the video is fully buffered + if (!!progressInterval && 1 === (self.domRef.player.buffered.end(self.domRef.player.buffered.length - 1 - i) / duration)) { + clearInterval(progressInterval); + } + + break; + } + }; + progressInterval = setInterval(logProgress, 500); + self.destructors.push(() => clearInterval(progressInterval)); + }; + + self.createPlaybackList = () => { + if (!self.displayOptions.layoutControls.playbackRateEnabled) { + return; + } + + const sourceChangeButton = self.domRef.wrapper.querySelector('.fluid_control_playback_rate'); + sourceChangeButton.style.display = 'inline-block'; + + const sourceChangeList = document.createElement('div'); + sourceChangeList.className = 'fluid_video_playback_rates'; + sourceChangeList.style.display = 'none'; + + if ( + !Array.isArray(self.displayOptions.layoutControls.controlBar.playbackRates) + || self.displayOptions.layoutControls.controlBar.playbackRates.some( + rate => typeof rate !== 'string' || Number.isNaN(Number(rate.replace('x', ''))) + ) + ) { + self.displayOptions.layoutControls.controlBar.playbackRates = ['x2', 'x1.5', 'x1', 'x0.5']; + } + + self.displayOptions.layoutControls.controlBar.playbackRates.forEach(function (rate) { + const sourceChangeDiv = document.createElement('div'); + sourceChangeDiv.className = 'fluid_video_playback_rates_item'; + sourceChangeDiv.innerText = rate; + + sourceChangeDiv.addEventListener('click', function (event) { + event.stopPropagation(); + let playbackRate = this.innerText.replace('x', ''); + self.setPlaybackSpeed(playbackRate); + self.openCloseVideoPlaybackRate(); + + }); + sourceChangeList.appendChild(sourceChangeDiv); + }); + + sourceChangeButton.appendChild(sourceChangeList); + sourceChangeButton.addEventListener('click', function () { + self.openCloseVideoPlaybackRate(); + }); + }; + + self.openCloseVideoPlaybackRate = () => { + const sourceChangeList = self.domRef.wrapper.querySelector('.fluid_video_playback_rates'); + + if (self.isCurrentlyPlayingAd || 'none' !== sourceChangeList.style.display) { + sourceChangeList.style.display = 'none'; + return; + } + + sourceChangeList.style.display = 'block'; + const mouseOut = function () { + sourceChangeList.removeEventListener('mouseleave', mouseOut); + sourceChangeList.style.display = 'none'; + }; + sourceChangeList.addEventListener('mouseleave', mouseOut); + }; + + self.createDownload = () => { + const downloadOption = self.domRef.wrapper.querySelector('.fluid_control_download'); + if (!self.displayOptions.layoutControls.allowDownload) { + return; + } + downloadOption.style.display = 'inline-block'; + + let downloadClick = document.createElement('a'); + downloadClick.className = 'fp_download_click'; + downloadClick.onclick = function (e) { + const linkItem = this; + + if (typeof e.stopImmediatePropagation !== 'undefined') { + e.stopImmediatePropagation(); + } + + setTimeout(function () { + linkItem.download = ''; + linkItem.href = ''; + }, 100); + }; + + downloadOption.appendChild(downloadClick); + + downloadOption.addEventListener('click', function () { + const downloadItem = self.domRef.wrapper.querySelector('.fp_download_click'); + downloadItem.download = self.originalSrc; + downloadItem.href = self.originalSrc; + downloadClick.click(); + }); + }; + + self.theatreToggle = () => { + self.debugMessage(`Toggling Theater Mode`); + if (self.isInIframe) { + return; + } + + // Theatre and fullscreen, it's only one or the other + this.resetDisplayMode('theaterMode'); + + // Advanced Theatre mode if specified + if (self.displayOptions.layoutControls.theatreAdvanced) { + const elementForTheatre = self.domRef.wrapper.querySelector(`#${self.displayOptions.layoutControls.theatreAdvanced.theatreElement}`); + const theatreClassToApply = self.displayOptions.layoutControls.theatreAdvanced.classToApply; + if (elementForTheatre != null && theatreClassToApply != null) { + if (!self.theatreMode) { + elementForTheatre.classList.add(theatreClassToApply); + } else { + elementForTheatre.classList.remove(theatreClassToApply); + } + self.theatreModeAdvanced = !self.theatreModeAdvanced; + } else { + console.log('[FP_ERROR] Theatre mode elements could not be found, defaulting behaviour.'); + // Default overlay behaviour + self.defaultTheatre(); + } + } else { + // Default overlay behaviour + self.defaultTheatre(); + } + + // Set correct variables + self.theatreMode = !self.theatreMode; + self.fluidStorage.fluidTheatre = self.theatreMode; + + // Trigger theatre event + const theatreEvent = (self.theatreMode) ? 'theatreModeOn' : 'theatreModeOff'; + const event = document.createEvent('CustomEvent'); + event.initEvent(theatreEvent, false, true); + self.domRef.player.dispatchEvent(event); + + self.resizeVpaidAuto(); + }; + + self.defaultTheatre = () => { + const videoWrapper = self.domRef.wrapper; + + if (self.theatreMode) { + videoWrapper.classList.remove('fluid_theatre_mode'); + videoWrapper.style.maxHeight = ''; + videoWrapper.style.marginTop = ''; + videoWrapper.style.left = ''; + videoWrapper.style.right = ''; + videoWrapper.style.position = ''; + if (!self.displayOptions.layoutControls.fillToContainer) { + videoWrapper.style.width = self.originalWidth + 'px'; + videoWrapper.style.height = self.originalHeight + 'px'; + } else { + videoWrapper.style.width = '100%'; + videoWrapper.style.height = '100%'; + } + return; + } + + videoWrapper.classList.add('fluid_theatre_mode'); + const workingWidth = self.displayOptions.layoutControls.theatreSettings.width; + let defaultHorizontalMargin = '10px'; + videoWrapper.style.width = workingWidth; + videoWrapper.style.height = self.displayOptions.layoutControls.theatreSettings.height; + videoWrapper.style.maxHeight = screen.height + "px"; + videoWrapper.style.marginTop = self.displayOptions.layoutControls.theatreSettings.marginTop + 'px'; + switch (self.displayOptions.layoutControls.theatreSettings.horizontalAlign) { + case 'center': + // We must calculate the margin differently based on whether they passed % or px + if (typeof (workingWidth) == 'string' && workingWidth.substr(workingWidth.length - 1) === "%") { + // A margin of half the remaining space + defaultHorizontalMargin = ((100 - parseInt(workingWidth.substring(0, workingWidth.length - 1))) / 2) + "%"; + } else if (typeof (workingWidth) == 'string' && workingWidth.substr(workingWidth.length - 2) === "px") { + // Half the (Remaining width / fullwidth) + defaultHorizontalMargin = (((screen.width - parseInt(workingWidth.substring(0, workingWidth.length - 2))) / screen.width) * 100 / 2) + "%"; + } else { + console.log('[FP_ERROR] Theatre width specified invalid.'); + } + + videoWrapper.style.left = defaultHorizontalMargin; + break; + case 'right': + videoWrapper.style.right = defaultHorizontalMargin; + break; + case 'left': + default: + videoWrapper.style.left = defaultHorizontalMargin; + break; + } + }; + + // Set the poster for the video, taken from custom params + // Cannot use the standard video tag poster image as it can be removed by the persistent settings + self.posterImage = () => { + if (!self.displayOptions.layoutControls.posterImage) { + return; + } + + const containerDiv = document.createElement('div'); + containerDiv.className = 'fluid_pseudo_poster'; + if (['auto', 'contain', 'cover'].indexOf(self.displayOptions.layoutControls.posterImageSize) === -1) { + console.log('[FP_ERROR] Not allowed value in posterImageSize'); + return; + } + containerDiv.style.background = "url('" + self.displayOptions.layoutControls.posterImage + "') center center / " + + self.displayOptions.layoutControls.posterImageSize + " no-repeat black"; + self.domRef.player.parentNode.insertBefore(containerDiv, null); + }; + + // This is called when a media type is unsupported. We'll find the current source and try set the next source if it exists + self.nextSource = () => { + const sources = self.domRef.player.getElementsByTagName('source'); + + if (!sources.length) { + return null; + } + + for (let i = 0; i < sources.length - 1; i++) { + if (sources[i].getAttribute('src') === self.originalSrc && sources[i + 1].getAttribute('src')) { + self.setVideoSource(sources[i + 1].getAttribute('src')); + return; + } + } + }; + + self.inIframe = () => { + try { + return window.self !== window.top; + } catch (e) { + return true; + } + }; + + self.setPersistentSettings = (ignoreMute = false) => { + try { + if (!(typeof (Storage) !== 'undefined' && typeof (localStorage) !== 'undefined')) { + return; + } + } catch (e) { + return; + } + + // See https://github.com/fluid-player/fluid-player/issues/271 + const testKey = '_fp_storage_enabled', storage = localStorage; + try { + storage.setItem(testKey, '1'); + storage.removeItem(testKey); + } catch (error) { + return false; + } + + self.fluidStorage = localStorage; + if (typeof (self.fluidStorage.fluidVolume) !== 'undefined' + && self.displayOptions.layoutControls.persistentSettings.volume + && !ignoreMute) { + self.setVolume(self.fluidStorage.fluidVolume); + + if (typeof (self.fluidStorage.fluidMute) !== 'undefined' && self.fluidStorage.fluidMute === 'true') { + self.muteToggle(); + } + } + + if (typeof (self.fluidStorage.fluidQuality) !== 'undefined' + && self.displayOptions.layoutControls.persistentSettings.quality) { + const sourceOption = self.domRef.wrapper.querySelector('.js-source_' + self.fluidStorage.fluidQuality); + const sourceChangeButton = self.domRef.wrapper.querySelector('.fluid_control_video_source'); + if (sourceOption) { + sourceOption.click(); + sourceChangeButton.click(); + } + } + + if (typeof (self.fluidStorage.fluidSpeed) !== 'undefined' + && self.displayOptions.layoutControls.persistentSettings.speed) { + self.setPlaybackSpeed(self.fluidStorage.fluidSpeed); + } + + if (typeof (self.fluidStorage.fluidTheatre) !== 'undefined' + && self.fluidStorage.fluidTheatre === 'true' + && self.displayOptions.layoutControls.persistentSettings.theatre) { + self.theatreToggle(); + } + }; + + // "API" Functions + self.play = () => { + if (!self.domRef.player.paused) { + return; + } + self.playPauseToggle(); + + return true; + }; + + self.pause = () => { + if (!self.domRef.player.paused) { + self.playPauseToggle(); + } + return true; + }; + + self.skipTo = (time) => { + self.domRef.player.currentTime = time; + }; + + self.setPlaybackSpeed = (speed) => { + if (self.isCurrentlyPlayingAd) { + return; + } + self.domRef.player.playbackRate = speed; + self.fluidStorage.fluidSpeed = speed; + }; + + self.setVolume = (passedVolume) => { + self.domRef.player.volume = passedVolume; + + // If user scrolls to volume 0, we should not store 0 as + // latest volume - there is a property called "muted" already + // and storing 0 will break the toggle. + // In case user scrolls to 0 we assume last volume to be 1 + // for toggle. + const latestVolume = 0 === passedVolume ? 1 : passedVolume; + + self.latestVolume = latestVolume; + self.fluidStorage.fluidVolume = latestVolume; + }; + + /** + * @param {HTMLVideoElement} instance + */ + self.isCurrentlyPlayingVideo = (instance) => { + return instance && instance.currentTime > 0 && !instance.paused && !instance.ended && instance.readyState > 2; + }; + + self.setHtmlOnPauseBlock = (passedHtml) => { + if (typeof passedHtml != 'object' || typeof passedHtml.html == 'undefined') { + return false; + } + + const htmlBlock = self.domRef.wrapper.querySelector('.fluid_html_on_pause_container'); + + // We create the HTML block from scratch if it doesn't already exist + if (!htmlBlock) { + const containerDiv = document.createElement('div'); + containerDiv.className = 'fluid_html_on_pause'; + containerDiv.style.display = 'none'; + containerDiv.innerHTML = passedHtml.html; + containerDiv.onclick = function () { + self.playPauseToggle(); + }; + + if (passedHtml.width) { + containerDiv.style.width = passedHtml.width + 'px'; + } + + if (passedHtml.height) { + containerDiv.style.height = passedHtml.height + 'px'; + } + + self.domRef.player.parentNode.insertBefore(containerDiv, null); + return; + } + + htmlBlock.innerHTML = passedHtml.html; + + if (passedHtml.width) { + htmlBlock.style.width = passedHtml.width + 'px'; + } + + if (passedHtml.height) { + htmlBlock.style.height = passedHtml.height + 'px'; + } + }; + + self.toggleControlBar = (show) => { + const controlBar = self.domRef.wrapper.querySelector('.fluid_controls_container'); + + if (show) { + controlBar.className += ' initial_controls_show'; + return; + } + + controlBar.className = controlBar.className.replace(' initial_controls_show', ''); + }; + + self.on = (eventCall, callback) => { + /** + * Improves events by adding player info to the callbacks + * + * source | preRoll | midRoll | postRoll + */ + const getAdditionalInfo = () => ({ + mediaSourceType: self.currentMediaSourceType + }); + + const functionCall = (event, additionalEventData = {}) => { + const additionalInfo = {...getAdditionalInfo(), ...additionalEventData} + return callback(event, additionalInfo); + } + + const eventHandlers = { + play: () => self.domRef.player.addEventListener('play', functionCall), + seeked: () => self.domRef.player.addEventListener('seeked', functionCall), + ended: () => self.domRef.player.addEventListener('ended', functionCall), + pause: () => self.domRef.player.addEventListener('pause', (event) => { + if (!self.fluidPseudoPause) { + functionCall(event) + } + }), + playing: () => self.domRef.player.addEventListener('playing', functionCall), + waiting: () => self.domRef.player.addEventListener('waiting', functionCall), + theatreModeOn: () => self.domRef.player.addEventListener('theatreModeOn', functionCall), + theatreModeOff: () => self.domRef.player.addEventListener('theatreModeOff', functionCall), + timeupdate: () => self.domRef.player.addEventListener('timeupdate', (event) => { + functionCall(event, { currentTime: self.domRef.player.currentTime }); + }), + miniPlayerToggle: () => self.domRef.player.addEventListener('miniPlayerToggle', functionCall) + }; + + if (!eventHandlers[eventCall]) { + console.error(`[FP_ERROR] Event "${eventCall}" is not recognized`); + return; + } + + // Call event handler + eventHandlers[eventCall](); + }; + + self.toggleLogo = (logo) => { + if (typeof logo != 'object' || !logo.imageUrl) { + return false; + } + + const logoBlock = self.domRef.wrapper.querySelector('.fp_logo'); + + // We create the logo from scratch if it doesn't already exist, they might not give everything correctly so we + self.displayOptions.layoutControls.logo.imageUrl = (logo.imageUrl) ? logo.imageUrl : null; + self.displayOptions.layoutControls.logo.position = (logo.position) ? logo.position : 'top left'; + self.displayOptions.layoutControls.logo.clickUrl = (logo.clickUrl) ? logo.clickUrl : null; + self.displayOptions.layoutControls.logo.opacity = (logo.opacity) ? logo.opacity : 1; + self.displayOptions.layoutControls.logo.mouseOverImageUrl = (logo.mouseOverImageUrl) ? logo.mouseOverImageUrl : null; + self.displayOptions.layoutControls.logo.imageMargin = (logo.imageMargin) ? logo.imageMargin : '2px'; + self.displayOptions.layoutControls.logo.hideWithControls = (logo.hideWithControls) ? logo.hideWithControls : false; + self.displayOptions.layoutControls.logo.showOverAds = (logo.showOverAds) ? logo.showOverAds : false; + + if (logoBlock) { + logoBlock.remove(); + } + + self.initLogo(); + }; + + // this functions helps in adding event listeners for future dynamic elements + // trackEvent(document, "click", ".some_elem", callBackFunction); + self.trackEvent = (el, evt, sel, handler) => { + if (typeof self.events[sel] === 'undefined') { + self.events[sel] = {}; + } + + if (typeof self.events[sel][evt] === 'undefined') { + self.events[sel][evt] = []; + } + + self.events[sel][evt].push(handler); + self.registerListener(el, evt, sel, handler); + }; + + self.registerListener = (el, evt, sel, handler) => { + const currentElements = el.querySelectorAll(sel); + for (let i = 0; i < currentElements.length; i++) { + currentElements[i].addEventListener(evt, handler); + } + }; + + self.copyEvents = (topLevelEl) => { + for (let sel in self.events) { + if (!self.events.hasOwnProperty(sel)) { + continue; + } + + for (let evt in self.events[sel]) { + if (!self.events[sel].hasOwnProperty(evt)) { + continue; + } + + for (let i = 0; i < self.events[sel][evt].length; i++) { + self.registerListener(topLevelEl, evt, sel, self.events[sel][evt][i]); + } + } + } + }; + + /** + * Resets all display types that are not the target display mode + * + * @param {'fullScreen'|'theaterMode'|'miniPlayer'} displayTarget + */ + self.resetDisplayMode = (displayTarget) => { + if (self.fullscreenMode && displayTarget !== 'fullScreen') { + self.fullscreenToggle(); + } + + if (self.theatreMode && displayTarget !== 'theaterMode') { + self.theatreToggle(); + } + + if (self.miniPlayerToggledOn && displayTarget !== 'miniPlayer') { + self.toggleMiniPlayer('off'); + } + } + + self.destroy = () => { + self.domRef.player.classList.remove('js-fluid-player'); + const numDestructors = self.destructors.length; + + if (0 === numDestructors) { + return; + } + + self.destructors.forEach(destructor => destructor.call(self)); + + const container = self.domRef.wrapper; + + if (!container) { + console.warn('Unable to remove wrapper element for Fluid Player instance - element not found'); + return; + } + + if ('function' === typeof container.remove) { + container.remove(); + return; + } + + if (container.parentNode) { + container.parentNode.removeChild(container); + return; + } + + console.error('Unable to remove wrapper element for Fluid Player instance - no parent'); + } +}; + +/** + * Public Fluid Player API interface + * @param {FluidPlayer} instance + */ +const fluidPlayerInterface = function (instance) { + this.play = () => { + return instance.play() + }; + + this.pause = () => { + return instance.pause() + }; + + this.skipTo = (position) => { + return instance.skipTo(position) + }; + + this.setPlaybackSpeed = (speed) => { + return instance.setPlaybackSpeed(speed) + }; + + this.setVolume = (volume) => { + return instance.setVolume(volume) + }; + + this.setHtmlOnPauseBlock = (options) => { + return instance.setHtmlOnPauseBlock(options) + }; + + this.toggleControlBar = (state) => { + return instance.toggleControlBar(state) + }; + + this.toggleFullScreen = (state) => { + return instance.fullscreenToggle(state) + }; + + this.toggleMiniPlayer = (state) => { + if (state === undefined) { + state = !instance.miniPlayerToggledOn; + } + + return instance.toggleMiniPlayer(state ? 'on' : 'off', true); + }; + + this.destroy = () => { + instance.domRef.player + return instance.destroy() + }; + + this.dashInstance = () => { + return !!instance.dashPlayer ? instance.dashPlayer : null; + } + + this.hlsInstance = () => { + return !!instance.hlsPlayer ? instance.hlsPlayer : null; + } + + this.on = (event, callback) => { + return instance.on(event, callback) + }; + + this.setDebug = (value) => { + instance.displayOptions.debug = value; + } +} + +/** + * Initialize and attach Fluid Player to instance of HTMLVideoElement + * + * @param target ID of HTMLVideoElement or reference to HTMLVideoElement + * @param options Fluid Player configuration options + * @returns {fluidPlayerInterface} + */ +const fluidPlayerInitializer = function (target, options) { + const instance = new fluidPlayerClass(); + + if (!options) { + options = {}; + } + + instance.init(target, options); + + const publicInstance = new fluidPlayerInterface(instance); + + if (window && FP_DEVELOPMENT_MODE) { + const debugApi = { + id: target, + options: options, + instance: publicInstance, + internals: instance + }; + + if (typeof window.fluidPlayerDebug === 'undefined') { + window.fluidPlayerDebug = []; + } + + window.fluidPlayerDebug.push(debugApi); + + console.log('Created instance of Fluid Player. ' + + 'Debug API available at window.fluidPlayerDebug[' + (window.fluidPlayerDebug.length - 1) + '].', debugApi); + } + + return publicInstance; +} + + +if (FP_DEVELOPMENT_MODE) { + console.log('Fluid Player - Development Build' + (FP_RUNTIME_DEBUG ? ' (in debug mode)' : '')); +} + +export default fluidPlayerInitializer; diff --git a/client/fluid-player/src/index.d.ts b/client/fluid-player/src/index.d.ts new file mode 100644 index 0000000..e9e232a --- /dev/null +++ b/client/fluid-player/src/index.d.ts @@ -0,0 +1,222 @@ +declare module 'fluid-player' { + function fluidPlayer( + target: HTMLVideoElement | String | string, + options?: Partial + ): FluidPlayerInstance; + + export default fluidPlayer; +} + +declare type AdditionalEventInfo = { mediaSourceType: 'ad' | 'source' }; +declare type OnPlay = (event: 'play', callback: (additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnPlaying = + (event: 'playing', callback: (event: Event, additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnPause = (event: 'pause', callback: (additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnEnded = (event: 'ended', callback: (additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnSeeked = (event: 'seeked', callback: (additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnTheaterModeOn = + (event: 'theatreModeOn', callback: (event: Event, additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnTheaterModeOff = + (event: 'theatreModeOff', callback: (event: Event, additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnTimeUpdate = + (event: 'timeupdate', callback: (time: number, additionalInfo: AdditionalEventInfo) => void) => void; +declare type OnMiniPlayerToggle = + (event: 'miniPlayerToggle', callback: (event: CustomEvent<{ + isToggledOn: boolean + }>, additionalInfo: AdditionalEventInfo) => void) => void; + +declare interface FluidPlayerInstance { + play: () => void; + pause: () => void; + skipTo: (seconds: number) => void; + setPlaybackSpeed: (speed: number) => void; + setVolume: (volume: number) => void; + toggleControlBar: (shouldToggle: boolean) => void; + toggleFullScreen: (shouldToggle: boolean) => void; + toggleMiniPlayer: (shouldToggle: boolean) => void; + setHtmlOnPauseBlock: (pauseBlock: { html: string; width: number; height: number; }) => void; + destroy: () => void; + dashInstance: () => any | null; + hlsInstance: () => any | null; + on: OnPlay & OnPlaying & OnPause & OnEnded & OnSeeked & OnTheaterModeOn & OnTheaterModeOff & OnTimeUpdate & + OnMiniPlayerToggle; +} + +declare interface LayoutControls { + primaryColor: false | string; + posterImage: false | string; + posterImageSize: 'auto' | 'cover' | 'contain'; + playButtonShowing: boolean; + playPauseAnimation: boolean; + fillToContainer: boolean; + autoPlay: boolean; + preload: 'none' | 'metadata' | 'auto' | string; + mute: boolean; + doubleclickFullscreen: boolean; + subtitlesEnabled: boolean; + keyboardControl: boolean; + title: string; + loop: boolean; + logo: Partial<{ + imageUrl: string | null; + position: 'top right' | 'top left' | 'bottom right' | 'bottom left'; + clickUrl: string | null; + opacity: number; + mouseOverImageUrl: string | null; + imageMargin: string; + hideWithControls: boolean; + showOverAds: boolean; + }>; + controlBar: Partial<{ + autoHide: boolean; + autoHideTimeout: number; + animated: boolean; + }>; + timelinePreview: VTTPreviewOptions | StaticPreviewOptions; + htmlOnPauseBlock: Partial<{ + html: string | null; + height: number | null; + width: number | null; + }>; + layout: 'default' | string; + allowDownload: boolean; + playbackRateEnabled: boolean; + allowTheatre: boolean; + theatreAdvanced: Partial<{ + theatreElement: string; + classToApply: string; + }>; + theatreSettings: Partial<{ + width: string; + height: string; + marginTop: number; + horizontalAlign: 'center' | 'left' | 'right'; + }>; + playerInitCallback: () => void; + persistentSettings: Partial<{ + volume: boolean; + quality: boolean; + speed: boolean; + theatre: boolean; + }>; + controlForwardBackward: Partial<{ + show: boolean; + doubleTapMobile: boolean; + }>; + contextMenu: Partial<{ + controls: boolean; + links: Array<{ + href: string; + label: string; + }>; + }>; + miniPlayer: Partial<{ + enabled: boolean; + width: number; + height: number; + widthMobile: number; + placeholderText: string; + position: 'top right' | 'top left' | 'bottom right' | 'bottom left'; + autoToggle: boolean; + }>; + showCardBoardView: boolean; + showCardBoardJoystick: boolean; +} + +declare interface VTTPreviewOptions { + file: string; + type: 'VTT'; + spriteRelativePath?: boolean; + sprite?: string; +} + +declare interface StaticPreviewOptions { + type: 'static'; + frames: Array<{ + startTime: number; + endTime: number; + image: string; + x: number; + y: number; + w: number; + h: number; + }> +} + +declare interface VastOptions { + adList: Array; + skipButtonCaption: string; + skipButtonClickCaption: string; + adText: string; + adTextPosition: 'top right' | 'top left' | 'bottom right' | 'bottom left'; + adCTAText: string | boolean; + adCTATextPosition: 'top right' | 'top left' | 'bottom right' | 'bottom left'; + adCTATextVast: boolean; + vastTimeout: number; + showPlayButton: boolean; + maxAllowedVastTagRedirects: number; + showProgressbarMarkers: boolean; + adClickable: boolean; + allowVPAID: boolean; + vastAdvanced: Partial<{ + vastLoadedCallback: () => void; + noVastVideoCallback: () => void; + vastVideoSkippedCallback: () => void; + vastVideoEndedCallback: () => void; + }>; +} + +declare interface AdOptions { + vastTag: string; + roll: string; + fallbackVastTags?: Array; + adText?: string; + adTextPosition?: 'top right' | 'top left' | 'bottom right' | 'bottom left'; + adClickable?: boolean; + vAlign?: 'top' | 'middle' | 'bottom'; + nonLinearDuration?: number; + size?: '468x60' | '300x250' | '728x90'; +} + +declare interface PreRollAdOptions extends AdOptions { + roll: 'preRoll'; +} + +declare interface MidRollAdOptions extends AdOptions { + roll: 'midRoll'; + timer: number | string; +} + +declare interface PostRollAdOptions extends AdOptions { + roll: 'postRoll'; +} + +declare interface OnPauseRollAdOptions extends AdOptions { + roll: 'onPauseRoll'; +} + +declare interface ModulesOptions { + configureHls: (options: any) => any; + onBeforeInitHls: (hls: any) => void; + onAfterInitHls: (hls: any) => void; + configureDash: (options: any) => any; + onBeforeInitDash: (dash: any) => void; + onAfterInitDash: (dash: any) => void; +} + +declare interface FluidPlayerOptions { + layoutControls: Partial; + vastOptions: Partial; + modules: Partial; + onBeforeXMLHttpRequestOpen?: (request: XMLHttpRequest) => void; + onBeforeXMLHttpRequest?: (request: XMLHttpRequest) => void; + debug?: boolean; + captions: Partial<{ + play: string; + pause: string; + mute: string; + unmute: string; + fullscreen: string; + exitFullscreen: string; + }>; +} diff --git a/client/fluid-player/src/index.js b/client/fluid-player/src/index.js new file mode 100644 index 0000000..d26f47e --- /dev/null +++ b/client/fluid-player/src/index.js @@ -0,0 +1,33 @@ + +if ('undefined' === typeof FP_HOMEPAGE) { + globalThis.FP_HOMEPAGE = 'https://fluidplayer.com'; +} + +if ('undefined' === typeof FP_BUILD_VERSION) { + globalThis.FP_BUILD_VERSION = 'v3'; +} + +if ('undefined' === typeof FP_ENV) { + const isLocalhost = globalThis + && globalThis.location + && (globalThis.location.hostname === 'localhost' + || globalThis.location.hostname === '127.0.0.1' + || globalThis.location.hostname === ''); + + if ('undefined' !== typeof process && process && process.env && process.env.NODE_ENV) { + globalThis.FP_ENV = process.env.NODE_ENV; + } else if (globalThis && !isLocalhost) { + globalThis.FP_ENV = 'production'; + } else { + globalThis.FP_ENV = 'development'; + } +} + +if ('undefined' === typeof FP_DEBUG) { + globalThis.FP_DEBUG = false; +} + +import './polyfills'; +import fluidPlayerInitializer from './fluidplayer.js'; + +export default fluidPlayerInitializer; diff --git a/client/fluid-player/src/modules/adsupport.js b/client/fluid-player/src/modules/adsupport.js new file mode 100644 index 0000000..4fb2d6a --- /dev/null +++ b/client/fluid-player/src/modules/adsupport.js @@ -0,0 +1,1751 @@ +// @ts-check + +/** + * @param {import("../fluidplayer.js").FluidPlayer} playerInstance + * @param {unknown} options + */ +function adSupport(playerInstance, options) { + const VPAID_VERSION = '2.0'; + + playerInstance.renderLinearAd = (ad, backupTheVideoTime) => { + playerInstance.toggleLoader(false); + + //get the proper ad + playerInstance.vastOptions = ad; + + if (backupTheVideoTime) { + playerInstance.backupMainVideoContentTime(ad.rollListId); + } + + const playVideoPlayer = ad => { + playerInstance.switchPlayerToVpaidMode = ad => { + playerInstance.debugMessage('starting function switchPlayerToVpaidMode'); + const vpaidIframe = "fp_" + ad.id + "_fluid_vpaid_iframe"; + const creativeData = {}; + creativeData.AdParameters = ad.adParameters; + const slotElement = document.createElement('div'); + slotElement.className = 'fluid_vpaid_slot'; + slotElement.setAttribute('adListId', ad.id); + + playerInstance.domRef.player.parentNode.insertBefore(slotElement, vpaidIframe.nextSibling); + + const environmentVars = { + slot: slotElement, + videoSlot: playerInstance.domRef.player, + videoSlotCanAutoPlay: true + }; + + // calls this functions after ad unit is loaded in iframe + const ver = playerInstance.vpaidAdUnit.handshakeVersion(VPAID_VERSION); + const compare = playerInstance.compareVersion(VPAID_VERSION, ver); + if (compare === 1) { + //VPAID version of ad is lower than we need + ad.error = true; + playerInstance.playMainVideoWhenVpaidFails(403); + return false; + } + + if (playerInstance.vastOptions.skipoffset !== false) { + playerInstance.addSkipButton(); + } + + playerInstance.domRef.player.loop = false; + playerInstance.domRef.player.removeAttribute('controls'); //Remove the default Controls + + playerInstance.vpaidCallbackListenersAttach(); + const mode = (playerInstance.fullscreenMode ? 'fullscreen' : 'normal'); + const adWidth = playerInstance.domRef.player.offsetWidth; + const adHeight = playerInstance.domRef.player.offsetHeight; + playerInstance.vpaidAdUnit.initAd(adWidth, adHeight, mode, 3000, creativeData, environmentVars); + + const progressbarContainer = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_controls_currentprogress'); + for (let i = 0; i < progressbarContainer.length; i++) { + progressbarContainer[i].style.backgroundColor = playerInstance.displayOptions.layoutControls.adProgressColor; + } + + playerInstance.toggleLoader(false); + ad.played = true; + playerInstance.adFinished = false; + }; + + playerInstance.switchPlayerToVastMode = () => { + // Get the actual duration from the video file if it is not present in the VAST XML + if (!playerInstance.vastOptions.duration) { + playerInstance.vastOptions.duration = selectedMediaFile.delivery === 'streaming' ? + Infinity : playerInstance.domRef.player.duration; + } + + if (playerInstance.displayOptions.layoutControls.showCardBoardView) { + + if (!ad.landingPage) { + playerInstance.addCTAButton(ad.clickthroughUrl); + } else { + playerInstance.addCTAButton(ad.landingPage); + } + + } else { + let idAdClickable = [undefined, true] + .includes(playerInstance.displayOptions.vastOptions.adClickable); + + if (playerInstance.rollsById[ad.rollListId].adClickable !== undefined) { + idAdClickable = playerInstance.rollsById[ad.rollListId].adClickable; + } + + if (idAdClickable) { + playerInstance.addClickthroughLayer(); + } + + playerInstance.addCTAButton(ad.landingPage); + } + + if (playerInstance.vastOptions.skipoffset !== false) { + playerInstance.addSkipButton(); + } + + playerInstance.domRef.player.loop = false; + + playerInstance.addAdCountdown(); + + playerInstance.domRef.player.removeAttribute('controls'); //Remove the default Controls + + playerInstance.vastLogoBehaviour(true); + + const progressbarContainer = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_controls_currentprogress'); + for (let i = 0; i < progressbarContainer.length; i++) { + progressbarContainer[i].style.backgroundColor = playerInstance.displayOptions.layoutControls.adProgressColor; + } + + if (playerInstance.rollsById[ad.rollListId].adText || ad.adText) { + const adTextToShow = ad.adText ? ad.adText : playerInstance.rollsById[ad.rollListId].adText; + playerInstance.addAdPlayingText(adTextToShow); + } + + playerInstance.positionTextElements(ad); + + playerInstance.toggleLoader(false); + ad.played = true; + playerInstance.adFinished = false; + playerInstance.domRef.player.play(); + + //Announce the impressions + playerInstance.trackSingleEvent('impression'); + + playerInstance.domRef.player.removeEventListener('loadedmetadata', playerInstance.switchPlayerToVastMode); + + // if in vr mode then do not show + if (playerInstance.vrMode) { + const adCountDownTimerText = playerInstance.domRef.wrapper.querySelector('.ad_countdown'); + const ctaButton = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta'); + const addAdPlayingTextOverlay = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing'); + const skipBtn = playerInstance.domRef.wrapper.querySelector('.skip_button'); + + if (adCountDownTimerText) { + adCountDownTimerText.style.display = 'none'; + } + + if (ctaButton) { + ctaButton.style.display = 'none'; + } + + if (addAdPlayingTextOverlay) { + addAdPlayingTextOverlay.style.display = 'none'; + } + + if (skipBtn) { + skipBtn.style.display = 'none'; + } + } + }; + + playerInstance.domRef.player.pause(); + + // Remove the streaming objects to prevent errors on the VAST content + playerInstance.detachStreamers(); + + // Try to load multiple + const selectedMediaFile = playerInstance.getSupportedMediaFileObject(playerInstance.vastOptions.mediaFileList); + + // if player in cardboard mode then, linear ads media type should be a '360' video + if (playerInstance.displayOptions.layoutControls.showCardBoardView && ad.mediaType !== '360') { + ad.error = true; + playerInstance.playMainVideoWhenVastFails(403); + return false; + } + + const isVpaid = playerInstance.vastOptions.vpaid; + + if (!isVpaid && selectedMediaFile.isUnsuportedHls) { + import(/* webpackChunkName: "hlsjs" */ 'hls.js').then((it) => { + window.Hls = it.default; + const hls = new Hls({ + debug: typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true, + startPosition: 0, + p2pConfig: { + logLevel: false, + }, + enableWebVTT: false, + enableCEA708Captions: false, + }); + + hls.attachMedia(playerInstance.domRef.player); + hls.loadSource(selectedMediaFile.src); + playerInstance.isCurrentlyPlayingAd = true; + + playerInstance.hlsPlayer = hls; + + playerInstance.domRef.player.addEventListener('loadedmetadata', playerInstance.switchPlayerToVastMode); + playerInstance.domRef.player.addEventListener('ended', () => { + hls.detachMedia(); + hls.destroy(); + playerInstance.hlsPlayer = false; + playerInstance.onVastAdEnded(); + }); + + playerInstance.domRef.player.play(); + }); + } else if (!isVpaid) { + if (selectedMediaFile.src === false) { + // Couldn’t find MediaFile that is supported by this video player, based on the attributes of the MediaFile element. + ad.error = true; + playerInstance.playMainVideoWhenVastFails(403); + return false; + } + + playerInstance.domRef.player.addEventListener('loadedmetadata', playerInstance.switchPlayerToVastMode); + + playerInstance.domRef.player.src = selectedMediaFile.src; + playerInstance.isCurrentlyPlayingAd = true; + + if (playerInstance.displayOptions.vastOptions.showProgressbarMarkers) { + playerInstance.hideAdMarkers(); + } + + playerInstance.domRef.player.load(); + + //Handle the ending of the Pre-Roll ad + playerInstance.domRef.player.addEventListener('ended', playerInstance.onVastAdEnded); + + } else { + playerInstance.loadVpaid(ad, selectedMediaFile.src); + + if (playerInstance.displayOptions.vastOptions.showProgressbarMarkers) { + playerInstance.hideAdMarkers(); + } + } + }; + + /** + * Sends requests to the tracking URIs + */ + const videoPlayerTimeUpdate = () => { + if (playerInstance.adFinished) { + playerInstance.domRef.player.removeEventListener('timeupdate', videoPlayerTimeUpdate); + return; + } + + const currentTime = Math.floor(playerInstance.domRef.player.currentTime); + if (playerInstance.vastOptions.duration !== 0) { + playerInstance.scheduleTrackingEvent(currentTime, playerInstance.vastOptions.duration); + } + + if (currentTime >= (playerInstance.vastOptions.duration - 1) && playerInstance.vastOptions.duration !== 0) { + playerInstance.domRef.player.removeEventListener('timeupdate', videoPlayerTimeUpdate); + playerInstance.adFinished = true; + } + + }; + + playVideoPlayer(ad); + + playerInstance.domRef.player.addEventListener('timeupdate', videoPlayerTimeUpdate); + + }; + + playerInstance.playRoll = (adList) => { + // register all the ad pods + const newPods = []; + for (let i = 0; i < adList.length; i++) { + newPods.push(adList[i]); + } + playerInstance.temporaryAdPods = newPods; + + if (playerInstance.vastOptions !== null && playerInstance.vastOptions.adType.toLowerCase() === 'linear') { + return; + } + + const adToPlay = playerInstance.getNextAdPod(); + + if (adToPlay !== null) { + playerInstance.renderLinearAd(adToPlay, true); + } + + const roll = playerInstance.rollsById[adToPlay.rollListId].roll; + playerInstance.currentMediaSourceType = roll; + }; + + playerInstance.backupMainVideoContentTime = (rollListId) => { + const roll = playerInstance.rollsById[rollListId].roll; + + //spec configs by roll + switch (roll) { + case 'midRoll': + playerInstance.domRef.player.mainVideoCurrentTime = playerInstance.domRef.player.currentTime - 1; + break; + + case 'postRoll': + playerInstance.domRef.player.mainVideoCurrentTime = playerInstance.mainVideoDuration; + playerInstance.autoplayAfterAd = false; + playerInstance.domRef.player.currentTime = playerInstance.mainVideoDuration; + break; + + case 'preRoll': + if (playerInstance.domRef.player.currentTime > 0) { + playerInstance.domRef.player.mainVideoCurrentTime = playerInstance.domRef.player.currentTime - 1; + } + break; + } + }; + + playerInstance.getSupportedMediaFileObject = (mediaFiles) => { + let selectedMediaFile = null; + let adSupportedType = false; + if (mediaFiles.length) { + for (let i = 0; i < mediaFiles.length; i++) { + + if (mediaFiles[i].apiFramework !== 'VPAID') { + const supportLevel = playerInstance.getMediaFileTypeSupportLevel(mediaFiles[i]['type']); + + if (supportLevel === 'maybe' || supportLevel === 'probably') { + selectedMediaFile = mediaFiles[i]; + adSupportedType = true; + } + + //one of the best(s) option, no need to seek more + if (supportLevel === 'probably') { + break; + } + + if ( + supportLevel === 'no' && mediaFiles[i].delivery === 'streaming' && + (mediaFiles[i].type === 'application/vnd.apple.mpegurl' || mediaFiles[i].type === 'application/x-mpegURL') + ) { + selectedMediaFile = mediaFiles[i]; + selectedMediaFile.isUnsuportedHls = true; + adSupportedType = true; + } + + } else { + selectedMediaFile = mediaFiles[i]; + adSupportedType = true; + break; + } + } + } + + if (adSupportedType === false) { + return false; + } + + return selectedMediaFile; + }; + + /** + * Reports how likely it is that the current browser will be able to play media of a given MIME type. + * @return string|null "probably", "maybe", "no" or null + */ + playerInstance.getMediaFileTypeSupportLevel = (mediaType) => { + if (null === mediaType) { + return null; + } + + const tmpVideo = document.createElement('video'); + let response = tmpVideo.canPlayType(mediaType); + + return !response ? "no" : response; + }; + + playerInstance.scheduleTrackingEvent = (currentTime, duration) => { + if (currentTime === 0) { + playerInstance.trackSingleEvent('start'); + playerInstance.observe(); + playerInstance.domRef.player.timeInView = 0; + } + + // View Impression is defined by IAB as: Watching at least 2 seconds of the video where at least 50% of the ad’s pixels are visible on the screen + if (playerInstance.domRef.player.inView) { + if (playerInstance.domRef.player.timeInView > 2) { + playerInstance.trackSingleEvent('viewImpression'); + } else { + playerInstance.domRef.player.timeInView += currentTime; + } + } + + if ((typeof playerInstance.vastOptions.tracking['progress'] !== 'undefined') && + (playerInstance.vastOptions.tracking['progress'].length) && + (typeof playerInstance.vastOptions.tracking['progress'][currentTime] !== 'undefined')) { + + playerInstance.trackSingleEvent('progress', currentTime); + } + + if (currentTime === (Math.floor(duration / 4))) { + playerInstance.trackSingleEvent('firstQuartile'); + } + + if (currentTime === (Math.floor(duration / 2))) { + playerInstance.trackSingleEvent('midpoint'); + } + + if (currentTime === (Math.floor(duration * 3 / 4))) { + playerInstance.trackSingleEvent('thirdQuartile'); + } + + if (currentTime >= (duration - 1)) { + playerInstance.trackSingleEvent('complete'); + } + }; + + + // ADS + playerInstance.trackSingleEvent = (eventType, eventSubType) => { + if (typeof playerInstance.vastOptions === 'undefined' || playerInstance.vastOptions === null) { + return; + } + + let trackingUris = []; + trackingUris.length = 0; + + switch (eventType) { + case 'start': + case 'firstQuartile': + case 'midpoint': + case 'thirdQuartile': + case 'complete': + if (playerInstance.vastOptions.stopTracking[eventType] === false) { + if (playerInstance.vastOptions.tracking[eventType] !== null) { + trackingUris = playerInstance.vastOptions.tracking[eventType]; + } + + playerInstance.vastOptions.stopTracking[eventType] = true; + } + break; + + case 'progress': + playerInstance.vastOptions.tracking['progress'][eventSubType].elements.forEach(function (currentValue, index) { + if ( + (playerInstance.vastOptions.tracking['progress'][eventSubType].stopTracking === false) && + (playerInstance.vastOptions.tracking['progress'][eventSubType].elements.length) + ) { + trackingUris = playerInstance.vastOptions.tracking['progress'][eventSubType].elements; + } + + playerInstance.vastOptions.tracking['progress'][eventSubType].stopTracking = true; + }); + break; + + case 'impression': + if ( + (typeof playerInstance.vastOptions.impression !== 'undefined') && + (playerInstance.vastOptions.impression !== null) && + (typeof playerInstance.vastOptions.impression.length !== 'undefined') + ) { + trackingUris = playerInstance.vastOptions.impression; + } + break; + + case 'viewImpression': + if (playerInstance.vastOptions.stopTracking['viewImpression'] === true) { + break; + } + + if ( + (typeof playerInstance.vastOptions.viewImpression !== 'undefined') && + (playerInstance.vastOptions.viewImpression !== null) && + (typeof playerInstance.vastOptions.viewImpression.length !== 'undefined') + ) { + trackingUris = playerInstance.vastOptions.viewImpression; + playerInstance.vastOptions.stopTracking['viewImpression'] = true; + } + break; + + default: + break; + } + + playerInstance.callUris(trackingUris); + }; + + // ADS + playerInstance.completeNonLinearStatic = (ad) => { + playerInstance.closeNonLinear(ad.id); + if (playerInstance.adFinished === false) { + playerInstance.adFinished = true; + playerInstance.trackSingleEvent('complete'); + } + clearInterval(playerInstance.nonLinearTracking); + }; + + // ADS + /** + * Show up a nonLinear static creative + */ + playerInstance.createNonLinearStatic = (ad) => { + //get the proper ad + playerInstance.vastOptions = ad; + playerInstance.createBoard(ad); + if (playerInstance.rollsById[ad.rollListId].error === true || ad.error === true) { + playerInstance.announceLocalError(101); + return; + } + playerInstance.adFinished = false; + let duration = (playerInstance.rollsById[ad.rollListId].nonLinearDuration) ? playerInstance.rollsById[ad.rollListId].nonLinearDuration : false; + if (!playerInstance.vastOptions.vpaid) { + playerInstance.trackSingleEvent('start'); + duration = duration || playerInstance.vastOptions.duration; + + playerInstance.nonLinearTracking = setInterval(function () { + if (playerInstance.adFinished === true) { + return; + } + + const currentTime = Math.floor(playerInstance.domRef.player.currentTime); + playerInstance.scheduleTrackingEvent(currentTime, duration); + if (currentTime >= (duration - 1)) { + playerInstance.adFinished = true; + } + }, 400); + playerInstance.destructors.push(() => clearInterval(playerInstance.nonLinearTracking)); + } + + const time = parseInt(playerInstance.getCurrentTime()) + parseInt(duration); + playerInstance.scheduleTask({ time: time, closeStaticAd: ad, rollListId: ad.rollListId }); + }; + + // ADS + playerInstance.createVpaidNonLinearBoard = (ad) => { + // create iframe + // pass the js + + playerInstance.loadVpaidNonlinearAssets = function (ad) { + playerInstance.vastOptions = ad; + playerInstance.debugMessage('starting function switchPlayerToVpaidMode'); + + const vAlign = (ad.vAlign) ? ad.vAlign : playerInstance.nonLinearVerticalAlign; + const showCloseButton = (ad.vpaidNonLinearCloseButton) ? ad.vpaidNonLinearCloseButton : playerInstance.vpaidNonLinearCloseButton; + const vpaidIframe = "fp_" + ad.id + "_fluid_vpaid_iframe"; + const creativeData = {}; + creativeData.AdParameters = ad.adParameters; + const slotWrapper = document.createElement('div'); + slotWrapper.id = 'fluid_vpaidNonLinear_' + ad.id; + slotWrapper.className = 'fluid_vpaidNonLinear_' + vAlign; + slotWrapper.className += ' fluid_vpaidNonLinear_ad'; + slotWrapper.setAttribute('adListId', ad.id); + + // Default values in case nothing defined in VAST data or ad settings + let adWidth = Math.min(468, playerInstance.domRef.player.offsetWidth); + let adHeight = Math.min(60, Math.floor(playerInstance.domRef.player.offsetHeight / 4)); + + if (typeof ad.size !== 'undefined') { + const dimensions = ad.size.split('x'); + adWidth = dimensions[0]; + adHeight = dimensions[1]; + } else if (ad.dimension.width && ad.dimension.height) { + adWidth = ad.dimension.width; + adHeight = ad.dimension.height; + } + + slotWrapper.style.width = '100%'; + slotWrapper.style.height = adHeight + 'px'; + + let slotFrame; + if (showCloseButton) { + const slotFrame = document.createElement('div'); + slotFrame.className = 'fluid_vpaidNonLinear_frame'; + slotFrame.style.width = adWidth + 'px'; + slotFrame.style.height = adHeight + 'px'; + slotWrapper.appendChild(slotFrame); + + const closeBtn = document.createElement('div'); + closeBtn.className = 'close_button'; + closeBtn.innerHTML = ''; + closeBtn.title = playerInstance.displayOptions.layoutControls.closeButtonCaption; + const [tempadListId] = ad.id.split('_'); + closeBtn.onclick = function (event) { + + playerInstance.hardStopVpaidAd(''); + + if (typeof event.stopImmediatePropagation !== 'undefined') { + event.stopImmediatePropagation(); + } + playerInstance.adFinished = true; + + //if any other onPauseRoll then render it + if (playerInstance.rollsById[tempadListId].roll === 'onPauseRoll' && playerInstance.onPauseRollAdPods[0]) { + const getNextOnPauseRollAd = playerInstance.onPauseRollAdPods[0]; + playerInstance.createBoard(getNextOnPauseRollAd); + playerInstance.currentOnPauseRollAd = playerInstance.onPauseRollAdPods[0]; + delete playerInstance.onPauseRollAdPods[0]; + } + + return false; + }; + + slotFrame.appendChild(closeBtn); + + } + + const slotIframe = document.createElement('iframe'); + slotIframe.id = playerInstance.videoPlayerId + 'non_linear_vapid_slot_iframe'; + slotIframe.className = 'fluid_vpaid_nonlinear_slot_iframe'; + slotIframe.setAttribute('width', adWidth + 'px'); + slotIframe.setAttribute('height', adHeight + 'px'); + slotIframe.setAttribute('sandbox', 'allow-forms allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts'); + slotIframe.setAttribute('frameborder', '0'); + slotIframe.setAttribute('scrolling', 'no'); + slotIframe.setAttribute('marginwidth', '0'); + slotIframe.setAttribute('marginheight', '0'); + slotWrapper.appendChild(slotIframe); + + playerInstance.domRef.player.parentNode.insertBefore(slotWrapper, vpaidIframe.nextSibling); + + const slotElement = slotIframe.contentWindow.document.createElement('div'); + + slotIframe.contentWindow.document.body.appendChild(slotElement); + + playerInstance.vastOptions.slotIframe = slotIframe; + playerInstance.vastOptions.slotFrame = slotFrame; + + const environmentVars = { + slot: slotElement, + videoSlot: playerInstance.domRef.player, + videoSlotCanAutoPlay: true + }; + + playerInstance.debugMessage(ad); + + // calls this functions after ad unit is loaded in iframe + const ver = playerInstance.vpaidAdUnit.handshakeVersion(VPAID_VERSION); + const compare = playerInstance.compareVersion(VPAID_VERSION, ver); + if (compare === 1) { + //VPAID version of ad is lower than we need + ad.error = true; + playerInstance.playMainVideoWhenVpaidFails(403); + return false; + } + + playerInstance.domRef.player.loop = false; + playerInstance.domRef.player.removeAttribute('controls'); //Remove the default Controls + + playerInstance.vpaidCallbackListenersAttach(); + const mode = (playerInstance.fullscreenMode ? 'fullscreen' : 'normal'); + playerInstance.vpaidAdUnit.initAd(adWidth, adHeight, mode, 3000, creativeData, environmentVars); + + playerInstance.toggleLoader(false); + ad.played = true; + playerInstance.adFinished = false; + }; + + playerInstance.loadVpaid(ad, ad.staticResource); + + playerInstance.debugMessage('create non linear vpaid'); + }; + + // ADS + playerInstance.createNonLinearBoard = (ad) => { + ad.played = true; + const board = document.createElement('div'); + const vAlign = (playerInstance.rollsById[ad.rollListId].vAlign) ? playerInstance.rollsById[ad.rollListId].vAlign : playerInstance.nonLinearVerticalAlign; + + const creative = new Image(); + creative.src = ad.staticResource; + creative.id = 'fluid_nonLinear_imgCreative_' + ad.id + '_' + playerInstance.videoPlayerId; + + creative.onerror = function () { + playerInstance.rollsById[ad.rollListId].error = true; + playerInstance.announceError(500); + }; + + creative.onload = function () { + const playerWidth = playerInstance.domRef.player.clientWidth; + let origWidth; + let origHeight; + let newBannerWidth; + let newBannerHeight; + + //Set banner size based on the below priority + // 1. adList -> roll -> size + // 2. VAST XML width/height attriubute (VAST 3.) + // 3. VAST XML static resource dimension + if (typeof playerInstance.rollsById[ad.rollListId].size !== 'undefined') { + origWidth = playerInstance.rollsById[ad.rollListId].size.split('x')[0]; + origHeight = playerInstance.rollsById[ad.rollListId].size.split('x')[1]; + } else if (ad.dimension.width && ad.dimension.height) { + origWidth = ad.dimension.width; + origHeight = ad.dimension.height; + } else { + origWidth = creative.width; + origHeight = creative.height; + } + + if (origWidth > playerWidth) { + newBannerWidth = playerWidth - 5; + newBannerHeight = origHeight * newBannerWidth / origWidth; + } else { + newBannerWidth = origWidth; + newBannerHeight = origHeight; + } + + if (playerInstance.rollsById[ad.rollListId].roll !== 'onPauseRoll') { + //Show the board only if media loaded + const nonLinear = playerInstance.domRef.wrapper.querySelector('#fluid_nonLinear_' + ad.id); + + if (nonLinear) { + nonLinear.style.display = '' + } + } + + const img = playerInstance.domRef.wrapper.querySelector('#' + creative.id); + img.width = newBannerWidth; + img.height = newBannerHeight; + + playerInstance.trackSingleEvent('impression'); + }; + + board.id = 'fluid_nonLinear_' + ad.id; + board.className = 'fluid_nonLinear_' + vAlign; + board.className += ' fluid_nonLinear_ad'; + board.innerHTML = creative.outerHTML; + board.style.display = 'none'; + + //Bind the Onclick event + board.onclick = function () { + if (typeof ad.clickthroughUrl !== 'undefined') { + window.open(ad.clickthroughUrl); + } + + //Tracking the NonLinearClickTracking events + if (typeof ad.clicktracking !== 'undefined') { + playerInstance.callUris([ad.clicktracking]); + } + }; + + if (typeof ad.clickthroughUrl !== 'undefined') { + board.style.cursor = 'pointer'; + } + + const closeBtn = document.createElement('div'); + closeBtn.className = 'close_button'; + closeBtn.innerHTML = ''; + closeBtn.title = playerInstance.displayOptions.layoutControls.closeButtonCaption; + const tempRollListId = ad.rollListId; + closeBtn.onclick = function (event) { + this.parentElement.remove(); + if (typeof event.stopImmediatePropagation !== 'undefined') { + event.stopImmediatePropagation(); + } + playerInstance.adFinished = true; + clearInterval(playerInstance.nonLinearTracking); + + //if any other onPauseRoll then render it + if (playerInstance.rollsById[tempRollListId].roll === 'onPauseRoll' && playerInstance.onPauseRollAdPods[0]) { + const getNextOnPauseRollAd = playerInstance.onPauseRollAdPods[0]; + playerInstance.createBoard(getNextOnPauseRollAd); + playerInstance.currentOnPauseRollAd = playerInstance.onPauseRollAdPods[0]; + delete playerInstance.onPauseRollAdPods[0]; + } + + return false; + }; + + board.appendChild(closeBtn); + playerInstance.domRef.player.parentNode.insertBefore(board, playerInstance.domRef.player.nextSibling); + }; + + // ADS + /** + * Adds a nonLinear static Image banner + * + * currently only image/gif, image/jpeg, image/png supported + */ + playerInstance.createBoard = (ad) => { + // create nonLinear Vpaid + // create nonLinear regular + if (ad.vpaid) { + playerInstance.hardStopVpaidAd(''); + playerInstance.createVpaidNonLinearBoard(ad); + } else { + if ( + typeof ad.staticResource === 'undefined' || + playerInstance.supportedStaticTypes.indexOf(ad.creativeType) === -1 + ) { + // Couldn’t find NonLinear resource with supported type. + ad.error = true; + + if (!playerInstance.vastOptions || typeof playerInstance.vastOptions.errorUrl === 'undefined') { + playerInstance.announceLocalError(503); + } else { + playerInstance.announceError(503); + } + + return; + } + playerInstance.createNonLinearBoard(ad); + } + }; + + playerInstance.closeNonLinear = (adId) => { + const element = playerInstance.domRef.wrapper.querySelector('#fluid_nonLinear_' + adId + ', #fluid_vpaidNonLinear_' + adId); + if (element) { + element.remove(); + } + }; + + playerInstance.rollGroupContainsLinear = (groupedRolls) => { + let found = false; + for (let i = 0; i < groupedRolls.length; i++) { + if (playerInstance.rollsById[groupedRolls[i].id].adType && playerInstance.rollsById[groupedRolls[i].id].adType === 'linear') { + found = true; + break; + } + } + return found; + }; + playerInstance.rollGroupContainsNonlinear = (groupedRolls) => { + let found = false; + for (let i = 0; i < groupedRolls.length; i++) { + if (playerInstance.rollsById[groupedRolls[i].id].adType.toLowerCase() === 'nonlinear') { + found = true; + break; + } + } + return found; + }; + + playerInstance.preRollFail = () => { + const preRollsLength = playerInstance.preRollAdPodsLength; + + playerInstance.preRollVastResolved++; + + if (playerInstance.preRollVastResolved === preRollsLength) { + playerInstance.preRollAdsPlay(); + } + }; + + playerInstance.preRollSuccess = () => { + const preRollsLength = playerInstance.preRollAdPodsLength; + + playerInstance.preRollVastResolved++; + + if (playerInstance.preRollVastResolved === preRollsLength) { + playerInstance.preRollAdsPlay(); + } + }; + + playerInstance.preRollAdsPlay = () => { + const time = 0; + const rollListIds = playerInstance.preRollAdPods; + const adsByType = { + linear: [], + nonLinear: [] + }; + + playerInstance.firstPlayLaunched = true; + + for (let index = 0; index < rollListIds.length; index++) { + playerInstance.rollsById[rollListIds[index]].ads.forEach(ad => { + if (ad.played === true) { + return; + } + + if (ad.adType === 'linear') { + adsByType.linear.push(ad); + } + + if (ad.adType === 'nonLinear') { + adsByType.nonLinear.push(ad); + playerInstance.scheduleTask({time: time, playRoll: 'midRoll', rollListId: ad.rollListId }); + } + }); + + } + + if (adsByType.linear.length > 0) { + playerInstance.toggleLoader(false); + playerInstance.playRoll(adsByType.linear); + } else { + playerInstance.playMainVideoWhenVastFails(900); + } + + }; + + playerInstance.preRoll = (event) => { + const vastObj = event.vastObj; + playerInstance.domRef.player.removeEventListener(event.type, playerInstance.preRoll); + + const rollListIds = []; + rollListIds[0] = event.type.replace('adId_', ''); + const time = 0; + + if (playerInstance.rollsById[rollListIds[0]].played === true) { + return; + } + + playerInstance.preRollAdPods.push(rollListIds[0]); + + playerInstance.preRollSuccess(vastObj); + }; + + playerInstance.createAdMarker = (adListId, time) => { + const markersHolder = playerInstance.domRef.wrapper.querySelector('.fluid_controls_ad_markers_holder'); + const adMarker = document.createElement('div'); + adMarker.className = 'fluid_controls_ad_marker fluid_controls_ad_marker_' + adListId; + adMarker.dataset.adListId = adListId; + adMarker.style.left = (time / playerInstance.mainVideoDuration * 100) + '%'; + if (playerInstance.isCurrentlyPlayingAd) { + adMarker.style.display = 'none'; + } + markersHolder.appendChild(adMarker); + }; + + playerInstance.hideAdMarker = (adListId) => { + const element = playerInstance.domRef.wrapper.querySelector('fluid_controls_ad_marker_' + adListId); + if (element) { + element.style.display = 'none'; + } + }; + + playerInstance.showAdMarkers = () => { + const markersHolder = playerInstance.domRef.wrapper.querySelector('.fluid_controls_ad_markers_holder'); + const adMarkers = markersHolder.getElementsByClassName('fluid_controls_ad_marker'); + for (let i = 0; i < adMarkers.length; ++i) { + const item = adMarkers[i]; + const rollListId = item.dataset.adListId; + if (playerInstance.rollsById[rollListId].played === false) { + item.style.display = ''; + } + } + }; + + playerInstance.hideAdMarkers = () => { + const markersHolder = playerInstance.domRef.wrapper.querySelector('.fluid_controls_ad_markers_holder'); + const adMarkers = markersHolder.getElementsByClassName('fluid_controls_ad_marker'); + for (let i = 0; i < adMarkers.length; ++i) { + const item = adMarkers[i]; + item.style.display = 'none'; + } + }; + + playerInstance.midRoll = (event) => { + playerInstance.domRef.player.removeEventListener(event.type, playerInstance.midRoll); + + const rollListId = event.type.replace('adId_', ''); + if (playerInstance.rollsById[rollListId].played === true) { + return; + } + + let time = playerInstance.rollsById[rollListId].timer; + + if (typeof time == 'string' && time.indexOf("%") !== -1) { + time = time.replace('%', ''); + time = Math.floor(playerInstance.mainVideoDuration / 100 * time); + } + + if (playerInstance.displayOptions.vastOptions.showProgressbarMarkers && + playerInstance.rollsById[rollListId].adType === "nonLinear") { + playerInstance.createAdMarker(rollListId, time); + } + + playerInstance.scheduleTask({ + time: time, + playRoll: 'midRoll', + rollListId + }); + }; + + playerInstance.postRoll = (event) => { + playerInstance.domRef.player.removeEventListener(event.type, playerInstance.postRoll); + const rollListId = event.type.replace('adId_', ''); + + playerInstance.scheduleTask({ + time: Math.floor(playerInstance.mainVideoDuration), + playRoll: 'postRoll', + rollListId + }); + }; + + playerInstance.onPauseRoll = (event) => { + playerInstance.domRef.player.removeEventListener(event.type, playerInstance.onPauseRoll); + const rollListId = event.type.replace('adId_', ''); + + playerInstance.rollsById[rollListId].ads.forEach(ad => { + if (ad.adType === 'nonLinear') { + if (playerInstance.rollsById[ad.rollListId].error === true || ad.error === true) { + playerInstance.announceLocalError(101); + return; + } + + const nonLinearAdExists = playerInstance.domRef.wrapper.getElementsByClassName('fluid_nonLinear_ad')[0]; + if (!nonLinearAdExists) { + playerInstance.createBoard(ad); + playerInstance.currentOnPauseRollAd = rollListId; + let onPauseAd = ''; + for (const child of playerInstance.domRef.wrapper.children) { + if (child.id === 'fluid_nonLinear_' + rollListId) { + onPauseAd = child; + } + } + if (onPauseAd) { + onPauseAd.style.display = 'none'; + } + } else { + playerInstance.onPauseRollAdPods.push(rollListId); + } + + } + }); + }; + + /** + * Check if player has a valid nonLinear onPause Ad + */ + playerInstance.hasValidOnPauseAd = () => { + // TODO should be only one. Add validator to allow only one onPause roll + const onPauseAd = playerInstance.findRoll('onPauseRoll'); + + return ( + onPauseAd.length !== 0 && + playerInstance.rollsById[onPauseAd[0]] && + playerInstance.rollsById[onPauseAd[0]].error === false && + playerInstance.rollsById[onPauseAd[0]].ads.length && + playerInstance.rollsById[onPauseAd[0]].ads[0].error !== true + ); + }; + + /** + * Hide/show nonLinear onPause Ad + */ + playerInstance.toggleOnPauseAd = () => { + playerInstance.toggleLoader(false); + if (playerInstance.hasValidOnPauseAd() && !playerInstance.isCurrentlyPlayingAd) { + const onPauseRoll = playerInstance.findRoll('onPauseRoll'); + const ad = playerInstance.rollsById[onPauseRoll].ads[0]; + + playerInstance.vastOptions = ad; + let onPauseAd = ''; + for (const child of playerInstance.domRef.wrapper.children) { + if (child.id === 'fluid_nonLinear_' + ad.id) { + onPauseAd = child; + } + } + + if (onPauseAd && playerInstance.domRef.player.paused) { + setTimeout(function () { + onPauseAd.style.display = 'flex'; + ad.played = false; + playerInstance.trackingOnPauseNonLinearAd(ad, 'start'); + }, 500); + } else if (onPauseAd && !playerInstance.domRef.player.paused) { + onPauseAd.style.display = 'none'; + playerInstance.adFinished = true; + playerInstance.trackingOnPauseNonLinearAd(ad, 'complete'); + } + } + }; + + /** + * Helper function for tracking onPause Ads + */ + playerInstance.trackingOnPauseNonLinearAd = (ad, status) => { + if (playerInstance.rollsById[ad.rollListId].error === true || ad.error === true) { + playerInstance.announceLocalError(101); + return; + } + + playerInstance.vastOptions = ad; + playerInstance.trackSingleEvent(status); + }; + + playerInstance.getLinearAdsFromKeyTime = (keyTimeLinearObj) => { + const adListIds = []; + + for (let i = 0; i < keyTimeLinearObj.length; i++) { + if (playerInstance.rollsById[keyTimeLinearObj[i].adListId].played === false) { + adListIds.push(keyTimeLinearObj[i].adListId); + } + } + + return adListIds; + }; + + /** + * Handle scheduled tasks for a given key time + * + * @param keyTime key time in seconds + */ + playerInstance.adKeytimePlay = (keyTime) => { + if (!playerInstance.timerPool[keyTime] || playerInstance.isCurrentlyPlayingAd) { + return; + } + + const timerPoolKeytimeCloseStaticAdsLength = playerInstance.timerPool[keyTime]['closeStaticAd'].length; + const timerPoolKeytimeLinearAdsLength = playerInstance.timerPool[keyTime]['linear'].length; + const timerPoolKeytimeNonlinearAdsLength = playerInstance.timerPool[keyTime]['nonLinear'].length; + const timerPoolKeytimeLoadVastLength = playerInstance.timerPool[keyTime]['loadVast'].length; + + // remove the item from keytime if no ads to play + if ([ + timerPoolKeytimeCloseStaticAdsLength, + timerPoolKeytimeLinearAdsLength, + timerPoolKeytimeNonlinearAdsLength, + timerPoolKeytimeLoadVastLength + ].every(timerPoolLength => timerPoolLength === 0)) { + delete playerInstance.timerPool[keyTime]; + return; + } + + // Task: close nonLinear ads + if (timerPoolKeytimeCloseStaticAdsLength > 0) { + for (let index = 0; index < timerPoolKeytimeCloseStaticAdsLength; index++) { + const adToClose = playerInstance.timerPool[keyTime]['closeStaticAd'][index]; + if (adToClose.played === true) { + playerInstance.completeNonLinearStatic(adToClose); + } + } + + // empty closeStaticAd from the timerpool after closing + playerInstance.timerPool[keyTime]['closeStaticAd'] = []; + } + + // Task: play linear ads + if (timerPoolKeytimeLinearAdsLength > 0) { + if (playerInstance.timerPool[keyTime]['linear'].length > 0) { + playerInstance.playRoll(playerInstance.timerPool[keyTime]['linear']); + + // empty the linear ads from the timerpool after played + playerInstance.timerPool[keyTime]['linear'] = []; + + // return after starting video ad, so non-linear will not overlap + return; + } + } + + // Task: play nonLinear ads + if (timerPoolKeytimeNonlinearAdsLength > 0) { + for (let index = 0; index < timerPoolKeytimeNonlinearAdsLength; index++) { + const ad = playerInstance.timerPool[keyTime]['nonLinear'][index]; + const rollListId = ad.rollListId; + const vastOptions = playerInstance.adPool[rollListId]; + + // we are not supporting nonLinear ads in cardBoard mode + if (ad.played === false && !playerInstance.displayOptions.layoutControls.showCardBoardView) { + playerInstance.createNonLinearStatic(ad); + if (playerInstance.displayOptions.vastOptions.showProgressbarMarkers) { + playerInstance.hideAdMarker(rollListId); + } + + // delete nonLinear after playing + playerInstance.timerPool[keyTime]['nonLinear'].splice(index, 1); + + // return after starting non-linear ad, so multiple non-linear will not overlap + // unplayed non-linear will appear if user seeks back to the time :) + return; + } + } + } + + // Task: Load VAST on demand + if (timerPoolKeytimeLoadVastLength > 0) { + playerInstance.timerPool[keyTime]['loadVast'].forEach((roll) => { + if (roll.roll === `postRoll` && roll.voidPostRollTasks) { + // As postRoll schedules more than one task to cover the last few seconds of the video, we need to + // prevent any other loadVast task from running for that postRoll + return; + } else if (roll.roll === `postRoll`) { + roll.voidPostRollTasks = true; + } + + playerInstance.debugMessage(`Handling on-demand VAST load for roll ${roll.id}`) + playerInstance.processVastWithRetries(roll); + }); + + playerInstance.timerPool[keyTime]['loadVast'] = []; + } + }; + + playerInstance.adTimer = () => { + if (!!playerInstance.isTimer) { + return; + } + + playerInstance.isTimer = !playerInstance.isTimer; + + playerInstance.timer = setInterval( + function () { + const keyTime = Math.floor(playerInstance.getCurrentTime()); + playerInstance.adKeytimePlay(keyTime) + }, 800); + playerInstance.destructors.push(() => clearInterval(playerInstance.timer)); + }; + + /** + * Schedule tasks that need to be run with the main video timer + * + * @param {{ time: number, rollListId: any, loadVast: any }} task + */ + playerInstance.scheduleTask = (task) => { + if (task.time > playerInstance.mainVideoDuration || task.time < 0 || Number.isNaN(task.time)) { + console.warn(`Scheduled task has invalid time`, task.time, '. Check your configuration.'); + return; + } + + playerInstance.debugMessage(`Scheduling task`, task); + + if (!playerInstance.timerPool.hasOwnProperty(task.time)) { + playerInstance.timerPool[task.time] = {linear: [], nonLinear: [], closeStaticAd: [], loadVast: []}; + } + + // Handle AD rendering + if (task.rollListId) { + const roll = playerInstance.rollsById[task.rollListId]; + + roll.ads + .filter(({ adType }) => { + if (task.time === 0) { // Only non-linear should be scheduled on "preRoll" + return adType !== 'linear'; + } + + return true; + }) + .forEach(ad => { + if (task.hasOwnProperty('playRoll') && ad.adType === 'linear') { + playerInstance.timerPool[task.time]['linear'].push(ad); + } else if (task.hasOwnProperty('playRoll') && ad.adType === 'nonLinear') { + playerInstance.timerPool[task.time]['nonLinear'].push(ad); + } else if (task.hasOwnProperty('closeStaticAd')) { + playerInstance.timerPool[task.time]['closeStaticAd'].push(ad); + } + }); + } + + // Handle Loading VAST on demand + if (task.loadVast) { + playerInstance.timerPool[task.time]['loadVast'].push(task.roll) + } + }; + + /** + * Adds on demand rolls (midRoll, postRoll) to schedule + */ + playerInstance.scheduleOnDemandRolls = () => { + const midRollListIds = playerInstance.findRoll(`midRoll`) || []; + const postRollListIds = playerInstance.findRoll(`postRoll`) || []; + + [...midRollListIds, ...postRollListIds] + .map(rollListId => playerInstance.rollsById[rollListId]) + .filter(rollAd => rollAd.vastLoaded !== true && rollAd.error !== true) + .forEach(rollAd => { + // Request will have the vastTimeout time to load + if (rollAd.roll === `midRoll`) { + if (typeof rollAd.timer === 'string') { + // This can result in NaN, in that case the midRoll will simply not happen (user configuration error) + rollAd.timer = Math.floor(playerInstance.mainVideoDuration / 100 * rollAd.timer.replace('%', '')); + playerInstance.debugMessage(`Replaced midRoll from percentage to timer value ${rollAd.timer} seconds`); + } + + const time = rollAd.timer - (playerInstance.displayOptions.vastOptions.vastTimeout / 1000); + + // Handles cases where the midRoll should be loaded now, skipping the task scheduler + if (time <= Number(playerInstance.getCurrentTime())) { + playerInstance.debugMessage(`Loading Mid Roll VAST immediately as it needs to be played soon`); + playerInstance.processVastWithRetries(rollAd); + } else { + playerInstance.scheduleTask({ loadVast: true, time, roll: rollAd }) + } + } else { + const backwardScheduleTime = parseInt(playerInstance.mainVideoDuration); + const scheduleTimeAmount = (playerInstance.displayOptions.vastOptions.vastTimeout / 1000); + + // Used to prevent loading more than one of the tasks bellow + rollAd.voidPostRollTasks = false; + + for (let i = 1; i <= scheduleTimeAmount; i++) { + // Sets tasks for the last N seconds based on vastTimeout + playerInstance.scheduleTask({ loadVast: true, time: backwardScheduleTime - i, roll: rollAd }); + } + + } + }); + } + + // ADS + playerInstance.switchToMainVideo = () => { + playerInstance.debugMessage('starting main video'); + + playerInstance.domRef.player.src = playerInstance.originalSrc; + + playerInstance.currentMediaSourceType = 'source'; + + playerInstance.initialiseStreamers(); + + const newCurrentTime = (typeof playerInstance.domRef.player.mainVideoCurrentTime !== 'undefined') + ? Math.floor(playerInstance.domRef.player.mainVideoCurrentTime) : 0; + + if (playerInstance.domRef.player.hasOwnProperty('currentTime')) { + playerInstance.domRef.player.currentTime = newCurrentTime; + } + + if (playerInstance.displayOptions.layoutControls.loop) { + playerInstance.domRef.player.loop = true; + } + + playerInstance.setCurrentTimeAndPlay(newCurrentTime, playerInstance.autoplayAfterAd); + + playerInstance.isCurrentlyPlayingAd = false; + + playerInstance.deleteVastAdElements(); + + playerInstance.adFinished = true; + playerInstance.displayOptions.vastOptions.vastAdvanced.vastVideoEndedCallback(); + playerInstance.vastOptions = null; + + playerInstance.setBuffering(); + const progressbarContainer = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container'); + + if (progressbarContainer !== null) { + const backgroundColor = (playerInstance.displayOptions.layoutControls.primaryColor) ? playerInstance.displayOptions.layoutControls.primaryColor : "white"; + + const currentProgressBar = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_controls_currentprogress'); + + for (let i = 0; i < currentProgressBar.length; i++) { + currentProgressBar[i].style.backgroundColor = backgroundColor; + } + } + + playerInstance.domRef.player.removeEventListener('ended', playerInstance.onVastAdEnded); + + if (playerInstance.displayOptions.vastOptions.showProgressbarMarkers) { + playerInstance.showAdMarkers(); + } + + if (playerInstance.hasTitle()) { + const title = playerInstance.domRef.wrapper.querySelector('.fp_title'); + title.style.display = 'inline'; + } + }; + + // ADS + playerInstance.getNextAdPod = () => { + if (playerInstance.temporaryAdPods.length > 0) { + return playerInstance.temporaryAdPods.shift(); + } + + return null; + }; + + // ADS + playerInstance.checkForNextAd = () => { + const availableNextAdID = playerInstance.getNextAdPod(); + if (availableNextAdID === null) { + playerInstance.switchToMainVideo(); + playerInstance.vastOptions = null; + playerInstance.adFinished = true; + } else { + playerInstance.domRef.player.removeEventListener('ended', playerInstance.onVastAdEnded); + playerInstance.isCurrentlyPlayingAd = false; + playerInstance.vastOptions = null; + playerInstance.adFinished = true; + playerInstance.renderLinearAd(availableNextAdID, false); // passing false so it doesn't backup the Ad playbacktime as video playback time + } + }; + + + /** + * Adds a Skip Button + */ + playerInstance.addSkipButton = () => { + // TODO: ahh yes, the DIVbutton... + const divSkipButton = document.createElement('div'); + divSkipButton.className = 'skip_button skip_button_disabled'; + if (playerInstance.vastOptions.skipoffset > 0) { + divSkipButton.innerHTML = playerInstance.displayOptions.vastOptions.skipButtonCaption.replace('[seconds]', playerInstance.vastOptions.skipoffset); + } + + playerInstance.domRef.wrapper.appendChild(divSkipButton); + + if (playerInstance.vastOptions.skipoffset === 0) { + playerInstance.decreaseSkipOffset(); + } + + playerInstance.domRef.player.addEventListener('timeupdate', playerInstance.decreaseSkipOffset, false); + }; + + /** + * Ad Countdown + */ + playerInstance.addAdCountdown = () => { + if ((playerInstance.isCurrentlyPlayingAd && playerInstance.hlsPlayer) || playerInstance.currentVideoDuration === Infinity) { + return; // Shouldn't show countdown if ad is a video live stream + } + + const videoWrapper = playerInstance.domRef.wrapper; + const divAdCountdown = document.createElement('div'); + + // Create element + const adCountdown = playerInstance.pad(parseInt(playerInstance.currentVideoDuration / 60)) + ':' + playerInstance.pad(parseInt(playerInstance.currentVideoDuration % 60)); + const durationText = parseInt(adCountdown); + divAdCountdown.className = 'ad_countdown'; + divAdCountdown.innerHTML = "Ad - " + durationText; + + videoWrapper.appendChild(divAdCountdown); + + playerInstance.domRef.player.addEventListener('timeupdate', playerInstance.decreaseAdCountdown, false); + videoWrapper.addEventListener('mouseover', function () { + divAdCountdown.style.display = 'none'; + }, false); + }; + + playerInstance.decreaseAdCountdown = function decreaseAdCountdown() { + const sec = parseInt(playerInstance.currentVideoDuration) - parseInt(playerInstance.domRef.player.currentTime); + const btn = playerInstance.domRef.wrapper.querySelector('.ad_countdown'); + + if (btn && isNaN(sec)) { + btn.parentNode.removeChild(btn); + return; + } + + if (btn) { + btn.innerHTML = "Ad - " + playerInstance.pad(parseInt(sec / 60)) + ':' + playerInstance.pad(parseInt(sec % 60)); + } else { + playerInstance.domRef.player.removeEventListener('timeupdate', playerInstance.decreaseAdCountdown); + } + }; + + playerInstance.removeAdCountdown = () => { + const btn = playerInstance.domRef.wrapper.querySelector('.ad_countdown'); + if (btn) { + btn.parentElement.removeChild(btn); + } + }; + + playerInstance.toggleAdCountdown = (showing) => { + const btn = playerInstance.domRef.wrapper.querySelector('.ad_countdown'); + if (btn) { + if (showing) { + btn.style.display = 'inline-block'; + } else { + btn.style.display = 'none'; + } + } + }; + + playerInstance.addAdPlayingText = (textToShow) => { + const adPlayingDiv = document.createElement('div'); + + if (playerInstance.displayOptions.layoutControls.primaryColor) { + adPlayingDiv.style.backgroundColor = playerInstance.displayOptions.layoutControls.primaryColor; + adPlayingDiv.style.opacity = 1; + } + + adPlayingDiv.className = 'fluid_ad_playing'; + adPlayingDiv.innerText = textToShow; + + playerInstance.domRef.wrapper.appendChild(adPlayingDiv); + }; + + playerInstance.positionTextElements = (adListData) => { + const allowedPosition = ['top left', 'top right', 'bottom left', 'bottom right']; + + const skipButton = playerInstance.domRef.wrapper.querySelector('.skip_button'); + const adPlayingDiv = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing'); + const ctaButton = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta'); + + let ctaButtonHeightWithSpacing = 0; + let adPlayingDivHeightWithSpacing = 0; + const pixelSpacing = 8; + let isBottom = false; + let skipButtonHeightWithSpacing = 0; + let positionsCTA = []; + + const defaultPositions = { + top: { + left: { h: 34, v: 34 }, + right: { h: 0, v: 34 }, + }, + bottom: { + left: { h: 34, v: 50 }, + right: { h: 0, v: 50 }, + } + }; + + if (skipButton !== null) { + skipButtonHeightWithSpacing = skipButton.offsetHeight + pixelSpacing; + + const wrapperElement = playerInstance.domRef.wrapper; + + if (wrapperElement.classList.contains('mobile')) { + defaultPositions.top = { + left: { h: 0, v: 8 }, + right: { h: 0, v: 8 }, + } + defaultPositions.bottom = { + left: { h: 0, v: 50 }, + right: { h: 0, v: 50 }, + } + } + } + + let CTATextPosition; + if (ctaButton !== null) { + CTATextPosition = playerInstance.rollsById[adListData.rollListId].adCTATextPosition ? + playerInstance.rollsById[adListData.rollListId].adCTATextPosition.toLowerCase() : + playerInstance.displayOptions.vastOptions.adCTATextPosition; + + if (allowedPosition.indexOf(CTATextPosition) === -1) { + console.log('[FP Error] Invalid position for CTAText. Reverting to "bottom right"'); + CTATextPosition = 'bottom right'; + } + + ctaButton.classList.add.apply(ctaButton.classList, CTATextPosition.split(' ')); + + positionsCTA = CTATextPosition.split(' '); + + isBottom = positionsCTA[0] === 'bottom'; + + ctaButton.style[positionsCTA[0]] = defaultPositions[positionsCTA[0]][positionsCTA[1]].v + 'px'; + ctaButton.style[positionsCTA[1]] = defaultPositions[positionsCTA[0]][positionsCTA[1]].h + 'px'; + + if (isBottom && positionsCTA[1] === 'right') { + ctaButton.style[positionsCTA[0]] = defaultPositions[positionsCTA[0]][positionsCTA[1]].v + skipButtonHeightWithSpacing + 'px'; + } + + ctaButtonHeightWithSpacing = ctaButton.offsetHeight + pixelSpacing + 'px'; + } + + let adPlayingDivPosition; + let positionsAdText; + if (adPlayingDiv !== null) { + adPlayingDivPosition = playerInstance.rollsById[adListData.rollListId].adTextPosition ? + playerInstance.rollsById[adListData.rollListId].adTextPosition.toLowerCase() : + playerInstance.displayOptions.vastOptions.adTextPosition; + + if (allowedPosition.indexOf(adPlayingDivPosition) === -1) { + console.log('[FP Error] Invalid position for adText. Reverting to "top left"'); + adPlayingDivPosition = 'top left'; + } + + positionsAdText = adPlayingDivPosition.split(' '); + adPlayingDiv.style[positionsAdText[0]] = defaultPositions[positionsAdText[0]][positionsAdText[1]].v + 'px'; + adPlayingDiv.style[positionsAdText[1]] = defaultPositions[positionsAdText[0]][positionsAdText[1]].h + 'px'; + adPlayingDivHeightWithSpacing = adPlayingDiv.offsetHeight + pixelSpacing + 'px'; + } + + if (ctaButtonHeightWithSpacing > 0 && adPlayingDivHeightWithSpacing > 0 && CTATextPosition === adPlayingDivPosition) { + if (isBottom) { + if (positionsCTA[1] === 'right') { + adPlayingDiv.style.bottom = defaultPositions[positionsAdText[0]][positionsAdText[1]].v + skipButtonHeightWithSpacing + ctaButtonHeightWithSpacing + 'px'; + } else { + adPlayingDiv.style.bottom = defaultPositions[positionsAdText[0]][positionsAdText[1]].v + ctaButtonHeightWithSpacing + 'px'; + } + } else { + ctaButton.style.top = defaultPositions[positionsCTA[0]][positionsCTA[1]].v + adPlayingDivHeightWithSpacing + 'px'; + } + } + }; + + playerInstance.removeAdPlayingText = () => { + const div = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing'); + if (!div) { + return; + } + div.parentElement.removeChild(div); + }; + + /** + * Adds CTA button from VAST, with fallback to IconClickTrough + * + * @param {string} landingPage + */ + playerInstance.addCTAButton = (landingPage) => { + if (playerInstance.vastOptions.titleCTA) { + const { text, link, tracking } = playerInstance.vastOptions.titleCTA; + return playerInstance.createAndAppendCTAButton(text, link, tracking); + } + + if (landingPage && typeof playerInstance.displayOptions.vastOptions.adCTAText === 'string') { + return playerInstance.createAndAppendCTAButton( + playerInstance.displayOptions.vastOptions.adCTAText, + landingPage, + playerInstance.vastOptions.clickthroughUrl + ); + } + }; + + /** + * Creates and append CTA button given the input parameters + * + * @param {string} adCTAText + * + * @param {string} displayUrl + * + * @param {string} trackingUrl + */ + playerInstance.createAndAppendCTAButton = (adCTAText, displayUrl, trackingUrl) => { + const ctaButton = document.createElement('div'); + ctaButton.className = 'fluid_ad_cta'; + + const link = document.createElement('span'); + let innerHTML = adCTAText; + + if (displayUrl) { + innerHTML += "
" + displayUrl + "" + } + + link.innerHTML = innerHTML; + + ctaButton.addEventListener('click', () => { + if (!playerInstance.domRef.player.paused) { + playerInstance.domRef.player.pause(); + } + + const win = window.open(trackingUrl, '_blank'); + win.focus(); + return true; + }, false); + + ctaButton.appendChild(link); + + playerInstance.domRef.wrapper.appendChild(ctaButton); + }; + + playerInstance.removeCTAButton = () => { + const btn = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta'); + if (!btn) { + return; + } + + btn.parentElement.removeChild(btn); + }; + + playerInstance.decreaseSkipOffset = () => { + if (typeof playerInstance.vastOptions === 'undefined' || playerInstance.vastOptions === null) { + return; + } + let sec = playerInstance.vastOptions.skipoffset - Math.floor(playerInstance.domRef.player.currentTime); + const btn = playerInstance.domRef.wrapper.querySelector('.skip_button'); + + if (!btn) { + playerInstance.domRef.player.removeEventListener('timeupdate', playerInstance.decreaseSkipOffset); + return; + } + + if (sec >= 1) { + //set the button label with the remaining seconds + btn.innerHTML = playerInstance.displayOptions.vastOptions.skipButtonCaption.replace('[seconds]', sec); + return; + } + + // TODO: refactored, but this is still terrible - remove all this and just make the button clickable... + const skipLink = document.createElement('a'); + skipLink.href = '#'; + skipLink.className = 'js-skipHref'; + skipLink.innerHTML = playerInstance.displayOptions.vastOptions.skipButtonClickCaption; + skipLink.onclick = (e) => { + e.preventDefault(); + e.stopPropagation(); + playerInstance.pressSkipButton(); + }; + + btn.innerHTML = ''; + btn.appendChild(skipLink); + + //removes the CSS class for a disabled button + btn.className = btn.className.replace(/\bskip_button_disabled\b/, ''); + + playerInstance.domRef.player.removeEventListener('timeupdate', playerInstance.decreaseSkipOffset); + }; + + playerInstance.pressSkipButton = () => { + playerInstance.removeSkipButton(); + playerInstance.removeAdPlayingText(); + playerInstance.removeCTAButton(); + + if (playerInstance.vastOptions.vpaid) { + // skip the linear vpaid ad + playerInstance.skipVpaidAd(); + return; + } + + // skip the regular linear vast + playerInstance.displayOptions.vastOptions.vastAdvanced.vastVideoSkippedCallback(); + const event = document.createEvent('Event'); + event.initEvent('ended', false, true); + playerInstance.domRef.player.dispatchEvent(event); + }; + + playerInstance.removeSkipButton = () => { + const btn = playerInstance.domRef.wrapper.querySelector('.skip_button'); + if (btn) { + btn.parentElement.removeChild(btn); + } + }; + + /** + * Makes the player open the ad URL on clicking + */ + playerInstance.addClickthroughLayer = () => { + const divWrapper = playerInstance.domRef.wrapper; + + const divClickThrough = document.createElement('div'); + divClickThrough.className = 'vast_clickthrough_layer'; + divClickThrough.setAttribute( + 'style', + 'position: absolute; cursor: pointer; top: 0; left: 0; width: ' + + playerInstance.domRef.player.offsetWidth + 'px; height: ' + + (playerInstance.domRef.player.offsetHeight) + 'px;' + ); + + divWrapper.appendChild(divClickThrough); + + //Bind the Onclick event + const openClickthrough = function () { + window.open(playerInstance.vastOptions.clickthroughUrl); + + //Tracking the Clickthorugh events + if (typeof playerInstance.vastOptions.clicktracking !== 'undefined') { + playerInstance.callUris(playerInstance.vastOptions.clicktracking); + } + }; + + const clickthroughLayer = playerInstance.domRef.wrapper.querySelector('.vast_clickthrough_layer'); + const isIos9orLower = (playerInstance.mobileInfo.device === 'iPhone') && (playerInstance.mobileInfo.userOsMajor !== false) && (playerInstance.mobileInfo.userOsMajor <= 9); + + clickthroughLayer.onclick = () => { + if (playerInstance.domRef.player.paused) { + //On Mobile Safari on iPhones with iOS 9 or lower open the clickthrough only once + if (isIos9orLower && !playerInstance.suppressClickthrough) { + openClickthrough(); + playerInstance.suppressClickthrough = true; + + } else { + playerInstance.domRef.player.play(); + } + + } else { + openClickthrough(); + playerInstance.domRef.player.pause(); + } + }; + }; + + /** + * Remove the Clickthrough layer + */ + playerInstance.removeClickthrough = () => { + const clickthroughLayer = playerInstance.domRef.wrapper.querySelector('.vast_clickthrough_layer'); + + if (clickthroughLayer) { + clickthroughLayer.parentNode.removeChild(clickthroughLayer); + } + }; +} + +export default adSupport diff --git a/client/fluid-player/src/modules/cardboard.js b/client/fluid-player/src/modules/cardboard.js new file mode 100644 index 0000000..a528c4e --- /dev/null +++ b/client/fluid-player/src/modules/cardboard.js @@ -0,0 +1,343 @@ +export default function (playerInstance, options) { + playerInstance.createCardboardJoystickButton = (identity) => { + const vrJoystickPanel = playerInstance.domRef.wrapper.querySelector('.fluid_vr_joystick_panel'); + const joystickButton = document.createElement('div'); + + joystickButton.className = 'fluid_vr_button fluid_vr_joystick_' + identity; + vrJoystickPanel.appendChild(joystickButton); + + return joystickButton; + }; + + playerInstance.cardboardRotateLeftRight = (param /* 0 - right, 1 - left */) => { + const go = playerInstance.vrROTATION_POSITION; + const back = -playerInstance.vrROTATION_POSITION; + const pos = param < 1 ? go : back; + const easing = {val: pos}; + const tween = new TWEEN.Tween(easing) + .to({val: 0}, playerInstance.vrROTATION_SPEED) + .easing(TWEEN.Easing.Quadratic.InOut) + .onUpdate(function () { + playerInstance.vrViewer.OrbitControls.rotateLeft(easing.val) + }).start(); + }; + + playerInstance.cardboardRotateUpDown = (param /* 0 - down, 1- up */) => { + const go = playerInstance.vrROTATION_POSITION; + const back = -playerInstance.vrROTATION_POSITION; + const pos = param < 1 ? go : back; + const easing = {val: pos}; + const tween = new TWEEN.Tween(easing) + .to({val: 0}, playerInstance.vrROTATION_SPEED) + .easing(TWEEN.Easing.Quadratic.InOut) + .onUpdate(function () { + playerInstance.vrViewer.OrbitControls.rotateUp(easing.val) + }).start(); + }; + + playerInstance.createCardboardJoystick = () => { + const vrContainer = playerInstance.domRef.wrapper.querySelector('.fluid_vr_container'); + + // Create a JoyStick and append to VR container + const vrJoystickPanel = document.createElement('div'); + vrJoystickPanel.className = 'fluid_vr_joystick_panel'; + vrContainer.appendChild(vrJoystickPanel); + + // Create Joystick buttons + const upButton = playerInstance.createCardboardJoystickButton('up'); + const leftButton = playerInstance.createCardboardJoystickButton('left'); + const rightButton = playerInstance.createCardboardJoystickButton('right'); + const downButton = playerInstance.createCardboardJoystickButton('down'); + const zoomDefaultButton = playerInstance.createCardboardJoystickButton('zoomdefault'); + const zoomInButton = playerInstance.createCardboardJoystickButton('zoomin'); + const zoomOutButton = playerInstance.createCardboardJoystickButton('zoomout'); + + // Camera movement buttons + upButton.addEventListener('click', function () { + //player.vrViewer.OrbitControls.rotateUp(-0.1); + playerInstance.cardboardRotateUpDown(1); + }); + + downButton.addEventListener('click', function () { + //player.vrViewer.OrbitControls.rotateUp(0.1); + playerInstance.cardboardRotateUpDown(0); + }); + + rightButton.addEventListener('click', function () { + //player.vrViewer.OrbitControls.rotateLeft(0.1); + playerInstance.cardboardRotateLeftRight(0); + }); + + leftButton.addEventListener('click', function () { + //player.vrViewer.OrbitControls.rotateLeft(-0.1); + playerInstance.cardboardRotateLeftRight(1); + }); + + zoomDefaultButton.addEventListener('click', function () { + playerInstance.vrViewer.camera.fov = 60; + playerInstance.vrViewer.camera.updateProjectionMatrix(); + }); + + // Camera Zoom buttons + zoomOutButton.addEventListener('click', function () { + playerInstance.vrViewer.camera.fov *= 1.1; + playerInstance.vrViewer.camera.updateProjectionMatrix(); + }); + + zoomInButton.addEventListener('click', function () { + playerInstance.vrViewer.camera.fov *= 0.9; + playerInstance.vrViewer.camera.updateProjectionMatrix(); + }); + + }; + + playerInstance.cardBoardResize = () => { + playerInstance.domRef.player.removeEventListener('theatreModeOn', handleWindowResize); + playerInstance.domRef.player.addEventListener('theatreModeOn', handleWindowResize); + + playerInstance.domRef.player.removeEventListener('theatreModeOff', handleWindowResize); + playerInstance.domRef.player.addEventListener('theatreModeOff', handleWindowResize); + }; + + function handleWindowResize() { + playerInstance.vrViewer.onWindowResize(); + } + + playerInstance.cardBoardSwitchToNormal = () => { + const vrJoystickPanel = playerInstance.domRef.wrapper.querySelector('.fluid_vr_joystick_panel'); + const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container') + const videoPlayerTag = playerInstance.domRef.player; + + playerInstance.vrViewer.enableEffect(PANOLENS.MODES.NORMAL); + playerInstance.vrViewer.onWindowResize(); + playerInstance.vrMode = false; + + // remove dual control bar + const newControlBar = videoPlayerTag.parentNode.getElementsByClassName('fluid_vr2_controls_container')[0]; + videoPlayerTag.parentNode.removeChild(newControlBar); + + if (playerInstance.displayOptions.layoutControls.showCardBoardJoystick && vrJoystickPanel) { + vrJoystickPanel.style.display = "block"; + } + controlBar.classList.remove("fluid_vr_controls_container"); + + // show volume control bar + const volumeContainer = playerInstance.domRef.wrapper.getElementById('.fluid_control_volume_container'); + volumeContainer.style.display = "block"; + + // show all ads overlays if any + const adCountDownTimerText = playerInstance.domRef.wrapper.querySelector('.ad_countdown'); + const ctaButton = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta'); + const addAdPlayingTextOverlay = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing'); + const skipBtn = playerInstance.domRef.wrapper.querySelector('.skip_button'); + + if (adCountDownTimerText) { + adCountDownTimerText.style.display = 'block'; + } + + if (ctaButton) { + ctaButton.style.display = 'block'; + } + + if (addAdPlayingTextOverlay) { + addAdPlayingTextOverlay.style.display = 'block'; + } + + if (skipBtn) { + skipBtn.style.display = 'block'; + } + }; + + playerInstance.cardBoardHideDefaultControls = () => { + const vrJoystickPanel = playerInstance.domRef.wrapper.querySelector('.fluid_vr_joystick_panel'); + const initialPlay = playerInstance.domRef.wrapper.querySelector('.fluid_initial_play'); + const volumeContainer = playerInstance.domRef.wrapper.querySelector('.fluid_control_volume_container'); + + // hide the joystick in VR mode + if (playerInstance.displayOptions.layoutControls.showCardBoardJoystick && vrJoystickPanel) { + vrJoystickPanel.style.display = "none"; + } + + // hide big play icon + if (initialPlay) { + playerInstance.domRef.wrapper.querySelector('.fluid_initial_play').style.display = "none"; + playerInstance.domRef.wrapper.querySelector('.fluid_initial_play_button_container').style.opacity = "1"; + } + + // hide volume control bar + volumeContainer.style.display = "none"; + + }; + + playerInstance.cardBoardCreateVRControls = () => { + const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container') + + // create and append dual control bar + const newControlBar = controlBar.cloneNode(true); + newControlBar.removeAttribute('id'); + newControlBar.querySelectorAll('*').forEach(function (node) { + node.removeAttribute('id'); + }); + + newControlBar.classList.add("fluid_vr2_controls_container"); + playerInstance.domRef.player.parentNode.insertBefore(newControlBar, playerInstance.domRef.player.nextSibling); + playerInstance.copyEvents(newControlBar); + }; + + playerInstance.cardBoardSwitchToVR = () => { + const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container') + + playerInstance.vrViewer.enableEffect(PANOLENS.MODES.CARDBOARD); + + playerInstance.vrViewer.onWindowResize(); + playerInstance.vrViewer.disableReticleControl(); + + playerInstance.vrMode = true; + + controlBar.classList.add("fluid_vr_controls_container"); + + playerInstance.cardBoardHideDefaultControls(); + playerInstance.cardBoardCreateVRControls(); + + // hide all ads overlays + const adCountDownTimerText = playerInstance.domRef.wrapper.querySelector('.ad_countdown'); + const ctaButton = playerInstance.domRef.wrapper.querySelector('.fluid_ad_cta'); + const addAdPlayingTextOverlay = playerInstance.domRef.wrapper.querySelector('.fluid_ad_playing'); + const skipBtn = playerInstance.domRef.wrapper.querySelector('.skip_button'); + + if (adCountDownTimerText) { + adCountDownTimerText.style.display = 'none'; + } + + if (ctaButton) { + ctaButton.style.display = 'none'; + } + + if (addAdPlayingTextOverlay) { + addAdPlayingTextOverlay.style.display = 'none'; + } + + if (skipBtn) { + skipBtn.style.display = 'none'; + } + + }; + + playerInstance.cardBoardMoveTimeInfo = () => { + const timePlaceholder = playerInstance.domRef.wrapper.querySelector('.fluid_control_duration'); + const controlBar = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container') + + timePlaceholder.classList.add("cardboard_time"); + controlBar.appendChild(timePlaceholder); + + // override the time display function for this instance + playerInstance.controlDurationUpdate = function () { + + const currentPlayTime = playerInstance.formatTime(playerInstance.domRef.player.currentTime); + const totalTime = playerInstance.formatTime(playerInstance.currentVideoDuration); + const timePlaceholder = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_control_duration'); + + let durationText = ''; + + if (playerInstance.isCurrentlyPlayingAd) { + durationText = "AD : " + currentPlayTime + ' / ' + totalTime; + + for (let i = 0; i < timePlaceholder.length; i++) { + timePlaceholder[i].classList.add("ad_timer_prefix"); + } + + } else { + durationText = currentPlayTime + ' / ' + totalTime; + + for (let i = 0; i < timePlaceholder.length; i++) { + timePlaceholder[i].classList.remove("ad_timer_prefix"); + } + } + + for (let i = 0; i < timePlaceholder.length; i++) { + timePlaceholder[i].innerHTML = durationText; + } + } + }; + + playerInstance.cardBoardAlterDefaultControls = () => { + playerInstance.cardBoardMoveTimeInfo(); + }; + + playerInstance.createCardboardView = () => { + // Create a container for 360degree + const vrContainer = document.createElement('div'); + vrContainer.className = 'fluid_vr_container'; + playerInstance.domRef.player.parentNode.insertBefore(vrContainer, playerInstance.domRef.player.nextSibling); + + // OverRide some conflicting functions from panolens + PANOLENS.VideoPanorama.prototype.pauseVideo = function () { + }; + PANOLENS.VideoPanorama.prototype.playVideo = function () { + }; + + playerInstance.vrPanorama = new PANOLENS.VideoPanorama('', { + videoElement: playerInstance.domRef.player, + autoplay: playerInstance.displayOptions.layoutControls.autoPlay, + loop: !!playerInstance.displayOptions.layoutControls.loop + }); + + playerInstance.vrViewer = new PANOLENS.Viewer({ + container: vrContainer, + controlBar: true, + controlButtons: [], + enableReticle: false + }); + playerInstance.vrViewer.add(playerInstance.vrPanorama); + + playerInstance.vrViewer.enableEffect(PANOLENS.MODES.NORMAL); + playerInstance.vrViewer.onWindowResize(); + + // if Mobile device then enable controls using gyroscope + if (playerInstance.getMobileOs().userOs === 'Android' || playerInstance.getMobileOs().userOs === 'iOS') { + playerInstance.vrViewer.enableControl(1); + } + + // Make Changes for default skin + playerInstance.cardBoardAlterDefaultControls(); + + // resize on toggle theater mode + playerInstance.cardBoardResize(); + + // Store initial camera position + playerInstance.vrViewer.initialCameraPosition = JSON.parse(JSON.stringify(playerInstance.vrViewer.camera.position)); + + if (playerInstance.displayOptions.layoutControls.showCardBoardJoystick) { + if (!(playerInstance.getMobileOs().userOs === 'Android' || playerInstance.getMobileOs().userOs === 'iOS')) { + playerInstance.createCardboardJoystick(); + } + // Disable zoom if showing joystick + playerInstance.vrViewer.OrbitControls.noZoom = true; + } + + playerInstance.trackEvent(playerInstance.domRef.player.parentNode, 'click', '.fluid_control_cardboard', function () { + if (playerInstance.vrMode) { + playerInstance.cardBoardSwitchToNormal(); + } else { + playerInstance.cardBoardSwitchToVR(); + } + }); + }; + + playerInstance.createCardboard = () => { + if (!playerInstance.displayOptions.layoutControls.showCardBoardView) { + return; + } + + playerInstance.domRef.wrapper.querySelector('.fluid_control_cardboard').style.display = 'inline-block'; + + if (!window.PANOLENS) { + import(/* webpackChunkName: "panolens" */ 'panolens').then((it) => { + window.PANOLENS = it; + playerInstance.createCardboardView(); + }); + } else { + playerInstance.createCardboardView(); + } + }; +} diff --git a/client/fluid-player/src/modules/miniplayer.js b/client/fluid-player/src/modules/miniplayer.js new file mode 100644 index 0000000..8c99c6f --- /dev/null +++ b/client/fluid-player/src/modules/miniplayer.js @@ -0,0 +1,385 @@ +export default function (playerInstance) { + // Module constants + const MINIMUM_WIDTH = 400; // Pixels + const MINIMUM_HEIGHT = 225; // Pixels + const MINIMUM_WIDTH_MOBILE = 40; // Percentage + const TOGGLE_BY_VISIBILITY_DETECTION_RATE = 1000 / 60; // ms + + const DISABLE_MINI_PLAYER_MOBILE_ANIMATION_CLAMP = 50; + const DISABLE_MINI_PLAYER_MOBILE_ANIMATION_DEADZONE = 5; + + const DESKTOP_ONLY_MEDIA_QUERY = '(max-width: 768px)'; + + const FLUID_PLAYER_WRAPPER_CLASS = 'fluid_mini_player_mode'; + const CLOSE_BUTTON_WRAPPER_CLASS = 'mini-player-close-button-wrapper'; + const CLOSE_BUTTON_CLASS = 'mini-player-close-button'; + const PLACEHOLDER_CLASS = 'fluidplayer-miniplayer-player-placeholder' + const DISABLE_MINI_PLAYER_MOBILE_CLASS = 'disable-mini-player-mobile'; + + const LINEAR_CLICKTHROUGH_SELECTOR = '.vast_clickthrough_layer'; + const NON_LINEAR_SELECTOR = '.fluid_nonLinear_ad img, .fluid_vpaid_nonlinear_slot_iframe'; + const VPAID_FRAME_SELECTOR = '.fluid_vpaidNonLinear_frame'; + + const MINI_PLAYER_TOGGLE_EVENT = 'miniPlayerToggle'; + + // Module variables + let originalWidth = null; + let originalHeight = null; + let originalNonLinearWidth = null + let originalNonLinearHeight = null; + let isSetup = false; + /** @type null | Element */ + let placeholderElement = null; + let isMobile = false; + /** @type boolean */ + let toggleByVisibilityControl = false; + + /** + * Toggles the MiniPlayer given that it's enabled. Resets all other display modes. + * + * @param {'on'|'off'} [forceToggle] + * @param {boolean} manualToggle + */ + function toggleMiniPlayer(forceToggle, manualToggle = false) { + playerInstance.debugMessage(`[MiniPlayer] Toggling MiniPlayer, forceToggle: ${forceToggle}`); + + const miniPlayerOptions = playerInstance.displayOptions.layoutControls.miniPlayer; + + if (!miniPlayerOptions.enabled) { + playerInstance.debugMessage(`[MiniPlayer] Prevent toggle MiniPlayer, it's currently disabled`); + return; + } + + if ((forceToggle === 'on' && playerInstance.miniPlayerToggledOn) || (forceToggle === 'off' && !playerInstance.miniPlayerToggledOn)) { + playerInstance.debugMessage(`[MiniPlayer] Can't force toggle Mini Player to it's same state`); + return; + } + + if (manualToggle) { + toggleScreenDetection(); + } + + if (window.matchMedia(DESKTOP_ONLY_MEDIA_QUERY).matches) { + isMobile = true; + } + + // Important as the player can be in full screen or theater mode + playerInstance.resetDisplayMode('miniPlayer'); + + if (!isSetup) { + // Setups JIT to avoid extra processing + setupMiniPlayer(); + } + + if (forceToggle === 'off' || playerInstance.miniPlayerToggledOn) { + toggleMiniPlayerOff(); + } else if (forceToggle === 'on' || !playerInstance.miniPlayerToggledOn) { + toggleMiniPlayerOn(miniPlayerOptions.width, miniPlayerOptions.height, miniPlayerOptions.widthMobile, miniPlayerOptions.position); + } + } + + /** + * Setups custom Mini Player DOM + */ + function setupMiniPlayer() { + const hasCloseButton = Boolean(playerInstance.domRef.player.parentNode.querySelector(`.${CLOSE_BUTTON_CLASS}`)); + + if (!hasCloseButton) { + const closeButtonWrapper = document.createElement('div'); + closeButtonWrapper.classList.add(CLOSE_BUTTON_WRAPPER_CLASS); + + const closeButton = document.createElement('span'); + closeButton.classList.add(CLOSE_BUTTON_CLASS); + closeButton.addEventListener('click', () => { + toggleMiniPlayer('off', true); + + if (!playerInstance.domRef.player.paused) { + playerInstance.playPauseToggle(); + } + }); + + closeButtonWrapper.appendChild(closeButton); + playerInstance.domRef.player.parentNode.append(closeButtonWrapper); + } + + if (isMobile) { + setupMobile(); + } + + isSetup = true; + } + + /** + * Toggles the MiniPlayer off and restores previous functionality to player + */ + function toggleMiniPlayerOff() { + const videoWrapper = playerInstance.domRef.wrapper; + + removePlayerPlaceholder(); + + videoWrapper.classList.remove(FLUID_PLAYER_WRAPPER_CLASS); + videoWrapper.style.width = `${originalWidth}px`; + videoWrapper.style.height = `${originalHeight}px`; + + originalWidth = null; + originalHeight = null; + + adaptNonLinearSize(); + adaptLinearSize(); + playerInstance.miniPlayerToggledOn = false; + emitToggleEvent(); + } + + /** + * Toggles the MiniPlayer on, stores the original size of the player. + * + * @param {number} width + * @param {number} height + * @param {number} mobileWidth + * @param {'top left'|'top right'|'bottom left'|'bottom right'} position + */ + function toggleMiniPlayerOn(width, height, mobileWidth, position) { + const videoWrapper = playerInstance.domRef.wrapper; + const targetWidth = width > MINIMUM_WIDTH ? width : MINIMUM_WIDTH; + const targetHeight = height > MINIMUM_HEIGHT ? height : MINIMUM_HEIGHT; + const targetMobileWidth = mobileWidth > MINIMUM_WIDTH_MOBILE ? mobileWidth : MINIMUM_WIDTH_MOBILE; + + originalWidth = extractSizeFromElement(videoWrapper, 'width', 'clientWidth'); + originalHeight = extractSizeFromElement(videoWrapper, 'height', 'clientHeight'); + + videoWrapper.classList.add( + FLUID_PLAYER_WRAPPER_CLASS, + `${FLUID_PLAYER_WRAPPER_CLASS}--${position.trim().replace(/\s/, '-')}` + ); + + if (!isMobile) { + videoWrapper.style.width = `${targetWidth}px`; + videoWrapper.style.height = `${targetHeight}px`; + } else { + videoWrapper.style.width = `${targetMobileWidth}vw`; + videoWrapper.style.height = `auto`; + videoWrapper.style.aspectRatio = `16 / 9`; + } + + createPlayerPlaceholder(originalWidth, originalHeight); + adaptNonLinearSize(targetWidth, targetHeight, targetMobileWidth); + adaptLinearSize(); + playerInstance.miniPlayerToggledOn = true; + emitToggleEvent(); + } + + /** + * Emits event to Fluid Player Event API + */ + function emitToggleEvent() { + playerInstance.domRef.player.dispatchEvent( + new CustomEvent(MINI_PLAYER_TOGGLE_EVENT, { detail: { isToggledOn: playerInstance.miniPlayerToggledOn } }) + ); + } + + /** + * Extracts size from an element checking multiple element properties + * + * @param {HTMLElement} element + * @param {'width'|'height'|null} styleProperty + * @param {'clientWidth'|'clientHeight'|'width'|'height'} htmlProperty + * @returns {number} + */ + function extractSizeFromElement(element, styleProperty, htmlProperty) { + if (styleProperty && element.style[styleProperty] && element.style[styleProperty].match('px')) { + return parseInt(element.style[styleProperty]); + } else { + return String(element[htmlProperty]).match('px') ? parseInt(element[htmlProperty]) : element[htmlProperty]; + } + } + + /** + * Adapts NonLinear size (if present) to fit MiniPlayer view + * + * @param {number} [width] + * @param {number} [height] + * @param {number} [mobileWidth] + */ + function adaptNonLinearSize(width, height, mobileWidth) { + /** @type HTMLImageElement|HTMLIFrameElement */ + const nonLinear = playerInstance.domRef.wrapper.querySelector(NON_LINEAR_SELECTOR); + /** @type HTMLElement */ + const vpaidFrame = playerInstance.domRef.wrapper.querySelector(VPAID_FRAME_SELECTOR); + + if (!nonLinear) return; + + if (isMobile) { + width = window.innerWidth * mobileWidth / 100; // Transforms vw to px + } + + const nonLinearWidth = extractSizeFromElement(nonLinear, null, 'width'); + const nonLinearHeight = extractSizeFromElement(nonLinear, null, 'height'); + + if (originalNonLinearWidth && originalNonLinearHeight) { + nonLinear.width = originalNonLinearWidth; + nonLinear.height = originalNonLinearHeight; + + if (vpaidFrame) { + vpaidFrame.style.width = `${originalNonLinearWidth}px`; + vpaidFrame.style.height = `${originalNonLinearHeight}px`; + } + + originalNonLinearWidth = originalNonLinearHeight = null; + } else if (nonLinearWidth > width || nonLinearHeight > height) { + const targetRatio = (width - (isMobile ? 4 : 32)) / nonLinearWidth; + + originalNonLinearWidth = nonLinearWidth; + originalNonLinearHeight = nonLinearHeight; + + nonLinear.width = Math.round(nonLinearWidth * targetRatio); + nonLinear.height = Math.round(nonLinearHeight * targetRatio); + + if (vpaidFrame) { + vpaidFrame.style.width = `${Math.round(nonLinearWidth * targetRatio)}px`; + vpaidFrame.style.height = `${Math.round(nonLinearHeight * targetRatio)}px`; + } + } + } + + /** + * Adapts Linear size (if present) to fit MiniPlayer view + */ + function adaptLinearSize() { + const clickTroughLayer = playerInstance.domRef.wrapper.querySelector(LINEAR_CLICKTHROUGH_SELECTOR); + + if (clickTroughLayer) { + clickTroughLayer.style.width = `${playerInstance.domRef.player.offsetWidth}px`; + clickTroughLayer.style.height = `${playerInstance.domRef.player.offsetHeight}px`; + } + } + + /** + * Setups mobile disable element + */ + function setupMobile() { + const disableMiniPlayerMobile = document.createElement('div'); + let animationAmount = 0; + let startTimestamp = 0; + let startScreenX = 0; + let hasTriggeredAnimation; + disableMiniPlayerMobile.classList.add(DISABLE_MINI_PLAYER_MOBILE_CLASS); + const closeButton = document.createElement('span'); + closeButton.classList.add(CLOSE_BUTTON_CLASS); + disableMiniPlayerMobile.appendChild(closeButton); + + disableMiniPlayerMobile.ontouchstart = event => { + hasTriggeredAnimation = false; + startTimestamp = event.timeStamp; + startScreenX = event.changedTouches[0].screenX; + event.preventDefault(); + } + + disableMiniPlayerMobile.ontouchmove = event => { + animationAmount = Math.min( + Math.max( + startScreenX - event.changedTouches[0].screenX, + DISABLE_MINI_PLAYER_MOBILE_ANIMATION_CLAMP * -1), + DISABLE_MINI_PLAYER_MOBILE_ANIMATION_CLAMP + ); + + if (Math.abs(animationAmount) > DISABLE_MINI_PLAYER_MOBILE_ANIMATION_DEADZONE) { + // Moves the element the same amount as the touch event moved + playerInstance.domRef.wrapper.style.transform = `translateX(${animationAmount * -1}px)`; + hasTriggeredAnimation = true; + } else { + playerInstance.domRef.wrapper.style.transform = `translateX(0px)` + } + } + + disableMiniPlayerMobile.ontouchend = event => { + if (Math.abs(animationAmount) > DISABLE_MINI_PLAYER_MOBILE_ANIMATION_DEADZONE) { + // Scroll X behaviour - Disable mini player and pauses video + toggleMiniPlayer('off', true); + + if (!playerInstance.domRef.player.paused) { + playerInstance.playPauseToggle(); + } + event.preventDefault(); + } else if (!hasTriggeredAnimation) { + // Tap behaviour - Disable mini player and moves screen to video + toggleMiniPlayer('off', true); + setTimeout(() => { + playerInstance.domRef.wrapper.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); + }) + } + + animationAmount = 0; + playerInstance.domRef.wrapper.style.transform = ``; + } + + // Fallback for when there is no touch event + disableMiniPlayerMobile.onmouseup = () => toggleMiniPlayer('off', true) + + playerInstance.domRef.wrapper.insertBefore(disableMiniPlayerMobile, playerInstance.domRef.player.nextSibling); + } + + /** + * Creates a placeholder element in place where the video player was + * + * @param {number} placeholderWidth + * @param {number} placeholderHeight + */ + function createPlayerPlaceholder(placeholderWidth, placeholderHeight) { + placeholderElement = document.createElement('div'); + placeholderElement.classList.add(PLACEHOLDER_CLASS); + placeholderElement.style.height = `${placeholderHeight}px`; + placeholderElement.style.width = `${placeholderWidth}px`; + placeholderElement.innerText = playerInstance.displayOptions.layoutControls.miniPlayer.placeholderText || ''; + placeholderElement.onclick = () => toggleMiniPlayer('off', true); + + playerInstance.domRef.wrapper.parentElement.insertBefore(placeholderElement, playerInstance.domRef.wrapper); + } + + /** + * Removes the placeholder that was in place where video player was + */ + function removePlayerPlaceholder() { + playerInstance.domRef.wrapper.parentElement.removeChild(placeholderElement); + placeholderElement = null; + } + + /** + * Toggles auto toggle for mini player + */ + function toggleScreenDetection() { + const autoToggle = playerInstance.displayOptions.layoutControls.miniPlayer.autoToggle; + + if (toggleByVisibilityControl || !autoToggle) { + document.removeEventListener('scroll', toggleMiniPlayerByVisibility); + return; + } + + toggleByVisibilityControl = true; + document.addEventListener('scroll', toggleMiniPlayerByVisibility, { passive: true }); + } + + /** + * Checks for player visibility and toggles mini player based on it + */ + const toggleMiniPlayerByVisibility = playerInstance.throttle(function toggleMiniPlayerByVisibility() { + if (playerInstance.domRef.player.paused) { + return; + } + + const isPlayerVisible = playerInstance.isElementVisible(playerInstance.domRef.player); + const isPlaceholderVisible = playerInstance.isElementVisible(playerInstance.domRef.wrapper.querySelector(`.${PLACEHOLDER_CLASS}`)); + + if (!isPlayerVisible && !playerInstance.miniPlayerToggledOn) { + toggleMiniPlayer('on'); + } else if (isPlaceholderVisible && playerInstance.miniPlayerToggledOn) { + toggleMiniPlayer('off'); + } + }, TOGGLE_BY_VISIBILITY_DETECTION_RATE); + + // Exposes public module functions + playerInstance.toggleMiniPlayer = toggleMiniPlayer; + playerInstance.toggleMiniPlayerScreenDetection = toggleScreenDetection; +} diff --git a/client/fluid-player/src/modules/streaming.js b/client/fluid-player/src/modules/streaming.js new file mode 100644 index 0000000..cdaa1cf --- /dev/null +++ b/client/fluid-player/src/modules/streaming.js @@ -0,0 +1,299 @@ + +// Prevent DASH.js from automatically attaching to video sources by default. +// Whoever thought this is a good idea?! +if (typeof window !== 'undefined' && !window.dashjs) { + window.dashjs = { + skipAutoCreate: true, + isDefaultSubject: true + }; +} + +export default function (playerInstance, options) { + playerInstance.initialiseStreamers = () => { + playerInstance.detachStreamers(); + switch (playerInstance.displayOptions.layoutControls.mediaType) { + case 'application/dash+xml': // MPEG-DASH + if (!playerInstance.dashScriptLoaded && (!window.dashjs || window.dashjs.isDefaultSubject)) { + playerInstance.dashScriptLoaded = true; + import(/* webpackChunkName: "dashjs" */ 'dashjs').then((it) => { + window.dashjs = it.default; + playerInstance.initialiseDash(); + }); + } else { + playerInstance.initialiseDash(); + } + break; + case 'application/x-mpegurl': // HLS + const { displayOptions, domRef } = playerInstance; + const { player } = domRef; + const { hls } = displayOptions; + + // Doesn't load hls.js if player can play it natively + if (player.canPlayType('application/x-mpegurl') && !hls.overrideNative) { + playerInstance.debugMessage('Native HLS support found, skipping hls.js'); + break; + } + + if (!playerInstance.hlsScriptLoaded && !window.Hls) { + playerInstance.hlsScriptLoaded = true; + import(/* webpackChunkName: "hlsjs" */ 'hls.js').then((it) => { + window.Hls = it.default; + playerInstance.initialiseHls(); + }); + } else { + playerInstance.initialiseHls(); + } + break; + } + }; + + playerInstance.initialiseDash = () => { + if (typeof (window.MediaSource || window.WebKitMediaSource) === 'function') { + // If false we want to override the autoPlay, as it comes from postRoll + const playVideo = !playerInstance.autoplayAfterAd + ? playerInstance.autoplayAfterAd + : playerInstance.displayOptions.layoutControls.autoPlay; + + const defaultOptions = { + 'debug': { + 'logLevel': typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true + ? dashjs.Debug.LOG_LEVEL_DEBUG + : dashjs.Debug.LOG_LEVEL_FATAL + } + }; + + const dashPlayer = dashjs.MediaPlayer().create(); + const options = playerInstance.displayOptions.modules.configureDash(defaultOptions); + + dashPlayer.updateSettings(options); + + playerInstance.displayOptions.modules.onBeforeInitDash(dashPlayer); + + dashPlayer.initialize(playerInstance.domRef.player, playerInstance.originalSrc, playVideo); + + dashPlayer.on('streamInitializing', () => { + playerInstance.toggleLoader(true); + }); + + dashPlayer.on('canPlay', () => { + playerInstance.toggleLoader(false); + }); + + dashPlayer.on('playbackPlaying', () => { + playerInstance.toggleLoader(false); + }); + + playerInstance.displayOptions.modules.onAfterInitDash(dashPlayer); + + playerInstance.dashPlayer = dashPlayer; + } else { + playerInstance.nextSource(); + console.log('[FP_WARNING] Media type not supported by this browser using DASH.js. (application/dash+xml)'); + } + }; + + playerInstance.initialiseHls = () => { + if (typeof Hls !== 'undefined' && Hls.isSupported()) { + playerInstance.debugMessage('Initializing hls.js'); + + const defaultOptions = { + debug: typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true, + startPosition: 0, + p2pConfig: { + logLevel: false, + }, + enableWebVTT: false, + enableCEA708Captions: false, + }; + + const options = playerInstance.displayOptions.modules.configureHls(defaultOptions); + const hls = new Hls(options); + playerInstance.displayOptions.modules.onBeforeInitHls(hls); + + hls.attachMedia(playerInstance.domRef.player); + hls.loadSource(playerInstance.originalSrc); + + playerInstance.displayOptions.modules.onAfterInitHls(hls); + + playerInstance.hlsPlayer = hls; + + if (!playerInstance.firstPlayLaunched && playerInstance.displayOptions.layoutControls.autoPlay) { + playerInstance.domRef.player.play(); + } + + playerInstance.createHLSVideoSourceSwitch(); + } else { + playerInstance.nextSource(); + console.log('[FP_WARNING] Media type not supported by this browser using HLS.js. (application/x-mpegURL)'); + } + }; + + playerInstance.createHLSVideoSourceSwitch = () => { + playerInstance.hlsPlayer.on(Hls.Events.MANIFEST_PARSED, function () { + try { + const levels = createHLSLevels(); + const sortedLevels = sortLevels(levels); + playerInstance.videoSources = sortedLevels; + + // <=2 because of the added auto function + if (sortedLevels.length <= 2) return; + + const sourceChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_video_source'); + + toggleSourceChangeButtonVisibility(sortedLevels, sourceChangeButton); + + const sourceChangeList = createSourceChangeList(sortedLevels); + attachSourceChangeList(sourceChangeButton, sourceChangeList); + + // Set initial level based on persisted quality or default to auto + setInitialLevel(sortedLevels); + } catch (err) { + console.error(err); + } + }); + }; + + function createHLSLevels() { + const HLSLevels = playerInstance.hlsPlayer.levels + .map((level, index) => ({ + id: index, + title: String(level.width), + isHD: level.videoRange === 'HDR', + bitrate: level.bitrate + })); + + const autoLevel = { + id: -1, + title: 'auto', + isHD: false, + bitrate: 0 + }; + + return [...HLSLevels, autoLevel]; + } + + function toggleSourceChangeButtonVisibility(levels, sourceChangeButton) { + if (levels.length > 1) { + sourceChangeButton.style.display = 'inline-block'; + } else { + sourceChangeButton.style.display = 'none'; + } + } + + function createSourceChangeList(levels) { + const sourceChangeList = document.createElement('div'); + sourceChangeList.className = 'fluid_video_sources_list'; + sourceChangeList.style.display = 'none'; + + levels.forEach(level => { + const sourceChangeDiv = createSourceChangeItem(level); + sourceChangeList.appendChild(sourceChangeDiv); + }); + + return sourceChangeList; + } + + function createSourceChangeItem(level) { + const sourceSelectedClass = getSourceSelectedClass(level); + const hdIndicator = level.isHD ? `` : ''; + + const sourceChangeDiv = document.createElement('div'); + sourceChangeDiv.className = `fluid_video_source_list_item js-source_${level.title}`; + sourceChangeDiv.innerHTML = `${level.title}${hdIndicator}`; + + sourceChangeDiv.addEventListener('click', event => onSourceChangeClick(event, level)); + + return sourceChangeDiv; + } + + function getSourceSelectedClass(level) { + const matchingLevels = playerInstance.videoSources.filter(source => source.title === playerInstance.fluidStorage.fluidQuality); + + // If there are multiple matching levels, use the first one + if (matchingLevels.length > 1) { + if (matchingLevels[0].id === level.id) { + return "source_selected"; + } + } else if (matchingLevels.length === 1) { + return matchingLevels[0].id === level.id ? "source_selected" : ""; + } + + // Fallback to auto selection if no persistent level exists + if (!matchingLevels.length && level.title === 'auto') { + return "source_selected"; + } + + return ""; + } + + function onSourceChangeClick(event, selectedLevel) { + event.stopPropagation(); + + setPlayerDimensions(); + + const videoChangedTo = event.currentTarget; + clearSourceSelectedIcons(); + + videoChangedTo.firstChild.classList.add('source_selected'); + + playerInstance.videoSources.forEach(source => { + if (source.title === videoChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, '')) { + playerInstance.hlsPlayer.currentLevel = selectedLevel.id; + playerInstance.fluidStorage.fluidQuality = selectedLevel.title; + } + }); + + playerInstance.openCloseVideoSourceSwitch(); + } + + // While changing source the player size can flash, we want to set the pixel dimensions then back to 100% afterwards + function setPlayerDimensions() { + playerInstance.domRef.player.style.width = `${playerInstance.domRef.player.clientWidth}px`; + playerInstance.domRef.player.style.height = `${playerInstance.domRef.player.clientHeight}px`; + } + + function clearSourceSelectedIcons() { + const sourceIcons = playerInstance.domRef.wrapper.getElementsByClassName('source_button_icon'); + Array.from(sourceIcons).forEach(icon => icon.classList.remove('source_selected')); + } + + function attachSourceChangeList(sourceChangeButton, sourceChangeList) { + sourceChangeButton.appendChild(sourceChangeList); + sourceChangeButton.removeEventListener('click', playerInstance.openCloseVideoSourceSwitch); + sourceChangeButton.addEventListener('click', playerInstance.openCloseVideoSourceSwitch); + } + + function setInitialLevel(levels) { + // Check if a persistency level exists and set the current level accordingly + const persistedLevel = levels.find(level => level.title === playerInstance.fluidStorage.fluidQuality); + + if (persistedLevel) { + playerInstance.hlsPlayer.currentLevel = persistedLevel.id; + } else { + // Default to 'auto' if no persisted level is found + const autoLevel = levels.find(level => level.title === 'auto'); + playerInstance.hlsPlayer.currentLevel = autoLevel.id; + } + } + + function sortLevels(levels) { + return [...levels].sort((a, b) => { + // First sort by width in descending order + if (b.width !== a.width) { + return b.width - a.width; + } + // If width is the same, sort by bitrate in descending order + return b.bitrate - a.bitrate; + }); + } + + playerInstance.detachStreamers = () => { + if (playerInstance.dashPlayer) { + playerInstance.dashPlayer.reset(); + playerInstance.dashPlayer = false; + } else if (playerInstance.hlsPlayer) { + playerInstance.hlsPlayer.detachMedia(); + playerInstance.hlsPlayer = false; + } + }; +} diff --git a/client/fluid-player/src/modules/subtitles.js b/client/fluid-player/src/modules/subtitles.js new file mode 100644 index 0000000..5232aeb --- /dev/null +++ b/client/fluid-player/src/modules/subtitles.js @@ -0,0 +1,225 @@ +export default function (playerInstance, options) { + playerInstance.subtitleFetchParse = (subtitleItem) => { + playerInstance.sendRequest( + subtitleItem.url, + true, + playerInstance.displayOptions.vastOptions.vastTimeout, + function () { + const convertVttRawData = function (vttRawData) { + if (!( + (typeof vttRawData.cues !== 'undefined') && + (vttRawData.cues.length) + )) { + return []; + } + + const result = []; + + for (let i = 0; i < vttRawData.cues.length; i++) { + let tempThumbnailData = vttRawData.cues[i].text.split('#'); + + result.push({ + startTime: vttRawData.cues[i].startTime, + endTime: vttRawData.cues[i].endTime, + text: vttRawData.cues[i].text, + cue: vttRawData.cues[i] + }) + } + + return result; + }; + + const xmlHttpReq = this; + + if ((xmlHttpReq.readyState === 4) && (xmlHttpReq.status !== 200)) { + //The response returned an error. + return; + } + + if (!((xmlHttpReq.readyState === 4) && (xmlHttpReq.status === 200))) { + return; + } + + const textResponse = xmlHttpReq.responseText; + + const parser = new WebVTT.Parser(window, WebVTT.StringDecoder()); + const cues = []; + const regions = []; // TODO: unused? + parser.oncue = function (cue) { + cues.push(cue); + }; + parser.onregion = function (region) { + regions.push(region); + }; + parser.parse(textResponse); + parser.flush(); + playerInstance.subtitlesData = cues; + + } + ); + }; + + playerInstance.createSubtitlesSwitch = () => { + const subtitlesOff = 'OFF'; + playerInstance.subtitlesData = []; + + if (!playerInstance.displayOptions.layoutControls.subtitlesEnabled) { + // No other video subtitles + playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles').style.display = 'none'; + return; + } + + const tracks = []; + tracks.push({'label': subtitlesOff, 'url': 'na', 'lang': subtitlesOff}); + + const tracksList = playerInstance.domRef.player.querySelectorAll('track'); + + [].forEach.call(tracksList, function (track) { + if (track.kind === 'metadata' && track.src) { + tracks.push({'label': track.label, 'url': track.src, 'lang': track.srclang, 'default': track.default}); + } + }); + + playerInstance.subtitlesTracks = tracks; + const subtitlesChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles'); + subtitlesChangeButton.style.display = 'inline-block'; + let appendSubtitleChange = false; + + const subtitlesChangeList = document.createElement('div'); + subtitlesChangeList.className = 'fluid_subtitles_list'; + subtitlesChangeList.style.display = 'none'; + + let hasSelectedSubtitle = false; + const hasDefault = !!playerInstance.subtitlesTracks.find(track => track.default); + playerInstance.subtitlesTracks.forEach(function (subtitle) { + let subtitleSelected = '' + + const subtitlesOnByDefault = playerInstance.displayOptions.layoutControls.subtitlesOnByDefault; + + if (!hasSelectedSubtitle && (subtitlesOnByDefault && subtitle.default || + (!hasDefault && subtitle.label !== subtitlesOff) || + playerInstance.subtitlesTracks.length === 1) || + !subtitlesOnByDefault && subtitle.label === subtitlesOff + ) { + subtitleSelected = 'subtitle_selected'; + playerInstance.subtitleFetchParse(subtitle); + hasSelectedSubtitle = true; + } + + const subtitlesChangeDiv = document.createElement('div'); + subtitlesChangeDiv.className = 'fluid_subtitle_list_item'; + subtitlesChangeDiv.innerHTML = '' + subtitle.label; + + subtitlesChangeDiv.addEventListener('click', function (event) { + event.stopPropagation(); + const subtitleChangedTo = this; + const subtitleIcons = playerInstance.domRef.wrapper.getElementsByClassName('subtitle_button_icon'); + + for (let i = 0; i < subtitleIcons.length; i++) { + subtitleIcons[i].className = subtitleIcons[i].className.replace("subtitle_selected", ""); + } + + subtitleChangedTo.firstChild.className += ' subtitle_selected'; + + playerInstance.subtitlesTracks.forEach(function (subtitle) { + if (subtitle.label === subtitleChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, "")) { + if (subtitle.label === subtitlesOff) { + playerInstance.subtitlesData = []; + } else { + playerInstance.subtitleFetchParse(subtitle); + } + } + }); + playerInstance.openCloseSubtitlesSwitch(); + + }); + + subtitlesChangeList.appendChild(subtitlesChangeDiv); + appendSubtitleChange = true; + + }); + + if (appendSubtitleChange) { + subtitlesChangeButton.appendChild(subtitlesChangeList); + subtitlesChangeButton.removeEventListener('click', handleSubtitlesChange); + subtitlesChangeButton.addEventListener('click', handleSubtitlesChange); + } else { + // Didn't give any subtitle options + playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles').style.display = 'none'; + } + + playerInstance.domRef.player.removeEventListener('timeupdate', videoPlayerSubtitlesUpdate); + playerInstance.domRef.player.addEventListener('timeupdate', videoPlayerSubtitlesUpdate); + }; + + function handleSubtitlesChange() { + playerInstance.openCloseSubtitlesSwitch(); + } + + //attach subtitles to show based on time + //this function is for rendering of subtitles when content is playing + function videoPlayerSubtitlesUpdate() { + playerInstance.renderSubtitles(); + } + + playerInstance.renderSubtitles = () => { + const videoPlayer = playerInstance.domRef.player; + + //if content is playing then no subtitles + let currentTime = Math.floor(videoPlayer.currentTime); + let subtitlesAvailable = false; + let subtitlesContainer = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_container'); + + if (playerInstance.isCurrentlyPlayingAd) { + subtitlesContainer.innerHTML = ''; + return; + } + + for (let i = 0; i < playerInstance.subtitlesData.length; i++) { + if (currentTime >= (playerInstance.subtitlesData[i].startTime) && currentTime <= (playerInstance.subtitlesData[i].endTime)) { + subtitlesContainer.innerHTML = ''; + subtitlesContainer.appendChild(WebVTT.convertCueToDOMTree(window, playerInstance.subtitlesData[i].text)); + subtitlesAvailable = true; + } + } + + if (!subtitlesAvailable) { + subtitlesContainer.innerHTML = ''; + } + }; + + playerInstance.openCloseSubtitlesSwitch = () => { + const subtitleChangeList = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_list'); + + if (playerInstance.isCurrentlyPlayingAd) { + subtitleChangeList.style.display = 'none'; + return; + } + + if (subtitleChangeList.style.display === 'none') { + subtitleChangeList.style.display = 'block'; + const mouseOut = function (event) { + subtitleChangeList.removeEventListener('mouseleave', mouseOut); + subtitleChangeList.style.display = 'none'; + }; + subtitleChangeList.addEventListener('mouseleave', mouseOut); + } else { + subtitleChangeList.style.display = 'none'; + } + }; + + playerInstance.createSubtitles = () => { + const divSubtitlesContainer = document.createElement('div'); + divSubtitlesContainer.className = 'fluid_subtitles_container'; + playerInstance.domRef.player.parentNode.insertBefore(divSubtitlesContainer, playerInstance.domRef.player.nextSibling); + + if (!playerInstance.displayOptions.layoutControls.subtitlesEnabled) { + return; + } + + import(/* webpackChunkName: "vttjs" */ 'videojs-vtt.js').then((it) => { + window.WebVTT = it.WebVTT || it.default.WebVTT; + playerInstance.createSubtitlesSwitch(); + }); + }; +} diff --git a/client/fluid-player/src/modules/suggestedVideos.js b/client/fluid-player/src/modules/suggestedVideos.js new file mode 100644 index 0000000..7f3ab8a --- /dev/null +++ b/client/fluid-player/src/modules/suggestedVideos.js @@ -0,0 +1,197 @@ +export default function (playerInstance, options) { + playerInstance.suggestedVideosGrid = null; + + playerInstance.generateSuggestedVideoList = () => { + const configUrl = playerInstance.displayOptions.suggestedVideos.configUrl; + + if (!configUrl) { + return; + } + + playerInstance.sendRequestAsync(configUrl, false, 5000).then(({response}) => { + const config = JSON.parse(response); + const suggestedVideosGrid = playerInstance.generateSuggestedVideoGrid(config); + + playerInstance.suggestedVideosGrid = suggestedVideosGrid; + + }).catch(err => { + console.error('[FP_ERROR] given suggested videos config url is invalid or not found.', err); + }) + }; + + playerInstance.generateSuggestedVideoGrid = (config) => { + const suggestedVideosGrid = document.createElement('div'); + suggestedVideosGrid.className = 'suggested_tile_grid'; + + for (let i = 0; i < 12; i++) { + const videoTile = playerInstance.createVideoTile(config[i]); + suggestedVideosGrid.appendChild(videoTile); + } + + return suggestedVideosGrid; + }; + + playerInstance.displaySuggestedVideos = () => { + const PlayerDOM = playerInstance.domRef.wrapper; + PlayerDOM.appendChild(playerInstance.suggestedVideosGrid); + }; + + playerInstance.clickSuggestedVideo = (sources, subtitles, configUrl) => { + playerInstance.toggleLoader(true); + playerInstance.hideSuggestedVideos(); + playerInstance.resetPlayer(sources, subtitles, configUrl); + + playerInstance.generateSuggestedVideoList(); + }; + + playerInstance.resetPlayer = (sources, subtitles, configUrl) => { + const videoDOM = playerInstance.domRef.wrapper.querySelector(`#${playerInstance.videoPlayerId}`); + videoDOM.innerHTML = ''; + + let sourcesHTML = ''; + if (sources) { + sources.forEach(source => { + sourcesHTML += ``; + }); + } + if (subtitles) { + subtitles.forEach(subtitle => { + sourcesHTML += ``; + }); + } + + videoDOM.innerHTML = sourcesHTML; + + playerInstance.removeVideoSourcesListFromDOM(); + + const videoSourceList = playerInstance.domRef.wrapper.getElementsByClassName('fluid_control_video_source')[0]; + videoSourceList.innerHTML = ''; + playerInstance.domRef.player.src = ''; + playerInstance.domRef.player.removeAttribute('src'); + playerInstance.setVideoSource(sources[0].url); + playerInstance.createVideoSourceSwitch(false); + playerInstance.resetSubtitles(); + playerInstance.setPersistentSettings(true); + playerInstance.loadInNewVideo(); + playerInstance.resetAds(); + + // set new API config url + if (configUrl) { + playerInstance.displayOptions.suggestedVideos.configUrl = configUrl; + playerInstance.generateSuggestedVideoList(); + } + } + + playerInstance.createVideoTile = (config) => { + const videoTile = document.createElement('div'); + videoTile.addEventListener('click', function () { + playerInstance.clickSuggestedVideo(config.sources, config.subtitles, config.configUrl); + }, false); + videoTile.className = 'suggested_tile'; + videoTile.id = 'suggested_tile_' + config.id; + + playerInstance.getImageTwoMostProminentColours(config.thumbnailUrl).then(mostProminentColors => { + if (mostProminentColors && mostProminentColors.length) { + videoTile.style = `background: ${mostProminentColors[0]};`; + } + }); + + const videoImage = document.createElement('img'); + videoImage.src = config.thumbnailUrl; + videoImage.className = 'suggested_tile_image'; + + const videoTileOverlay = document.createElement('div'); + videoTileOverlay.className='suggested_tile_overlay'; + const title = document.createElement('p'); + title.className = 'suggested_tile_title'; + title.innerText = config.title; + videoTileOverlay.appendChild(title); + + videoTile.appendChild(videoImage); + videoTile.appendChild(videoTileOverlay); + + return videoTile; + } + + playerInstance.resetSubtitles = () => { + playerInstance.removeSubtitlesListFromDOM(); + const videoSubtitlesList = playerInstance.domRef.wrapper.getElementsByClassName('fluid_control_subtitles')[0]; + videoSubtitlesList.innerHTML = ''; + playerInstance.domRef.player.load(); + playerInstance.createSubtitles(false); + } + + playerInstance.loadInNewVideo = () => { + playerInstance.displayOptions.layoutControls.mediaType = playerInstance.getCurrentSrcType(); + playerInstance.initialiseStreamers(); + playerInstance.domRef.player.currentTime = 0; + playerInstance.domRef.player.mainVideoCurrentTime = 0; + playerInstance.setBuffering(); + } + + playerInstance.resetAds = () => { + // Clear midroll and postroll ads + playerInstance.timerPool = {}; + playerInstance.rollsById = {}; + playerInstance.adPool = {}; + playerInstance.adGroupedByRolls = {}; + playerInstance.onPauseRollAdPods = []; + playerInstance.currentOnPauseRollAd = ''; + + // Reset variables and flags, needed for assigning the different rolls correctly + playerInstance.isTimer = false; + playerInstance.timer = null; + playerInstance.firstPlayLaunched = false; + + // Clear preroll ads + playerInstance.preRollAdsResolved = false; + playerInstance.preRollAdPods = []; + playerInstance.preRollAdPodsLength = 0; + playerInstance.preRollVastResolved = 0; + playerInstance.autoplayAfterAd = true; + + // Wait until new selected video is buffered so we can get the video length + // This is needed for mid and post rolls to assign the correct time key to their respective triggers + const checkMainVideoDuration = () => { + if (!isNaN(playerInstance.domRef.player.duration) && playerInstance.domRef.player.duration > 0) { + playerInstance.toggleLoader(true); + playerInstance.mainVideoDuration = playerInstance.domRef.player.duration; + + clearInterval(intervalId); + + // Set up ads + playerInstance.setVastList(); + playerInstance.checkForNextAd(); + playerInstance.playPauseToggle(); + } + }; + + const intervalId = setInterval(checkMainVideoDuration, 100); + } + + playerInstance.removeVideoSourcesListFromDOM = () => { + const sourcesDOM = playerInstance.domRef.wrapper.getElementsByClassName('fluid_video_source_list_item'); + for (let i = 0; i < sourcesDOM.length; i++) { + sourcesDOM[i].remove(); + } + }; + + playerInstance.removeSubtitlesListFromDOM = () => { + const tracksDOM = playerInstance.domRef.wrapper.getElementsByClassName('fluid_subtitle_list_item'); + for (let i = 0; i < tracksDOM.length; i++) { + tracksDOM[i].remove(); + } + }; + + playerInstance.hideSuggestedVideos = () => { + const suggestedVideosDOM = playerInstance.domRef.wrapper.getElementsByClassName('suggested_tile_grid')[0]; + if (suggestedVideosDOM) { + suggestedVideosDOM.remove(); + } + }; + + playerInstance.isShowingSuggestedVideos = () => { + return !!playerInstance.domRef.wrapper.getElementsByClassName('suggested_tile_grid')[0]; + } + +} diff --git a/client/fluid-player/src/modules/timeline.js b/client/fluid-player/src/modules/timeline.js new file mode 100644 index 0000000..24ce806 --- /dev/null +++ b/client/fluid-player/src/modules/timeline.js @@ -0,0 +1,202 @@ +export default function (playerInstance, options) { + playerInstance.setupThumbnailPreviewVtt = () => { + playerInstance.sendRequest( + playerInstance.displayOptions.layoutControls.timelinePreview.file, + true, + playerInstance.displayOptions.vastOptions.vastTimeout, + function () { + const convertVttRawData = function (vttRawData) { + if (!( + (typeof vttRawData.cues !== 'undefined') && + (vttRawData.cues.length) + )) { + return []; + } + + const result = []; + let tempThumbnailData = null; + let tempThumbnailCoordinates = null; + + for (let i = 0; i < vttRawData.cues.length; i++) { + tempThumbnailData = vttRawData.cues[i].text.split('#'); + let xCoords = 0, yCoords = 0, wCoords = 122.5, hCoords = 69; + + // .vtt file contains sprite corrdinates + if ( + (tempThumbnailData.length === 2) && + (tempThumbnailData[1].indexOf('xywh=') === 0) + ) { + tempThumbnailCoordinates = tempThumbnailData[1].substring(5); + tempThumbnailCoordinates = tempThumbnailCoordinates.split(','); + + if (tempThumbnailCoordinates.length === 4) { + playerInstance.displayOptions.layoutControls.timelinePreview.spriteImage = true; + xCoords = parseInt(tempThumbnailCoordinates[0]); + yCoords = parseInt(tempThumbnailCoordinates[1]); + wCoords = parseInt(tempThumbnailCoordinates[2]); + hCoords = parseInt(tempThumbnailCoordinates[3]); + } + } + + let imageUrl; + if (playerInstance.displayOptions.layoutControls.timelinePreview.spriteRelativePath + && playerInstance.displayOptions.layoutControls.timelinePreview.file.indexOf('/') !== -1 + && (typeof playerInstance.displayOptions.layoutControls.timelinePreview.sprite === 'undefined' || playerInstance.displayOptions.layoutControls.timelinePreview.sprite === '') + ) { + imageUrl = playerInstance.displayOptions.layoutControls.timelinePreview.file.substring(0, playerInstance.displayOptions.layoutControls.timelinePreview.file.lastIndexOf('/')); + imageUrl += '/' + tempThumbnailData[0]; + } else { + imageUrl = (playerInstance.displayOptions.layoutControls.timelinePreview.sprite ? playerInstance.displayOptions.layoutControls.timelinePreview.sprite : tempThumbnailData[0]); + } + + result.push({ + startTime: vttRawData.cues[i].startTime, + endTime: vttRawData.cues[i].endTime, + image: imageUrl, + x: xCoords, + y: yCoords, + w: wCoords, + h: hCoords + }); + } + + return result; + }; + + const xmlHttpReq = this; + + if ((xmlHttpReq.readyState === 4) && (xmlHttpReq.status !== 200)) { + //The response returned an error. + return; + } + + if (!((xmlHttpReq.readyState === 4) && (xmlHttpReq.status === 200))) { + return; + } + + const textResponse = xmlHttpReq.responseText; + + const webVttParser = new window.WebVTTParser(); + const vttRawData = webVttParser.parse(textResponse); + + playerInstance.timelinePreviewData = convertVttRawData(vttRawData); + } + ); + }; + + playerInstance.generateTimelinePreviewTags = () => { + const progressContainer = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container'); + const previewContainer = document.createElement('div'); + + previewContainer.className = 'fluid_timeline_preview_container'; + previewContainer.style.display = 'none'; + previewContainer.style.position = 'absolute'; + + progressContainer.appendChild(previewContainer); + + //Shadow is needed to not trigger mouseleave event, that stops showing thumbnails, in case one scrubs a bit too fast and leaves current thumb before new one drawn. + const previewContainerShadow = document.createElement('div'); + previewContainerShadow.className = 'fluid_timeline_preview_container_shadow'; + previewContainerShadow.style.position = 'absolute'; + previewContainerShadow.style.display = 'none'; + previewContainerShadow.style.opacity = 1; + progressContainer.appendChild(previewContainerShadow); + }; + + playerInstance.getThumbnailCoordinates = (second) => { + if (playerInstance.timelinePreviewData.length) { + for (let i = 0; i < playerInstance.timelinePreviewData.length; i++) { + if ((second >= playerInstance.timelinePreviewData[i].startTime) && (second <= playerInstance.timelinePreviewData[i].endTime)) { + return playerInstance.timelinePreviewData[i]; + } + } + } + + return false; + }; + + playerInstance.drawTimelinePreview = (event) => { + const timelinePreviewTag = playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container'); + const timelinePreviewShadow = playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container_shadow'); + const progressContainer = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container'); + const totalWidth = progressContainer.clientWidth; + + if (playerInstance.isCurrentlyPlayingAd) { + if (timelinePreviewTag.style.display !== 'none') { + timelinePreviewTag.style.display = 'none'; + } + + return; + } + + //get the hover position + const hoverX = playerInstance.getEventOffsetX(event, progressContainer); + let hoverSecond = null; + + if (totalWidth) { + hoverSecond = playerInstance.currentVideoDuration * hoverX / totalWidth; + + //get the corresponding thumbnail coordinates + const thumbnailCoordinates = playerInstance.getThumbnailCoordinates(hoverSecond); + timelinePreviewShadow.style.width = totalWidth + 'px'; + timelinePreviewShadow.style.display = 'block'; + + if (thumbnailCoordinates !== false) { + timelinePreviewTag.style.width = thumbnailCoordinates.w + 'px'; + timelinePreviewTag.style.height = thumbnailCoordinates.h + 'px'; + timelinePreviewShadow.style.height = thumbnailCoordinates.h + 'px'; + timelinePreviewTag.style.background = + 'url(' + thumbnailCoordinates.image + ') no-repeat scroll -' + thumbnailCoordinates.x + 'px -' + thumbnailCoordinates.y + 'px'; + timelinePreviewTag.style.left = hoverX - (thumbnailCoordinates.w / 2) + 'px'; + timelinePreviewTag.style.display = 'block'; + if (!playerInstance.displayOptions.layoutControls.timelinePreview.spriteImage) { + timelinePreviewTag.style.backgroundSize = 'contain'; + } + + } else { + timelinePreviewTag.style.display = 'none'; + } + } + }; + + playerInstance.setupThumbnailPreview = () => { + let timelinePreview = playerInstance.displayOptions.layoutControls.timelinePreview; + if (!timelinePreview || !timelinePreview.type) { + return; + } + + let eventOn = 'mousemove'; + let eventOff = 'mouseleave'; + if (playerInstance.mobileInfo.userOs) { + eventOn = 'touchmove'; + eventOff = 'touchend'; + } + playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container') + .addEventListener(eventOn, playerInstance.drawTimelinePreview.bind(playerInstance), false); + playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container') + .addEventListener(eventOff, function (event) { + const progress = playerInstance.domRef.wrapper.querySelector('.fluid_controls_progress_container'); + if (typeof event.clientX !== 'undefined' && progress.contains(document.elementFromPoint(event.clientX, event.clientY))) { + //False positive (Chrome bug when fast click causes leave event) + return; + } + playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container').style.display = 'none'; + playerInstance.domRef.wrapper.querySelector('.fluid_timeline_preview_container_shadow').style.display = 'none'; + }, false); + playerInstance.generateTimelinePreviewTags(); + + if ('VTT' === timelinePreview.type && typeof timelinePreview.file === 'string') { + import(/* webpackChunkName: "webvtt" */ '../../vendor/webvtt').then((it) => { + window.WebVTTParser = it.default; + playerInstance.setupThumbnailPreviewVtt(); + }); + } else if ('static' === timelinePreview.type && typeof timelinePreview.frames === 'object') { + timelinePreview.spriteImage = true; + playerInstance.timelinePreviewData = timelinePreview.frames; + } else { + throw 'Invalid thumbnail-preview - type must be VTT or static'; + } + + playerInstance.showTimeOnHover = false; + }; +} diff --git a/client/fluid-player/src/modules/utils.js b/client/fluid-player/src/modules/utils.js new file mode 100644 index 0000000..5b63bb3 --- /dev/null +++ b/client/fluid-player/src/modules/utils.js @@ -0,0 +1,326 @@ +export default function (playerInstance, options) { + playerInstance.isTouchDevice = () => { + return !!('ontouchstart' in window // works on most browsers + || navigator.maxTouchPoints); // works on IE10/11 and Surface + }; + + /** + * Distinguishes iOS from Android devices and the OS version. + * + * This should be avoided in favor of capability detection. + * + * @deprecated deprecated as of v3.0 + * @returns object + */ + playerInstance.getMobileOs = () => { + const ua = navigator.userAgent || ''; + const result = {device: false, userOs: false, userOsVer: false, userOsMajor: false}; + + let versionIndex; + // determine OS + if (ua.match(/Android/i)) { + result.userOs = 'Android'; + versionIndex = ua.indexOf('Android '); + } else if (ua.match(/iPhone/i)) { + result.device = 'iPhone'; + result.userOs = 'iOS'; + versionIndex = ua.indexOf('OS '); + } else if (ua.match(/iPad/i)) { + result.device = 'iPad'; + result.userOs = 'iOS'; + versionIndex = ua.indexOf('OS '); + } else { + result.userOs = false; + } + + // determine version + if ('iOS' === result.userOs && versionIndex > -1) { + const userOsTemp = ua.substr(versionIndex + 3); + const indexOfEndOfVersion = userOsTemp.indexOf(' '); + + if (indexOfEndOfVersion !== -1) { + result.userOsVer = userOsTemp.substring(0, userOsTemp.indexOf(' ')).replace(/_/g, '.'); + result.userOsMajor = parseInt(result.userOsVer); + } + } else if ('Android' === result.userOs && versionIndex > -1) { + result.userOsVer = ua.substr(versionIndex + 8, 3); + } else { + result.userOsVer = false; + } + + return result; + }; + + /** + * Browser detection. + * This should be avoided in favor of capability detection. + * + * @deprecated deprecated as of v3.0 + * + * @returns object + */ + playerInstance.getBrowserVersion = () => { + const ua = navigator.userAgent || ''; + const result = {browserName: false, fullVersion: false, majorVersion: false, userOsMajor: false}; + + let idx, uaindex; + + try { + result.browserName = navigator.appName; + + if ((idx = ua.indexOf('OPR/')) !== -1) { + result.browserName = 'Opera'; + result.fullVersion = ua.substring(idx + 4); + } else if ((idx = ua.indexOf('Opera')) !== -1) { + result.browserName = 'Opera'; + result.fullVersion = ua.substring(idx + 6); + if ((idx = ua.indexOf('Version')) !== -1) + result.fullVersion = ua.substring(idx + 8); + } else if ((idx = ua.indexOf('MSIE')) !== -1) { + result.browserName = 'Microsoft Internet Explorer'; + result.fullVersion = ua.substring(idx + 5); + } else if ((idx = ua.indexOf('Chrome')) !== -1) { + result.browserName = 'Google Chrome'; + result.fullVersion = ua.substring(idx + 7); + } else if ((idx = ua.indexOf('Safari')) !== -1) { + result.browserName = 'Safari'; + result.fullVersion = ua.substring(idx + 7); + if ((idx = ua.indexOf('Version')) !== -1) + result.fullVersion = ua.substring(idx + 8); + } else if ((idx = ua.indexOf('Firefox')) !== -1) { + result.browserName = 'Mozilla Firefox'; + result.fullVersion = ua.substring(idx + 8); + } + + // Others "name/version" is at the end of userAgent + else if ((uaindex = ua.lastIndexOf(' ') + 1) < (idx = ua.lastIndexOf('/'))) { + result.browserName = ua.substring(uaindex, idx); + result.fullVersion = ua.substring(idx + 1); + if (result.browserName.toLowerCase() === result.browserName.toUpperCase()) { + result.browserName = navigator.appName; + } + } + + // trim the fullVersion string at semicolon/space if present + if ((uaindex = result.fullVersion.indexOf(';')) !== -1) { + result.fullVersion = result.fullVersion.substring(0, uaindex); + } + if ((uaindex = result.fullVersion.indexOf(' ')) !== -1) { + result.fullVersion = result.fullVersion.substring(0, uaindex); + } + + result.majorVersion = parseInt('' + result.fullVersion, 10); + + if (isNaN(result.majorVersion)) { + result.fullVersion = '' + parseFloat(navigator.appVersion); + result.majorVersion = parseInt(navigator.appVersion, 10); + } + } catch (e) { + //Return default obj. + } + + return result; + }; + + playerInstance.compareVersion = (v1, v2) => { + if (typeof v1 !== 'string') return false; + if (typeof v2 !== 'string') return false; + v1 = v1.split('.'); + v2 = v2.split('.'); + const k = Math.min(v1.length, v2.length); + for (let i = 0; i < k; ++i) { + v1[i] = parseInt(v1[i], 10); + v2[i] = parseInt(v2[i], 10); + if (v1[i] > v2[i]) return 1; + if (v1[i] < v2[i]) return -1; + } + return v1.length === v2.length ? 0 : (v1.length < v2.length ? -1 : 1); + }; + + playerInstance.convertTimeStringToSeconds = (str) => { + if (!(str && str.match(/^(\d){2}(:[0-5][0-9]){2}(.(\d){1,3})?$/))) { + return false; + } + + const timeParts = str.split(':'); + return ((parseInt(timeParts[0], 10)) * 3600) + ((parseInt(timeParts[1], 10)) * 60) + (parseInt(timeParts[2], 10)); + }; + + // Format time to hh:mm:ss + playerInstance.formatTime = (duration) => { + const formatDateObj = new Date(duration * 1000); + const formatHours = playerInstance.pad(formatDateObj.getUTCHours()); + const formatMinutes = playerInstance.pad(formatDateObj.getUTCMinutes()); + const formatSeconds = playerInstance.pad(formatDateObj.getSeconds()); + + return formatHours >= 1 + ? formatHours + ':' + formatMinutes + ':' + formatSeconds + : formatMinutes + ':' + formatSeconds; + }; + + playerInstance.pad = (value) => { + if (value < 10) { + return '0' + value; + } + return value; + }; + + /** + * Checks if element is fully visible in the viewport + * + * @param {Element} element + * @returns {boolean|null} + */ + playerInstance.isElementVisible = (element) => { + if (!element) { return null; } + + const rect = element.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + } + + playerInstance.observe = () => { + var observer = new IntersectionObserver( + function (entries) { + entries.forEach(function (entry) { + if (entry.intersectionRatio >= 0.5) { + playerInstance.domRef.player.inView = true; + } + + if (entry.intersectionRatio == 0 && entry.target.glast) { + playerInstance.domRef.player.inView = false; + } + }); + }, + { + threshold: [0.0, 0.5], + }, + ); + + observer.observe(playerInstance.domRef.wrapper); + } + + /** + * Throttles callback by time + * + * @param callback + * @param time + * @returns {function(): void} + */ + playerInstance.throttle = function throttle(callback, time) { + let throttleControl = false; + + return function () { + if (!throttleControl) { + callback.apply(this, arguments); + throttleControl = true; + setTimeout(function () { + throttleControl = false; + }, time); + } + } + } + + playerInstance.getImageTwoMostProminentColours = (imageUrl) => { + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.src = imageUrl; + + img.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0, img.width, img.height); + const imageData = ctx.getImageData(0, 0, img.width, img.height).data; + + const colorCount = {}; + for (let i = 0; i < imageData.length; i += 4) { + const r = imageData[i]; + const g = imageData[i + 1]; + const b = imageData[i + 2]; + const color = `rgb(${r},${g},${b})`; + + if (colorCount[color]) { + colorCount[color]++; + } else { + colorCount[color] = 1; + } + } + + const rgbToHsl = (r, g, b) => { + r /= 255, g /= 255, b /= 255; + const max = Math.max(r, g, b), min = Math.min(r, g, b); + let h, s, l = (max + min) / 2; + + if (max === min) { + h = s = 0; + } else { + const d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + + return [h, s, l]; + }; + + const sortedColors = Object.keys(colorCount).map(color => { + const [r, g, b] = color.match(/\d+/g).map(Number); + const [h, s, l] = rgbToHsl(r, g, b); + return { color, h, s, l, score: s + (1 - Math.abs(2 * l - 1)) }; + }).sort((a, b) => b.score - a.score); + + const isCloseToBlack = (color) => { + const rgb = color.match(/\d+/g).map(Number); + const blackThreshold = 40; + return rgb[0] < blackThreshold && rgb[1] < blackThreshold && rgb[2] < blackThreshold; + }; + + const minHueDifference = 0.1; + const minSaturationDifference = 0.1; + const minLightnessDifference = 0.1; + + let mostVibrantColors = []; + + for (const colorObj of sortedColors) { + if (mostVibrantColors.length === 2) break; + if (!isCloseToBlack(colorObj.color)) { + const enoughDifference = mostVibrantColors.every(existingColor => { + const hueDifference = Math.abs(colorObj.h - existingColor.h); + const saturationDifference = Math.abs(colorObj.s - existingColor.s); + const lightnessDifference = Math.abs(colorObj.l - existingColor.l); + return ( + hueDifference >= minHueDifference && + saturationDifference >= minSaturationDifference && + lightnessDifference >= minLightnessDifference + ); + }); + if (enoughDifference) { + mostVibrantColors.push(colorObj); + } + } + } + + if (mostVibrantColors.length < 2) { + mostVibrantColors = sortedColors.slice(0, 2); + } + + resolve(mostVibrantColors.map(colorObj => colorObj.color)); + }; + + img.onerror = () => { + reject(new Error('Failed to load image')); + }; + }); + } +} diff --git a/client/fluid-player/src/modules/vast.js b/client/fluid-player/src/modules/vast.js new file mode 100644 index 0000000..b69735c --- /dev/null +++ b/client/fluid-player/src/modules/vast.js @@ -0,0 +1,1122 @@ +// @ts-check + +// VAST support module + +/* Type declarations */ + +/** + * @typedef {Object} RawAdTree + * @property {Array} children + * @property {XMLDocument} data + * @property {'inLine'|'wrapper'} tagType + * @property {boolean|undefined} fallbackOnNoAd + * @property {Array | undefined} wrappers +*/ + +/** + * @typedef {Object} RawAd + * @property {XMLDocument} data + * @property {Array} wrappers + * @property {'inLine' | 'wrapper'} tagType + */ + +/** + * @typedef {Object & RawAd} Ad + * @property {Array} clicktracking + * @property {string} errorUrl + * @property {Array} impressions + * @property {Array} viewImpression + * @property {Array} stopTracking + * @property {Array} tracking + * @property {number|null} sequence + * @property {number} duration + * @property {boolean} played + */ + +/** + * + * @param {import("../fluidplayer.js").FluidPlayer} playerInstance + * @param {unknown} options + */ +function vast(playerInstance, options) { + /** + * Gets CTA parameters from VAST and sets them on tempOptions + * + * Fallbacks to any value that is filled on the TitleCTA extension, but needs at least an url and a text + * + * @param {HTMLElement} titleCtaElement + * + * @param {any} tmpOptions + */ + playerInstance.setCTAFromVast = (titleCtaElement, tmpOptions) => { + if (playerInstance.displayOptions.vastOptions.adCTATextVast && titleCtaElement) { + const mobileText = playerInstance.extractNodeDataByTagName(titleCtaElement, 'MobileText'); + const desktopText = playerInstance.extractNodeDataByTagName(titleCtaElement, 'PCText'); + const link = + playerInstance.extractNodeDataByTagName(titleCtaElement, 'DisplayUrl') || + playerInstance.extractNodeDataByTagName(titleCtaElement, 'Link'); + const tracking = playerInstance.extractNodeDataByTagName(titleCtaElement, 'Tracking'); + const isMobile = window.matchMedia('(max-width: 768px)').matches; + + if ((desktopText || mobileText) && tracking) { + tmpOptions.titleCTA = { + text: isMobile ? + mobileText || desktopText : + desktopText || mobileText, + link: link || null, + tracking, + } + } + } + }; + + playerInstance.getClickThroughUrlFromLinear = (linear) => { + const videoClicks = linear.getElementsByTagName('VideoClicks'); + + if (videoClicks.length) { //There should be exactly 1 node + const clickThroughs = videoClicks[0].getElementsByTagName('ClickThrough'); + + if (clickThroughs.length) { + return playerInstance.extractNodeData(clickThroughs[0]); + } + } + + return false; + }; + + playerInstance.getVastAdTagUriFromWrapper = (xmlResponse) => { + const wrapper = xmlResponse.getElementsByTagName('Wrapper'); + + if (typeof wrapper !== 'undefined' && wrapper.length) { + const vastAdTagURI = wrapper[0].getElementsByTagName('VASTAdTagURI'); + + if (vastAdTagURI.length) { + return playerInstance.extractNodeData(vastAdTagURI[0]); + } + } + + return false; + }; + + playerInstance.hasInLine = (xmlResponse) => { + const inLine = xmlResponse.getElementsByTagName('InLine'); + return ((typeof inLine !== 'undefined') && inLine.length); + }; + + playerInstance.hasVastAdTagUri = (xmlResponse) => { + const vastAdTagURI = xmlResponse.getElementsByTagName('VASTAdTagURI'); + return ((typeof vastAdTagURI !== 'undefined') && vastAdTagURI.length); + }; + + playerInstance.getClickThroughUrlFromNonLinear = (nonLinear) => { + let result = ''; + const nonLinears = nonLinear.getElementsByTagName('NonLinear'); + + if (nonLinears.length) {//There should be exactly 1 node + const nonLinearClickThrough = nonLinear.getElementsByTagName('NonLinearClickThrough'); + if (nonLinearClickThrough.length) { + result = playerInstance.extractNodeData(nonLinearClickThrough[0]); + } + } + + return result; + }; + + playerInstance.getTrackingFromLinear = (linear) => { + const trackingEvents = linear.getElementsByTagName('TrackingEvents'); + + if (trackingEvents.length) {//There should be no more than one node + return trackingEvents[0].getElementsByTagName('Tracking'); + } + + return []; + }; + + playerInstance.getDurationFromLinear = (linear) => { + const duration = linear.getElementsByTagName('Duration'); + + if (duration.length && (typeof duration[0].childNodes[0] !== 'undefined')) { + const nodeDuration = playerInstance.extractNodeData(duration[0]); + return playerInstance.convertTimeStringToSeconds(nodeDuration); + } + + return false; + }; + + playerInstance.getDurationFromNonLinear = (tag) => { + let result = 0; + const nonLinear = tag.getElementsByTagName('NonLinear'); + if (nonLinear.length && (typeof nonLinear[0].getAttribute('minSuggestedDuration') !== 'undefined')) { + result = playerInstance.convertTimeStringToSeconds(nonLinear[0].getAttribute('minSuggestedDuration')); + } + return result; + }; + + playerInstance.getDimensionFromNonLinear = (tag) => { + const result = { 'width': null, 'height': null }; + const nonLinear = tag.getElementsByTagName('NonLinear'); + + if (nonLinear.length) { + if (typeof nonLinear[0].getAttribute('width') !== 'undefined') { + result.width = nonLinear[0].getAttribute('width'); + } + if (typeof nonLinear[0].getAttribute('height') !== 'undefined') { + result.height = nonLinear[0].getAttribute('height'); + } + } + + return result; + }; + + playerInstance.getCreativeTypeFromStaticResources = (tag) => { + let result = ''; + const nonLinears = tag.getElementsByTagName('NonLinear'); + + if (nonLinears.length && (typeof nonLinears[0].childNodes[0] !== 'undefined')) {//There should be exactly 1 StaticResource node + result = nonLinears[0].getElementsByTagName('StaticResource')[0].getAttribute('creativeType'); + } + + return result.toLowerCase(); + }; + + playerInstance.getMediaFilesFromLinear = (linear) => { + const mediaFiles = linear.getElementsByTagName('MediaFiles'); + + if (mediaFiles.length) {//There should be exactly 1 MediaFiles node + return mediaFiles[0].getElementsByTagName('MediaFile'); + } + + return []; + }; + + playerInstance.getStaticResourcesFromNonLinear = (linear) => { + let result = []; + const nonLinears = linear.getElementsByTagName('NonLinear'); + + if (nonLinears.length) {//There should be exactly 1 StaticResource node + result = nonLinears[0].getElementsByTagName('StaticResource'); + } + + return result; + }; + + /** + * Gets the first element found by tag name, and returns the element data + * + * @param {HTMLElement} parentNode + * + * @param {string} tagName + * + * @returns {string|null} + */ + playerInstance.extractNodeDataByTagName = (parentNode, tagName) => { + const element = parentNode.getElementsByTagName(tagName); + + if (element && element.length) { + return playerInstance.extractNodeData(element[0]); + } else { + return null; + } + }; + + playerInstance.extractNodeData = (parentNode) => { + let contentAsString = ""; + for (let n = 0; n < parentNode.childNodes.length; n++) { + const child = parentNode.childNodes[n]; + if (child.nodeType === 8 || (child.nodeType === 3 && /^\s*$/.test(child.nodeValue))) { + // Comments or text with no content + } else { + contentAsString += child.nodeValue; + } + } + return contentAsString.replace(/(^\s+|\s+$)/g, ''); + }; + + playerInstance.getAdParametersFromLinear = (linear) => { + const adParameters = linear.getElementsByTagName('AdParameters'); + let adParametersData = null; + + if (adParameters.length) { + adParametersData = playerInstance.extractNodeData(adParameters[0]); + } + + return adParametersData; + }; + + playerInstance.getMediaFileListFromLinear = (linear) => { + const mediaFileList = []; + const mediaFiles = playerInstance.getMediaFilesFromLinear(linear); + + if (!mediaFiles.length) { + return mediaFileList; + } + + for (let n = 0; n < mediaFiles.length; n++) { + let mediaType = mediaFiles[n].getAttribute('mediaType'); + + if (!mediaType) { + // if there is no mediaType attribute then the video is 2D + mediaType = '2D'; + } + + // get all the attributes of media file + mediaFileList.push({ + 'src': playerInstance.extractNodeData(mediaFiles[n]), + 'type': mediaFiles[n].getAttribute('type'), + 'apiFramework': mediaFiles[n].getAttribute('apiFramework'), + 'codec': mediaFiles[n].getAttribute('codec'), + 'id': mediaFiles[n].getAttribute('codec'), + 'fileSize': mediaFiles[n].getAttribute('fileSize'), + 'delivery': mediaFiles[n].getAttribute('delivery'), + 'width': mediaFiles[n].getAttribute('width'), + 'height': mediaFiles[n].getAttribute('height'), + 'mediaType': mediaType.toLowerCase() + }); + + } + + return mediaFileList; + }; + + playerInstance.getIconClickThroughFromLinear = (linear) => { + const iconClickThrough = linear.getElementsByTagName('IconClickThrough'); + + if (iconClickThrough.length) { + return playerInstance.extractNodeData(iconClickThrough[0]); + } + + return ''; + }; + + playerInstance.getStaticResourceFromNonLinear = (linear) => { + let fallbackStaticResource; + const staticResources = playerInstance.getStaticResourcesFromNonLinear(linear); + + for (let i = 0; i < staticResources.length; i++) { + if (!staticResources[i].getAttribute('type')) { + fallbackStaticResource = playerInstance.extractNodeData(staticResources[i]); + } + + if (staticResources[i].getAttribute('type') === playerInstance.displayOptions.staticResource) { + return playerInstance.extractNodeData(staticResources[i]); + } + } + + return fallbackStaticResource; + }; + + playerInstance.registerTrackingEvents = (creativeLinear, tmpOptions) => { + const trackingEvents = playerInstance.getTrackingFromLinear(creativeLinear); + let eventType = ''; + let oneEventOffset = 0; + + for (let i = 0; i < trackingEvents.length; i++) { + eventType = trackingEvents[i].getAttribute('event'); + + switch (eventType) { + case 'start': + case 'firstQuartile': + case 'midpoint': + case 'thirdQuartile': + case 'complete': + if (typeof tmpOptions.tracking[eventType] === 'undefined') { + tmpOptions.tracking[eventType] = []; + } + + if (typeof tmpOptions.stopTracking[eventType] === 'undefined') { + tmpOptions.stopTracking[eventType] = []; + } + tmpOptions.tracking[eventType].push(trackingEvents[i].textContent.trim()); + tmpOptions.stopTracking[eventType] = false; + + break; + + case 'progress': + if (typeof tmpOptions.tracking[eventType] === 'undefined') { + tmpOptions.tracking[eventType] = []; + } + + oneEventOffset = playerInstance.convertTimeStringToSeconds(trackingEvents[i].getAttribute('offset')); + + if (typeof tmpOptions.tracking[eventType][oneEventOffset] === 'undefined') { + tmpOptions.tracking[eventType][oneEventOffset] = { + elements: [], + stopTracking: false + }; + } + + tmpOptions.tracking[eventType][oneEventOffset].elements.push(trackingEvents[i].textContent.trim()); + + break; + + default: + break; + } + } + }; + + playerInstance.registerClickTracking = (clickTrackingTag, tmpOptions) => { + if (!clickTrackingTag || !clickTrackingTag.length) { + return; + } + + for (let i = 0; i < clickTrackingTag.length; i++) { + if (clickTrackingTag[i] === '') { + continue; + } + + tmpOptions.clicktracking.push(clickTrackingTag[i]); + } + + }; + + playerInstance.registerViewableImpressionEvents = (viewableImpressionTags, tmpOptions) => { + if (!viewableImpressionTags.length) { + return; + } + + for (let i = 0; i < viewableImpressionTags.length; i++) { + const viewableImpressionEvent = playerInstance.extractNodeData(viewableImpressionTags[i]); + tmpOptions.viewImpression.push(viewableImpressionEvent); + } + }; + + playerInstance.registerImpressionEvents = (impressionTags, tmpOptions) => { + if (!impressionTags.length) { + return; + } + + for (let i = 0; i < impressionTags.length; i++) { + const impressionEvent = playerInstance.extractNodeData(impressionTags[i]); + tmpOptions.impression.push(impressionEvent); + } + }; + + playerInstance.registerErrorEvents = (errorTags, tmpOptions) => { + if ((typeof errorTags !== 'undefined') && + (errorTags !== null) && + (errorTags.length === 1) && //Only 1 Error tag is expected + (errorTags[0].childNodes.length === 1)) { + tmpOptions.errorUrl = errorTags[0].childNodes[0].nodeValue; + } + }; + + playerInstance.announceError = (code) => { + if (typeof playerInstance.vastOptions.errorUrl === 'undefined' || !playerInstance.vastOptions.errorUrl) { + return; + } + + const parsedCode = typeof code !== 'undefined' ? parseInt(code) : 900; + const errorUrl = playerInstance.vastOptions.errorUrl.replace('[ERRORCODE]', parsedCode); + + //Send the error request + playerInstance.callUris([errorUrl]); + }; + + playerInstance.getClickTrackingEvents = (linear) => { + const result = []; + + const videoClicks = linear.getElementsByTagName('VideoClicks'); + + //There should be exactly 1 node + if (!videoClicks.length) { + return; + } + + const clickTracking = videoClicks[0].getElementsByTagName('ClickTracking'); + + if (!clickTracking.length) { + return; + } + + for (let i = 0; i < clickTracking.length; i++) { + const clickTrackingEvent = playerInstance.extractNodeData(clickTracking[i]); + result.push(clickTrackingEvent); + } + + return result; + }; + + playerInstance.getNonLinearClickTrackingEvents = (nonLinear) => { + const result = []; + const nonLinears = nonLinear.getElementsByTagName('NonLinear'); + + if (!nonLinears.length) { + return; + } + + const clickTracking = nonLinear.getElementsByTagName('NonLinearClickTracking'); + + if (!clickTracking.length) { + return; + } + + for (let i = 0; i < clickTracking.length; i++) { + const NonLinearClickTracking = playerInstance.extractNodeData(clickTracking[i]); + result.push(NonLinearClickTracking); + } + + return result; + }; + + // TODO: ??? + playerInstance.callUris = (uris) => { + for (let i = 0; i < uris.length; i++) { + new Image().src = uris[i]; + } + }; + + playerInstance.recalculateAdDimensions = () => { + const videoPlayer = playerInstance.domRef.player; + const divClickThrough = playerInstance.domRef.wrapper.querySelector('.vast_clickthrough_layer'); + + if (divClickThrough) { + divClickThrough.style.width = videoPlayer.offsetWidth + 'px'; + divClickThrough.style.height = videoPlayer.offsetHeight + 'px'; + } + + const requestFullscreenFunctionNames = playerInstance.checkFullscreenSupport(); + const fullscreenButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_fullscreen'); + const menuOptionFullscreen = playerInstance.domRef.wrapper.querySelector('.context_option_fullscreen'); + + if (requestFullscreenFunctionNames) { + // this will go other way around because we already exited full screen + if (document[requestFullscreenFunctionNames.isFullscreen] === null) { + // Exit fullscreen + playerInstance.fullscreenOff(fullscreenButton, menuOptionFullscreen); + } else { + // Go fullscreen + playerInstance.fullscreenOn(fullscreenButton, menuOptionFullscreen); + } + } else { + // TODO: I am fairly certain this fallback does not work... + //The browser does not support the Fullscreen API, so a pseudo-fullscreen implementation is used + const fullscreenTag = playerInstance.domRef.wrapper; + + if (fullscreenTag.className.search(/\bpseudo_fullscreen\b/g) !== -1) { + fullscreenTag.className += ' pseudo_fullscreen'; + playerInstance.fullscreenOn(fullscreenButton, menuOptionFullscreen); + } else { + fullscreenTag.className = fullscreenTag.className.replace(/\bpseudo_fullscreen\b/g, ''); + playerInstance.fullscreenOff(fullscreenButton, menuOptionFullscreen); + } + } + }; + + /** + * Prepares VAST for instant ads + * + * @param roll + */ + playerInstance.prepareVast = (roll) => { + let list = playerInstance.findRoll(roll); + + for (let i = 0; i < list.length; i++) { + const rollListId = list[i]; + + if (!(playerInstance.rollsById[rollListId].vastLoaded !== true && playerInstance.rollsById[rollListId].error !== true)) { + continue; + } + + playerInstance.processVastWithRetries(playerInstance.rollsById[rollListId]); + } + }; + + playerInstance.playMainVideoWhenVastFails = (errorCode) => { + playerInstance.debugMessage('playMainVideoWhenVastFails called'); + playerInstance.domRef.player.removeEventListener('loadedmetadata', playerInstance.switchPlayerToVastMode); + playerInstance.domRef.player.pause(); + playerInstance.toggleLoader(false); + playerInstance.displayOptions.vastOptions.vastAdvanced.noVastVideoCallback(); + + if (!playerInstance.vastOptions || typeof playerInstance.vastOptions.errorUrl === 'undefined') { + playerInstance.announceLocalError(errorCode); + } else { + playerInstance.announceError(errorCode); + } + + playerInstance.switchToMainVideo(); + }; + + // TODO: ??? + playerInstance.switchPlayerToVastMode = () => { + }; + + /** + * Process the XML response + * + * @param ad + */ + function processAdCreatives(ad) { + const adElement = ad.data; + + if (!adElement) { + return; + } + + const creativeElements = Array.from(adElement.getElementsByTagName('Creative')); + + if (creativeElements.length) { + for (let i = 0; i < creativeElements.length; i++) { + const creativeElement = creativeElements[i]; + + try { + if (ad.adType === 'linear') { + const linearCreatives = creativeElement.getElementsByTagName('Linear'); + const creativeLinear = linearCreatives[0]; + + //Extract the Ad data if it is actually the Ad (!wrapper) + if (!playerInstance.hasVastAdTagUri(adElement) && playerInstance.hasInLine(adElement)) { + //Set initial values + ad.adFinished = false; + ad.vpaid = false; + + //Extract the necessary data from the Linear node + ad.skipoffset = playerInstance.convertTimeStringToSeconds(creativeLinear.getAttribute('skipoffset')); + ad.clickthroughUrl = playerInstance.getClickThroughUrlFromLinear(creativeLinear); + ad.duration = playerInstance.getDurationFromLinear(creativeLinear); + ad.mediaFileList = playerInstance.getMediaFileListFromLinear(creativeLinear); + ad.adParameters = playerInstance.getAdParametersFromLinear(creativeLinear); + ad.iconClick = ad.iconClick || playerInstance.getIconClickThroughFromLinear(creativeLinear); + + if (ad.adParameters) { + ad.vpaid = true; + } + } + } + + if (ad.adType === 'nonLinear') { + const nonLinearCreatives = creativeElement.getElementsByTagName('NonLinearAds'); + const creativeNonLinear = nonLinearCreatives[0]; + + //Extract the Ad data if it is actually the Ad (!wrapper) + if (!playerInstance.hasVastAdTagUri(adElement) && playerInstance.hasInLine(adElement)) { + //Set initial values + ad.vpaid = false; + + //Extract the necessary data from the NonLinear node + ad.clickthroughUrl = playerInstance.getClickThroughUrlFromNonLinear(creativeNonLinear); + ad.duration = playerInstance.getDurationFromNonLinear(creativeNonLinear); // VAST version < 4.0 + ad.dimension = playerInstance.getDimensionFromNonLinear(creativeNonLinear); // VAST version < 4.0 + ad.staticResource = playerInstance.getStaticResourceFromNonLinear(creativeNonLinear); + ad.creativeType = playerInstance.getCreativeTypeFromStaticResources(creativeNonLinear); + ad.adParameters = playerInstance.getAdParametersFromLinear(creativeNonLinear); + + if (ad.adParameters) { + ad.vpaid = true; + } + } + } + + // Current support is for only one creative element + // break the loop if creative was successful + break; + } catch (err) { + if (creativeElement.firstElementChild && + !(creativeElement.firstElementChild.tagName === 'Linear' || + creativeElement.firstElementChild.tagName === 'NonLinearAds')) { + console.warn('Skipping ' + creativeElement.firstElementChild.tagName + ', this might not be supported yet.') + } + console.error(err); + } + }; + } + + return ad; + } + + /** + * Parse the VAST Tag + * + * @param vastObj + */ + playerInstance.processVastWithRetries = (vastObj) => { + let vastTag = vastObj.vastTag; + const rollListId = vastObj.id; + + playerInstance.domRef.player.addEventListener('adId_' + rollListId, playerInstance[vastObj.roll]); + + const handleVastResult = function (pass, adOptionsList) { + if (pass && Array.isArray(adOptionsList) && !playerInstance.displayOptions.vastOptions.allowVPAID && adOptionsList.some(adOptions => adOptions.vpaid)) { + adOptionsList = adOptionsList.filter(adOptions => adOptions.vpaid !== true); + playerInstance.announceLocalError('103', 'VPAID not allowed, so skipping this VAST tag.') + } + + if (pass && Array.isArray(adOptionsList) && adOptionsList.length) { + + playerInstance.adPool[rollListId] = []; + + adOptionsList.forEach((tmpOptions, index) => { + tmpOptions.id = rollListId + '_AD' + index; + tmpOptions.rollListId = rollListId; + + if (tmpOptions.adType === 'linear') { + + if ((typeof tmpOptions.iconClick !== 'undefined') && (tmpOptions.iconClick !== null) && tmpOptions.iconClick.length) { + tmpOptions.landingPage = tmpOptions.iconClick; + } + + const selectedMediaFile = playerInstance.getSupportedMediaFileObject(tmpOptions.mediaFileList); + if (selectedMediaFile) { + tmpOptions.mediaType = selectedMediaFile.mediaType; + } + + } + + tmpOptions.adType = tmpOptions.adType ? tmpOptions.adType : 'unknown'; + playerInstance.adPool[rollListId].push(Object.assign({}, tmpOptions)); + + if (playerInstance.hasTitle()) { + const title = playerInstance.domRef.wrapper.querySelector('.fp_title'); + title.style.display = 'none'; + } + + playerInstance.rollsById[rollListId].ads.push(tmpOptions); + }); + + playerInstance.rollsById[rollListId].vastLoaded = true; + + const event = document.createEvent('Event'); + + event.initEvent('adId_' + rollListId, false, true); + playerInstance.domRef.player.dispatchEvent(event); + playerInstance.displayOptions.vastOptions.vastAdvanced.vastLoadedCallback(); + } else { + // when vast failed + playerInstance.announceLocalError('101'); + + if (vastObj.hasOwnProperty('fallbackVastTags') && vastObj.fallbackVastTags.length > 0) { + vastTag = vastObj.fallbackVastTags.shift(); + playerInstance.processUrl(vastTag, handleVastResult, rollListId); + } else { + if (vastObj.roll === 'preRoll') { + playerInstance.preRollFail(vastObj); + } + playerInstance.rollsById[rollListId].error = true; + } + } + }; + + playerInstance.processUrl(vastTag, handleVastResult, rollListId); + }; + + playerInstance.processUrl = (vastTag, callBack, rollListId) => { + const numberOfRedirects = 0; + + const tmpOptions = { + tracking: [], + stopTracking: [], + impression: [], + viewImpression: [], + clicktracking: [], + vastLoaded: false + }; + + playerInstance.resolveVastTag( + vastTag, + numberOfRedirects, + tmpOptions, + callBack, + rollListId + ); + }; + + /** + * Gets first stand-alone ad + * + * @param {Array} ads + * @returns {Array} + */ + function getFirstStandAloneAd(ads) { + for (const ad of ads) { + const isAdPod = ad.data.attributes.sequence !== undefined; + + if (!isAdPod) { + return [ad]; + } + } + + return []; + } + + /** + * Resolves ad requests recursively and returns a tree of "Ad" and "Wrapper" elements + * + * @param {string} url vast resource url + * @param {number} maxDepth depth of recursive calls (wrapper depth) + * @param {Partial} baseNode used for recursive calls as base node + * @param {number} currentDepth used internally to track depth + * @param {boolean} followAdditionalWrappers used internally to track nested wrapper calls + * @returns {Promise} + */ + async function resolveAdTreeRequests(url, maxDepth, baseNode = {}, currentDepth = 0, followAdditionalWrappers = true) { + const adTree = { ...baseNode, children: [] }; + const { responseXML } = await playerInstance.sendRequestAsync(url, true, playerInstance.displayOptions.vastOptions.vastTimeout); + const adElements = Array.from(responseXML.getElementsByTagName('Ad')); + + for (const adElement of adElements) { + const vastAdTagUri = playerInstance.getVastAdTagUriFromWrapper(adElement); + const isAdPod = adElement.attributes.sequence !== undefined; + const adNode = { data: adElement }; + + if (vastAdTagUri && currentDepth <= maxDepth && followAdditionalWrappers) { + const [wrapperElement] = adElement.getElementsByTagName('Wrapper'); + const disableAdditionalWrappers = wrapperElement.attributes.followAdditionalWrappers && ["false", "0"].includes(wrapperElement.attributes.followAdditionalWrappers.value); // See VAST Wrapper spec + const allowMultipleAds = wrapperElement.attributes.allowMultipleAds && ["true", "1"].includes(wrapperElement.attributes.allowMultipleAds.value); // See VAST Wrapper spec + const fallbackOnNoAd = wrapperElement.attributes.fallbackOnNoAd && ["true", "1"].includes(wrapperElement.attributes.fallbackOnNoAd.value); + + try { + const wrapperResponse = await resolveAdTreeRequests(vastAdTagUri, maxDepth, { tagType: 'wrapper', ...adNode, fallbackOnNoAd }, currentDepth+1, !disableAdditionalWrappers); + wrapperResponse.fallbackOnNoAd = fallbackOnNoAd; + + if (!allowMultipleAds || isAdPod) { + wrapperResponse.children = getFirstStandAloneAd(wrapperResponse.children); + } + + adTree.children.push(wrapperResponse); + } catch (e) { + adTree.children.push({ tagType: `wrapper`, fallbackOnNoAd, httpError: true }) + playerInstance.debugMessage(`Error when loading Wrapper, will trigger fallback if available`, e); + } + } else if (!vastAdTagUri) { + let mediaFileIsValid = true; + let mediaFileUrl = ''; + if (Array.from(adElement.getElementsByTagName('AdParameters')).length) { + const mediaFiles = Array.from(adElement.getElementsByTagName('AdParameters')); + mediaFileIsValid = false; + for (const mediaFile of mediaFiles) { + mediaFileUrl = mediaFile.textContent.trim(); + try { + const mediaFileObj = JSON.parse(mediaFileUrl); + mediaFileUrl = mediaFileObj.videos[0].url; + } catch (error) { + console.error("Error parsing media file URL:", error); + } + } + } else if (Array.from(adElement.getElementsByTagName('MediaFiles')).length) { + const mediaFiles = Array.from(adElement.getElementsByTagName('MediaFiles')); + const mediaFile = mediaFiles[0].getElementsByTagName('MediaFile'); + mediaFileIsValid = false; + for (const mediaFileTemp of mediaFile) { + mediaFileUrl = mediaFileTemp.textContent.trim(); + } + }; + mediaFileIsValid = await validateMediaFile(mediaFileUrl); + if (mediaFileIsValid) { + adTree.children.push({ tagType: 'inLine', mediaFileUrl, ...adNode }); + break; + } else { + adTree.children.push({ tagType: 'inLine', mediaError: true, ...adNode }); + playerInstance.debugMessage(`No valid media file found in Inline ad.`); + } + } + } + + return adTree; + } + + + /** + * Validate Media File to check if videos play + * + * @param {mediaFileUrl} + */ + async function validateMediaFile(mediaFileUrl) { + try { + const response = await fetch(mediaFileUrl); + if (!response.ok || response.headers.get('content-type').indexOf('video') === -1) { + return false; + } + const videoElement = document.createElement('video'); + videoElement.src = mediaFileUrl; + const canPlay = await videoElement.canPlayType(response.headers.get('content-type')); + return canPlay !== ""; + } catch (error) { + console.error('Failed to load media file:', error); + return false; + } + } + + /** + * Transforms an Ad Tree to a 1-dimensional array of Ads with wrapper data attached to each ad + * + * @param {RawAdTree} root + * @param {Array} ads + * @param {Array} wrappers + * @returns {Array} + */ + function flattenAdTree(root, ads = [], wrappers = []) { + const currentWrappers = [...wrappers, root.data]; + + if (Array.isArray(root.children) && root.children.length) { + root.children.forEach(child => flattenAdTree(child, ads, currentWrappers)); + } + + if (root.tagType === 'inLine') { + ads.push({ ...root, wrappers: currentWrappers.filter(Boolean) }); + } + + return ads; + } + + /** + * Register Ad element properties to an Ad based on its data and its wrapper data if available + * + * @param {RawAd} rawAd + * @param {{ tracking: Array, stopTracking: Array, impression: Array, viewImpression: Array, clicktracking: Array }} options + * @returns {Ad} + */ + function registerAdProperties(rawAd, options) { + const ad = { ...rawAd, ...JSON.parse(JSON.stringify(options)) }; + + ad.adType = (ad.data.getElementsByTagName('Linear').length && 'linear') || + (ad.data.getElementsByTagName('NonLinearAds').length && 'nonLinear') || 'unknown'; + + [...(ad.wrappers || []), ad.data].filter(Boolean).forEach(dataSource => { + // Register impressions + const impression = dataSource.getElementsByTagName('Impression'); + if (impression !== null) { + playerInstance.registerImpressionEvents(impression, ad); + } + + // Register viewable impressions + const viewableImpression = dataSource.getElementsByTagName('Viewable'); + if (viewableImpression !== null) { + playerInstance.registerViewableImpressionEvents(viewableImpression, ad); + } + + // Get the error tag, if any + const errorTags = dataSource.getElementsByTagName('Error'); + if (errorTags !== null) { + playerInstance.registerErrorEvents(errorTags, ad); + } + + // Sets CTA from vast + const [titleCta] = dataSource.getElementsByTagName('TitleCTA'); + if (titleCta) { + playerInstance.setCTAFromVast(titleCta, ad); + } + + // Register tracking events + playerInstance.registerTrackingEvents(dataSource, ad); + const clickTracks = ad.adType === 'linear' ? + playerInstance.getClickTrackingEvents(dataSource) : + playerInstance.getNonLinearClickTrackingEvents(dataSource); + playerInstance.registerClickTracking(clickTracks, ad); + }); + + ad.sequence = ad.data.attributes.sequence ? Number(ad.data.attributes.sequence.value) : null; + ad.played = false; + + return ad; + } + + /** + * Handles selection of ad pod or standalone ad to be played + * + * @param {Array} ads + * @param {number} maxDuration + * @param {number} maxQuantity + * @param {boolean} forceStandAloneAd + */ + function getPlayableAds(ads, maxDuration, maxQuantity, forceStandAloneAd) { + const { adPod } = ads + .filter(ad => Boolean(ad.sequence)) + .sort((adX, adY) => adX.sequence - adY.sequence) + .reduce((playableAds, ad) => { + if (playableAds.adPod.length < maxQuantity && (playableAds.totalDuration + ad.duration) <= maxDuration) { + playableAds.adPod.push(ad); + } + + return playableAds; + }, { adPod: [], totalDuration: 0 }); + const adBuffet = ads.filter(ad => !Boolean(ad.sequence) && ad.duration < maxDuration); + + const isValidAdPodFormats = adPod.map(ad => ad.adType).slice(0, -1).every(adType => adType === 'linear'); + + if (adPod.length > 0 && !forceStandAloneAd && isValidAdPodFormats) { + playerInstance.debugMessage('Playing valid adPod', adPod); + return adPod; + } else { + playerInstance.debugMessage('Trying to play single ad, adBuffet:', adBuffet); + return adBuffet.length > 0 ? [adBuffet[0]] : []; + } + } + + /** + * @param vastTag + * @param numberOfRedirects + * @param tmpOptions + * @param callback + * @param rollListId + */ + playerInstance.resolveVastTag = (vastTag, numberOfRedirects, tmpOptions, callback, rollListId) => { + if (!vastTag || vastTag === '') { + return callback(false); + } + + resolveAdTreeRequests(vastTag, playerInstance.displayOptions.vastOptions.maxAllowedVastTagRedirects) + .then(result => { + try { + /** @see VAST 4.0 Wrapper.fallbackOnNoAd */ + const triggerFallbackOnNoAd = result.children.some(ad => + ad.tagType === 'wrapper' && ad.fallbackOnNoAd && (!/"tagType":"ad"/.test(JSON.stringify(ad)) || ad.httpError) + ); + + if (triggerFallbackOnNoAd) { + playerInstance.debugMessage('Error on VAST Wrapper, triggering fallbackOnNoAd. Ad tree:', result); + } + + result = flattenAdTree(result).map(ad => processAdCreatives(registerAdProperties(ad, tmpOptions))); + + const playableAds = getPlayableAds( + result, + playerInstance.rollsById[rollListId].maxTotalDuration || Number.MAX_SAFE_INTEGER, + playerInstance.rollsById[rollListId].maxTotalQuantity || Number.MAX_SAFE_INTEGER, + triggerFallbackOnNoAd, + ); + + (playableAds && playableAds.length) ? callback(true, playableAds) : callback(false); + } catch (error) { + callback(false); + } + }) + .catch(() => { + return callback(false); + }); + }; + + playerInstance.setVastList = () => { + const rolls = {}; + const rollsGroupedByType = { preRoll: [], postRoll: [], midRoll: [], onPauseRoll: [] }; + const def = { + id: null, + roll: null, + vastLoaded: false, + error: false, + adText: null, + adTextPosition: null, + }; + let idPart = 0; + + const validateVastList = function (item) { + let hasError = false; + + if (item.roll === 'midRoll') { + if (typeof item.timer === 'undefined') { + hasError = true; + } + } + + return hasError; + }; + + const validateRequiredParams = function (item) { + let hasError = false; + + if (!item.vastTag) { + playerInstance.announceLocalError(102, '"vastTag" property is missing from adList.'); + hasError = true; + } + + if (!item.roll) { + playerInstance.announceLocalError(102, '"roll" is missing from adList.'); + hasError = true; + } + + if (playerInstance.availableRolls.indexOf(item.roll) === -1) { + playerInstance.announceLocalError(102, 'Only ' + playerInstance.availableRolls.join(',') + ' rolls are supported.'); + hasError = true; + } + + if (item.size && playerInstance.supportedNonLinearAd.indexOf(item.size) === -1) { + playerInstance.announceLocalError(102, 'Only ' + playerInstance.supportedNonLinearAd.join(',') + ' size are supported.'); + hasError = true; + } + + return hasError; + }; + + if (playerInstance.displayOptions.vastOptions.hasOwnProperty('adList')) { + + for (let key in playerInstance.displayOptions.vastOptions.adList) { + + let rollItem = playerInstance.displayOptions.vastOptions.adList[key]; + + if (validateRequiredParams(rollItem)) { + playerInstance.announceLocalError(102, 'Wrong adList parameters.'); + continue; + } + const id = 'ID' + idPart; + + rolls[id] = Object.assign({}, def); + rolls[id] = Object.assign(rolls[id], playerInstance.displayOptions.vastOptions.adList[key]); + if (rollItem.roll === 'midRoll') { + rolls[id].error = validateVastList('midRoll', rollItem); + } + rolls[id].id = id; + rolls[id].ads = []; + idPart++; + + } + } + + // group the ads by roll + // pushing object references and forming json + Object.keys(rolls).map(function (e) { + switch (rolls[e].roll.toLowerCase()) { + case 'preRoll'.toLowerCase(): + rollsGroupedByType.preRoll.push(rolls[e]); + break; + case 'midRoll'.toLowerCase(): + rollsGroupedByType.midRoll.push(rolls[e]); + break; + case 'postRoll'.toLowerCase(): + rollsGroupedByType.postRoll.push(rolls[e]); + break; + case 'onPauseRoll'.toLowerCase(): + rollsGroupedByType.onPauseRoll.push(rolls[e]); + break; + default: + console.error(`${rolls[e].roll.toLowerCase()} is not a recognized roll`); + break; + } + }); + + playerInstance.adGroupedByRolls = rollsGroupedByType; + playerInstance.rollsById = rolls; + }; + + playerInstance.onVastAdEnded = (event) => { + if (event) { + event.stopImmediatePropagation(); + } + playerInstance.vastOptions.adFinished = true; + //"this" is the HTML5 video tag, because it dispatches the "ended" event + playerInstance.deleteVastAdElements(); + playerInstance.checkForNextAd(); + }; + + playerInstance.vastLogoBehaviour = (vastPlaying) => { + if (!playerInstance.displayOptions.layoutControls.logo.showOverAds) { + const logoHolder = playerInstance.domRef.wrapper.querySelector('.logo_holder'); + + if (!logoHolder) { + return; + } + + logoHolder.style.display = vastPlaying ? 'none' : 'inline'; + } + }; + + playerInstance.deleteVastAdElements = () => { + playerInstance.removeClickthrough(); + playerInstance.removeSkipButton(); + playerInstance.removeAdCountdown(); + playerInstance.removeAdPlayingText(); + playerInstance.removeCTAButton(); + playerInstance.vastLogoBehaviour(false); + }; +} + +export default vast diff --git a/client/fluid-player/src/modules/vpaid.js b/client/fluid-player/src/modules/vpaid.js new file mode 100644 index 0000000..6bb3f82 --- /dev/null +++ b/client/fluid-player/src/modules/vpaid.js @@ -0,0 +1,543 @@ +// VPAID support module +export default function (playerInstance, options) { + const callbacks = { + AdStarted: () => playerInstance.onStartVpaidAd, + AdStopped: () => playerInstance.onStopVpaidAd, + AdSkipped: () => playerInstance.onSkipVpaidAd, + AdLoaded: () => playerInstance.onVpaidAdLoaded, + AdLinearChange: () => playerInstance.onVpaidAdLinearChange, + AdSizeChange: () => playerInstance.onVpaidAdSizeChange, + AdExpandedChange: () => playerInstance.onVpaidAdExpandedChange, + AdSkippableStateChange: () => playerInstance.onVpaidAdSkippableStateChange, + AdDurationChange: () => playerInstance.onVpaidAdDurationChange, + AdRemainingTimeChange: () => playerInstance.onVpaidAdRemainingTimeChange, + AdVolumeChange: () => playerInstance.onVpaidAdVolumeChange, + AdImpression: () => playerInstance.onVpaidAdImpression, + AdClickThru: () => playerInstance.onVpaidAdClickThru, + AdInteraction: () => playerInstance.onVpaidAdInteraction, + AdVideoStart: () => playerInstance.onVpaidAdVideoStart, + AdVideoFirstQuartile: () => playerInstance.onVpaidAdVideoFirstQuartile, + AdVideoMidpoint: () => playerInstance.onVpaidAdVideoMidpoint, + AdVideoThirdQuartile: () => playerInstance.onVpaidAdVideoThirdQuartile, + AdVideoComplete: () => playerInstance.onVpaidAdVideoComplete, + AdUserAcceptInvitation: () => playerInstance.onVpaidAdUserAcceptInvitation, + AdUserMinimize: () => playerInstance.onVpaidAdUserMinimize, + AdUserClose: () => playerInstance.onVpaidAdUserClose, + AdPaused: () => playerInstance.onVpaidAdPaused, + AdPlaying: () => playerInstance.onVpaidAdPlaying, + AdError: () => playerInstance.onVpaidAdError, + AdLog: () => playerInstance.onVpaidAdLog + }; + + playerInstance.checkVPAIDInterface = (vpaidAdUnit) => { + const VPAIDCreative = vpaidAdUnit; + // checks if all the mandatory params present + return !!(VPAIDCreative.handshakeVersion && typeof VPAIDCreative.handshakeVersion == "function" + && VPAIDCreative.initAd && typeof VPAIDCreative.initAd == "function" + && VPAIDCreative.startAd && typeof VPAIDCreative.startAd == "function" + && VPAIDCreative.stopAd && typeof VPAIDCreative.stopAd == "function" + && VPAIDCreative.skipAd && typeof VPAIDCreative.skipAd == "function" + && VPAIDCreative.resizeAd && typeof VPAIDCreative.resizeAd == "function" + && VPAIDCreative.pauseAd && typeof VPAIDCreative.pauseAd == "function" + && VPAIDCreative.resumeAd && typeof VPAIDCreative.resumeAd == "function" + && VPAIDCreative.expandAd && typeof VPAIDCreative.expandAd == "function" + && VPAIDCreative.collapseAd && typeof VPAIDCreative.collapseAd == "function" + && VPAIDCreative.subscribe && typeof VPAIDCreative.subscribe == "function" + && VPAIDCreative.unsubscribe && typeof VPAIDCreative.unsubscribe == "function"); + }; + + // Callback for AdPaused + playerInstance.onVpaidAdPaused = () => { + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.debugMessage("onAdPaused"); + }; + + // Callback for AdPlaying + playerInstance.onVpaidAdPlaying = () => { + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.debugMessage("onAdPlaying"); + }; + + // Callback for AdError + playerInstance.onVpaidAdError = (message) => { + playerInstance.debugMessage("onAdError: " + message); + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.onVpaidEnded(); + }; + + // Callback for AdLog + playerInstance.onVpaidAdLog = (message) => { + playerInstance.debugMessage("onAdLog: " + message); + }; + + // Callback for AdUserAcceptInvitation + playerInstance.onVpaidAdUserAcceptInvitation = () => { + playerInstance.debugMessage("onAdUserAcceptInvitation"); + }; + + // Callback for AdUserMinimize + playerInstance.onVpaidAdUserMinimize = () => { + playerInstance.debugMessage("onAdUserMinimize"); + }; + + // Callback for AdUserClose + playerInstance.onVpaidAdUserClose = () => { + playerInstance.debugMessage("onAdUserClose"); + }; + + // Callback for AdUserClose + playerInstance.onVpaidAdSkippableStateChange = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.debugMessage("Ad Skippable State Changed to: " + playerInstance.vpaidAdUnit.getAdSkippableState()); + }; + + // Callback for AdUserClose + playerInstance.onVpaidAdExpandedChange = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.debugMessage("Ad Expanded Changed to: " + playerInstance.vpaidAdUnit.getAdExpanded()); + }; + + // Pass through for getAdExpanded + playerInstance.getVpaidAdExpanded = () => { + playerInstance.debugMessage("getAdExpanded"); + + if (!playerInstance.vpaidAdUnit) { + return; + } + + return playerInstance.vpaidAdUnit.getAdExpanded(); + }; + + // Pass through for getAdSkippableState + playerInstance.getVpaidAdSkippableState = () => { + playerInstance.debugMessage("getAdSkippableState"); + + if (!playerInstance.vpaidAdUnit) { + return; + } + return playerInstance.vpaidAdUnit.getAdSkippableState(); + }; + + // Callback for AdSizeChange + playerInstance.onVpaidAdSizeChange = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.debugMessage("Ad size changed to: w=" + playerInstance.vpaidAdUnit.getAdWidth() + " h=" + playerInstance.vpaidAdUnit.getAdHeight()); + }; + + // Callback for AdDurationChange + playerInstance.onVpaidAdDurationChange = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.debugMessage("Ad Duration Changed to: " + playerInstance.vpaidAdUnit.getAdDuration()); + }; + + // Callback for AdRemainingTimeChange + playerInstance.onVpaidAdRemainingTimeChange = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.debugMessage("Ad Remaining Time Changed to: " + playerInstance.vpaidAdUnit.getAdRemainingTime()); + }; + + // Pass through for getAdRemainingTime + playerInstance.getVpaidAdRemainingTime = () => { + playerInstance.debugMessage("getAdRemainingTime"); + if (!playerInstance.vpaidAdUnit) { + return; + } + return playerInstance.vpaidAdUnit.getAdRemainingTime(); + }; + + // Callback for AdImpression + playerInstance.onVpaidAdImpression = () => { + playerInstance.debugMessage("Ad Impression"); + + //Announce the impressions + playerInstance.trackSingleEvent('impression'); + }; + + // Callback for AdClickThru + playerInstance.onVpaidAdClickThru = (url, id, playerHandles) => { + playerInstance.debugMessage("Clickthrough portion of the ad was clicked"); + + // if playerHandles flag is set to true + // then player need to open click thorough url in new window + if (playerHandles) { + window.open(playerInstance.vastOptions.clickthroughUrl); + } + + playerInstance.pauseVpaidAd(); + // fire click tracking + playerInstance.callUris(playerInstance.vastOptions.clicktracking); + }; + + // Callback for AdInteraction + playerInstance.onVpaidAdInteraction = (id) => { + playerInstance.debugMessage("A non-clickthrough event has occured"); + }; + + // Callback for AdVideoStart + playerInstance.onVpaidAdVideoStart = () => { + playerInstance.debugMessage("Video 0% completed"); + playerInstance.trackSingleEvent('start'); + }; + + // Callback for AdUserClose + playerInstance.onVpaidAdVideoFirstQuartile = () => { + playerInstance.debugMessage("Video 25% completed"); + playerInstance.trackSingleEvent('firstQuartile'); + }; + + // Callback for AdUserClose + playerInstance.onVpaidAdVideoMidpoint = () => { + playerInstance.debugMessage("Video 50% completed"); + playerInstance.trackSingleEvent('midpoint'); + }; + + // Callback for AdUserClose + playerInstance.onVpaidAdVideoThirdQuartile = () => { + playerInstance.debugMessage("Video 75% completed"); + playerInstance.trackSingleEvent('thirdQuartile'); + }; + + // Callback for AdVideoComplete + playerInstance.onVpaidAdVideoComplete = () => { + playerInstance.debugMessage("Video 100% completed"); + playerInstance.trackSingleEvent('complete'); + }; + + // Callback for AdLinearChange + playerInstance.onVpaidAdLinearChange = () => { + const vpaidNonLinearSlot = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaidNonLinear_ad")[0]; + const closeBtn = playerInstance.domRef.wrapper.querySelector('.close_button'); + const adListId = vpaidNonLinearSlot.getAttribute('adlistid'); + playerInstance.debugMessage("Ad linear has changed: " + playerInstance.vpaidAdUnit.getAdLinear()); + + if (!playerInstance.vpaidAdUnit.getAdLinear()) { + return; + } + + playerInstance.backupMainVideoContentTime(adListId.split('_')[0]); + playerInstance.isCurrentlyPlayingAd = true; + + if (closeBtn) { + closeBtn.remove(); + } + + vpaidNonLinearSlot.className = 'fluid_vpaid_slot'; + playerInstance.domRef.player.loop = false; + playerInstance.domRef.player.removeAttribute('controls'); + + const progressbarContainer = playerInstance.domRef.player.parentNode.getElementsByClassName('fluid_controls_currentprogress'); + + for (let i = 0; i < progressbarContainer.length; i++) { + progressbarContainer[i].style.backgroundColor = playerInstance.displayOptions.layoutControls.adProgressColor; + } + + playerInstance.toggleLoader(false); + }; + + // Pass through for getAdLinear + playerInstance.getVpaidAdLinear = () => { + playerInstance.debugMessage("getAdLinear"); + return playerInstance.vpaidAdUnit.getAdLinear(); + }; + + // Pass through for startAd() + playerInstance.startVpaidAd = () => { + playerInstance.debugMessage("startAd"); + playerInstance.vpaidTimeoutTimerStart(); + playerInstance.vpaidAdUnit.startAd(); + }; + + // Callback for AdLoaded + playerInstance.onVpaidAdLoaded = () => { + playerInstance.debugMessage("ad has been loaded"); + // start the video play as vpaid is loaded successfully + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.startVpaidAd(); + }; + + // Callback for StartAd() + playerInstance.onStartVpaidAd = () => { + playerInstance.debugMessage("Ad has started"); + playerInstance.vpaidTimeoutTimerClear(); + }; + + // Pass through for stopAd() + playerInstance.stopVpaidAd = () => { + playerInstance.vpaidTimeoutTimerStart(); + playerInstance.vpaidAdUnit.stopAd(); + }; + + // Hard Pass through for stopAd() excluding deleteOtherVpaidAdsApart + playerInstance.hardStopVpaidAd = (ad) => { + // this is hard stop of vpaid ads + // we delete all the vpaid assets so the new one can be loaded + // delete all assets apart from the ad from deleteOtherVpaidAdsApart + if (playerInstance.vpaidAdUnit) { + playerInstance.vpaidAdUnit.stopAd(); + playerInstance.vpaidAdUnit = null; + } + + const vpaidIframes = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaid_iframe"); + const vpaidSlots = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaid_slot"); + const vpaidNonLinearSlots = playerInstance.domRef.wrapper.getElementsByClassName("fluid_vpaidNonLinear_ad"); + + for (let i = 0; i < vpaidIframes.length; i++) { + if (vpaidIframes[i].getAttribute('adListId') !== ad.id) { + vpaidIframes[i].remove(); + } + } + + for (let j = 0; j < vpaidSlots.length; j++) { + if (vpaidSlots[j].getAttribute('adListId') !== ad.id) { + vpaidSlots[j].remove(); + } + } + + for (let k = 0; k < vpaidNonLinearSlots.length; k++) { + if (vpaidNonLinearSlots[k].getAttribute('adListId') !== ad.id) { + vpaidNonLinearSlots[k].remove(); + } + } + }; + + // Callback for AdUserClose + playerInstance.onStopVpaidAd = () => { + playerInstance.debugMessage("Ad has stopped"); + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.onVpaidEnded(); + }; + + // Callback for AdUserClose + playerInstance.onSkipVpaidAd = () => { + playerInstance.debugMessage("Ad was skipped"); + + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.onVpaidEnded(); + }; + + // Passthrough for skipAd + playerInstance.skipVpaidAd = () => { + playerInstance.vpaidTimeoutTimerStart(); + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.skipAd() + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.onVpaidEnded(); + }; + + // Passthrough for setAdVolume + playerInstance.setVpaidAdVolume = (val) => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.setAdVolume(val); + }; + + // Passthrough for getAdVolume + playerInstance.getVpaidAdVolume = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + return playerInstance.vpaidAdUnit.getAdVolume(); + }; + + // Callback for AdVolumeChange + playerInstance.onVpaidAdVolumeChange = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.debugMessage("Ad Volume has changed to - " + playerInstance.vpaidAdUnit.getAdVolume()); + }; + + playerInstance.resizeVpaidAuto = () => { + if (playerInstance.vastOptions !== null && playerInstance.vastOptions.vpaid && playerInstance.vastOptions.linear) { + const adWidth = playerInstance.domRef.player.offsetWidth; + const adHeight = playerInstance.domRef.player.offsetHeight; + const mode = (playerInstance.fullscreenMode ? 'fullscreen' : 'normal'); + playerInstance.resizeVpaidAd(adWidth, adHeight, mode); + } + }; + + // Passthrough for resizeAd + playerInstance.resizeVpaidAd = (width, height, viewMode) => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.resizeAd(width, height, viewMode); + }; + + // Passthrough for pauseAd() + playerInstance.pauseVpaidAd = () => { + playerInstance.vpaidTimeoutTimerStart(); + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.pauseAd(); + }; + + // Passthrough for resumeAd() + playerInstance.resumeVpaidAd = () => { + playerInstance.vpaidTimeoutTimerStart(); + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.resumeAd(); + }; + + // Passthrough for expandAd() + playerInstance.expandVpaidAd = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.expandAd(); + }; + + // Passthrough for collapseAd() + playerInstance.collapseVpaidAd = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + playerInstance.vpaidAdUnit.collapseAd(); + }; + + playerInstance.vpaidTimeoutTimerClear = () => { + if (playerInstance.vpaidTimer) { + clearTimeout(playerInstance.vpaidTimer); + } + }; + + // placeholder for timer function + playerInstance.vpaidTimeoutTimerStart = () => { + // clear previous timer if any + playerInstance.vpaidTimeoutTimerClear(); + playerInstance.vpaidTimer = setTimeout(function () { + playerInstance.announceLocalError('901'); + playerInstance.onVpaidEnded(); + }, playerInstance.displayOptions.vastOptions.vpaidTimeout); + }; + + playerInstance.vpaidCallbackListenersAttach = () => { + // The key of the object is the event name and the value is a reference to the callback function that is registered with the creative + // Looping through the object and registering each of the callbacks with the creative + for (let eventName in callbacks) { + playerInstance.vpaidAdUnit.subscribe(callbacks[eventName](), eventName, playerInstance); + } + }; + + playerInstance.vpaidCallbackListenersDetach = () => { + if (!playerInstance.vpaidAdUnit) { + return; + } + for (let eventName in callbacks) { + playerInstance.vpaidAdUnit.unsubscribe(callbacks[eventName](), eventName, playerInstance); + } + }; + + playerInstance.loadVpaid = (ad, vpaidJsUrl) => { + const vpaidIframe = document.createElement('iframe'); + vpaidIframe.id = "fp_" + ad.id + "_fluid_vpaid_iframe"; + vpaidIframe.className = 'fluid_vpaid_iframe'; + vpaidIframe.setAttribute('adListId', ad.id); + vpaidIframe.setAttribute('frameborder', '0'); + + playerInstance.domRef.player.parentNode.insertBefore(vpaidIframe, playerInstance.domRef.player.nextSibling); + + const vpaidJsScriptElement = document.createElement('script'); + vpaidJsScriptElement.src = vpaidJsUrl; + + vpaidIframe.contentWindow.document.head.append(vpaidJsScriptElement); + + // set interval with timeout + playerInstance.tempVpaidCounter = 0; + playerInstance.getVPAIDAdInterval = setInterval(function () { + if (vpaidIframe && vpaidIframe.contentWindow) { + const fn = vpaidIframe.contentWindow['getVPAIDAd']; + + // check if JS is loaded fully in iframe + if (fn && typeof fn == 'function') { + + if (playerInstance.vpaidAdUnit) { + playerInstance.hardStopVpaidAd(ad); + } + + playerInstance.vpaidAdUnit = fn(); + clearInterval(playerInstance.getVPAIDAdInterval); + if (playerInstance.checkVPAIDInterface(playerInstance.vpaidAdUnit)) { + + if (playerInstance.getVpaidAdLinear()) { + playerInstance.isCurrentlyPlayingAd = true; + playerInstance.switchPlayerToVpaidMode(ad); + } else { + playerInstance.debugMessage('non linear vpaid ad is loaded'); + playerInstance.loadVpaidNonlinearAssets(ad); + } + + } + + } else { + + // video player will wait for 2seconds if vpaid is not loaded, then it will declare vast error and move ahead + playerInstance.tempVpaidCounter++; + if (playerInstance.tempVpaidCounter >= 20) { + clearInterval(playerInstance.getVPAIDAdInterval); + playerInstance.rollsById[ad.rollListId].error = true; + playerInstance.playMainVideoWhenVpaidFails(403); + return false; + } else { + playerInstance.debugMessage(playerInstance.tempVpaidCounter); + } + + } + } + + }, 100); + + playerInstance.destructors.push(() => clearInterval(playerInstance.getVPAIDAdInterval)); + + }; + + playerInstance.onVpaidEnded = (event) => { + if (event) { + event.stopImmediatePropagation(); + } + + if (!playerInstance.vpaidAdUnit) { + return; + } + + const vpaidSlot = playerInstance.domRef.wrapper.querySelector('.fluid_vpaid_slot'); + + playerInstance.vpaidCallbackListenersDetach(); + + playerInstance.vpaidAdUnit = null; + clearInterval(playerInstance.getVPAIDAdInterval); + + if (!!vpaidSlot) { + vpaidSlot.remove(); + } + + playerInstance.checkForNextAd(); + }; + + playerInstance.playMainVideoWhenVpaidFails = (errorCode) => { + const vpaidSlot = playerInstance.domRef.wrapper.querySelector('.fluid_vpaid_slot'); + + if (vpaidSlot) { + vpaidSlot.remove(); + } + + clearInterval(playerInstance.getVPAIDAdInterval); + playerInstance.playMainVideoWhenVastFails(errorCode); + }; +} diff --git a/client/fluid-player/src/polyfills.js b/client/fluid-player/src/polyfills.js new file mode 100644 index 0000000..b30b8eb --- /dev/null +++ b/client/fluid-player/src/polyfills.js @@ -0,0 +1,77 @@ + +import promisePolyfill from 'es6-promise'; + +// Object.assign polyfill +if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, 'assign', { + value: function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + const to = Object(target); + + for (let index = 1; index < arguments.length; index++) { + const nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (let nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + writable: true, + configurable: true + }); +} + +// CustomEvent polyfill +(function () { + if (typeof globalThis.CustomEvent === 'function') return false; + + function CustomEvent(event, params) { + params = params || {bubbles: false, cancelable: false, detail: undefined}; + const evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + } + + CustomEvent.prototype = globalThis.Event.prototype; + + globalThis.CustomEvent = CustomEvent; +})(); + +// .remove() polyfill +if ( + typeof globalThis.Element !== 'undefined' && + typeof globalThis.CharacterData !== 'undefined' && + typeof globalThis.DocumentType !== 'undefined' +) { + (function (arr) { + arr.forEach(function (item) { + if (item.hasOwnProperty('remove')) { + return; + } + Object.defineProperty(item, 'remove', { + configurable: true, + enumerable: true, + writable: true, + value: function remove() { + if (this.parentNode === null) { + return; + } + this.parentNode.removeChild(this); + } + }); + }); + })([Element.prototype, CharacterData.prototype, DocumentType.prototype]); +} + +promisePolyfill.polyfill(); diff --git a/client/fluid-player/src/static/close-icon.svg b/client/fluid-player/src/static/close-icon.svg new file mode 100644 index 0000000..71276e9 --- /dev/null +++ b/client/fluid-player/src/static/close-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/fluid-player/src/static/fluid-icons.svg b/client/fluid-player/src/static/fluid-icons.svg new file mode 100644 index 0000000..c009d6f --- /dev/null +++ b/client/fluid-player/src/static/fluid-icons.svg @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/fluid-player/src/static/fluid-spinner.svg b/client/fluid-player/src/static/fluid-spinner.svg new file mode 100644 index 0000000..a63511f --- /dev/null +++ b/client/fluid-player/src/static/fluid-spinner.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/client/fluid-player/src/static/miniplayer-toggle-off.svg b/client/fluid-player/src/static/miniplayer-toggle-off.svg new file mode 100644 index 0000000..cf84132 --- /dev/null +++ b/client/fluid-player/src/static/miniplayer-toggle-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/fluid-player/src/static/miniplayer-toggle-on.svg b/client/fluid-player/src/static/miniplayer-toggle-on.svg new file mode 100644 index 0000000..75855fc --- /dev/null +++ b/client/fluid-player/src/static/miniplayer-toggle-on.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/fluid-player/src/static/skip-backward.svg b/client/fluid-player/src/static/skip-backward.svg new file mode 100644 index 0000000..ab4a557 --- /dev/null +++ b/client/fluid-player/src/static/skip-backward.svg @@ -0,0 +1,5 @@ + + + + diff --git a/client/fluid-player/src/static/skip-forward.svg b/client/fluid-player/src/static/skip-forward.svg new file mode 100644 index 0000000..503c3fc --- /dev/null +++ b/client/fluid-player/src/static/skip-forward.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/client/fluid-player/src/types.ts b/client/fluid-player/src/types.ts new file mode 100644 index 0000000..1dd3435 --- /dev/null +++ b/client/fluid-player/src/types.ts @@ -0,0 +1,546 @@ +export interface IFluidPlayer { + domRef: DomRef; + version: string; + homepage: string; + destructors: ((this: IFluidPlayer) => void)[]; + init: (this: IFluidPlayer, playerTarget: HTMLVideoElement | string, options: unknown) => void; + getCurrentVideoDuration: () => number; + getCurrentTime: () => number; + toggleLoader: (showLoader?: boolean) => void; + sendRequest: ( + url: unknown, + withCredentials: unknown, + timeout: unknown, + functionReadyStateChange: unknown + ) => void; + displayOptions: DisplayOptions; + sendRequestAsync: ( + url: unknown, + withCredentials: unknown, + timeout: unknown + ) => Promise; + announceLocalError: (code: unknown, msg: unknown) => void; + debugMessage: (...msg: unknown[]) => void; + onMainVideoEnded: (event: unknown) => void; + isCurrentlyPlayingAd: unknown; + autoplayAfterAd: boolean; + mainVideoDuration: unknown; + adKeytimePlay: (keyTime: unknown) => void; + timer: number | null; + switchToMainVideo: () => void; + playPauseToggle: () => void; + displaySuggestedVideos: () => void; + mainVideoCurrentTime: unknown; + getCurrentSrc: () => null | string; + getCurrentSrcType: () => null | string; + onRecentWaiting: () => void; + recentWaiting: boolean; + onFluidPlayerPause: () => void; + checkShouldDisplayVolumeBar: () => boolean; + getMobileOs: () => { userOS: string }; + generateCustomControlTags: (options: unknown) => CustomControls; + detectLiveStream: () => void; + isLiveStream: () => boolean; + showLiveIndicator: () => void; + currentVideoDuration: number; + controlPlayPauseToggle: () => void; + playPauseAnimationToggle: (play: any) => void; + isSwitchingSource: boolean; + contolProgressbarUpdate: () => void; + controlDurationUpdate: () => void; + formatTime: (time: number) => void; + hlsPlayer: false | HLSPlayer; + contolVolumebarUpdate: () => void; + latestVolume: number; + fluidStorage: { fluidMute?: boolean; fluidVolume?: unknown }; + muteToggle: () => void; + checkFullscreenSupport: () => + | false + | { + goFullscreen: string; + exitFullscreen: string; + isFullscreen: string; + }; + fullscreenOff: ( + fullscreenButton: unknown[], + menuOptionFullscreen: null + ) => void; + fullscreenMode: boolean; + fullscreenOn: ( + fullscreenButton: unknown, + menuOptionFullscreen: unknown + ) => void; + fullscreenToggle: () => void; + findClosestParent: (el: unknown, selector: unknown) => null; + getTranslateX: (el: unknown) => number; + getEventOffsetX: (evt: unknown, el: unknown) => number; + getEventOffsetY: (evt: unknown, el: unknown) => number; + onProgressbarMouseDown: (event: unknown) => void; + onVolumeBarMouseDown: () => void; + findRoll: (roll: unknown) => string[] | undefined; + onKeyboardVolumeChange: (direction: unknown) => void; + onKeyboardSeekPosition: (keyCode: unknown) => void; + getNewCurrentTimeValueByKeyCode: ( + keyCode: unknown, + currentTime: unknown, + duration: unknown + ) => unknown; + handleMouseleave: (event: unknown) => void; + handleMouseenterForKeyboard: () => void; + keyboardControl: () => void; + handleWindowClick: (event: unknown) => void; + initialPlay: () => void; + setCustomControls: () => void; + createTimePositionPreview: () => void; + setCustomContextMenu: () => void; + setDefaultLayout: () => void; + initSkipControls: () => void; + handleOrientationChange: () => void; + initSkipAnimationElements: () => void; + initDoubleTapSkip: () => void; + skipRelative: (timeOffset: number) => void; + checkIfVolumebarIsRendered: () => boolean; + setLayout: () => void; + handleFullscreen: () => void; + setupPlayerWrapper: () => HTMLDivElement; + onErrorDetection: () => void; + createVideoSourceSwitch: (initialLoad?: boolean) => void; + openCloseVideoSourceSwitch: () => void; + setVideoSource: (url: unknown) => false | undefined; + setCurrentTimeAndPlay: ( + newCurrentTime: unknown, + shouldPlay: unknown + ) => void; + initTitle: () => void; + hasTitle: () => unknown; + hideTitle: () => void; + showTitle: () => void; + initLogo: () => void; + initHtmlOnPauseBlock: () => void; + initPlayButton: () => void; + mainVideoReady: () => void; + userActivityChecker: () => void; + hasControlBar: () => boolean; + isControlBarVisible: () => boolean; + setVideoPreload: () => void; + hideControlBar: () => void; + showControlBar: (event: unknown) => void; + linkControlBarUserActivity: () => void; + initMute: () => void; + initLoop: () => void; + setBuffering: () => void; + createPlaybackList: () => void; + openCloseVideoPlaybackRate: () => void; + createDownload: () => void; + theatreToggle: () => void; + defaultTheatre: () => void; + posterImage: () => void; + nextSource: () => null | undefined; + inIframe: () => boolean; + setPersistentSettings: (ignoreMute?: boolean) => false | undefined; + play: () => true | undefined; + pause: () => boolean; + skipTo: (time: unknown) => void; + setPlaybackSpeed: (speed: unknown) => void; + setVolume: (passedVolume: unknown) => void; + isCurrentlyPlayingVideo: (instance: HTMLVideoElement) => boolean; + setHtmlOnPauseBlock: (passedHtml: unknown) => false | undefined; + toggleControlBar: (show: unknown) => void; + on: (eventCall: unknown, callback: unknown) => void; + toggleLogo: (logo: unknown) => false | undefined; + trackEvent: ( + el: unknown, + evt: unknown, + sel: unknown, + handler: unknown + ) => void; + registerListener: ( + el: unknown, + evt: unknown, + sel: unknown, + handler: unknown + ) => void; + copyEvents: (topLevelEl: unknown) => void; + resetDisplayMode: ( + displayTarget: "fullScreen" | "theaterMode" | "miniPlayer" + ) => void; + destroy: () => void; + setCTAFromVast: (titleCtaElement: HTMLElement, tmpOptions: unknown) => void; + getClickThroughUrlFromLinear: (linear: HTMLElement) => unknown; + getVastAdTagUriFromWrapper: (xmlResponse: HTMLElement) => unknown; + hasInLine: (xmlResponse: HTMLElement) => number | false; + hasVastAdTagUri: (xmlResponse: HTMLElement) => number | false; + getClickThroughUrlFromNonLinear: (nonLinear: HTMLElement) => string; + getTrackingFromLinear: ( + linear: HTMLElement + ) => never[] | HTMLCollectionOf; + getDurationFromLinear: (linear: HTMLElement) => unknown; + getDurationFromNonLinear: (tag: HTMLElement) => number; + getDimensionFromNonLinear: (tag: HTMLElement) => { + width: null; + height: null; + }; + getCreativeTypeFromStaticResources: (tag: HTMLElement) => string; + getMediaFilesFromLinear: ( + linear: HTMLElement + ) => HTMLCollectionOf | never[]; + getStaticResourcesFromNonLinear: (linear: HTMLElement) => unknown[]; + extractNodeDataByTagName: ( + parentNode: HTMLElement, + tagName: string + ) => string | null; + extractNodeData: (parentNode: HTMLElement) => string; + getAdParametersFromLinear: (linear: HTMLElement) => string | null; + getMediaFileListFromLinear: (linear: HTMLElement) => unknown[]; + getIconClickThroughFromLinear: (linear: HTMLElement) => string; + getStaticResourceFromNonLinear: (linear: HTMLElement) => string | undefined; + registerTrackingEvents: ( + creativeLinear: unknown, + tmpOptions: unknown + ) => void; + registerClickTracking: ( + clickTrackingTag: unknown, + tmpOptions: unknown + ) => void; + registerViewableImpressionEvents: ( + viewableImpressionTags: unknown, + tmpOptions: unknown + ) => void; + registerImpressionEvents: ( + impressionTags: unknown, + tmpOptions: unknown + ) => void; + registerErrorEvents: (errorTags: unknown, tmpOptions: unknown) => void; + announceError: (code: unknown) => void; + getClickTrackingEvents: (linear: unknown) => string[] | undefined; + getNonLinearClickTrackingEvents: ( + nonLinear: unknown + ) => string[] | undefined; + callUris: (uris: unknown) => void; + recalculateAdDimensions: () => void; + prepareVast: (roll: unknown) => void; + playMainVideoWhenVastFails: (errorCode: unknown) => void; + switchPlayerToVastMode: () => void; + processVastWithRetries: (vastObj: unknown) => void; + processUrl: ( + vastTag: unknown, + callBack: unknown, + rollListId: unknown + ) => void; + resolveVastTag: ( + vastTag: unknown, + numberOfRedirects: unknown, + tmpOptions: unknown, + callback: unknown, + rollListId: unknown + ) => any; + setVastList: () => void; + onVastAdEnded: (event: unknown) => void; + vastLogoBehaviour: (vastPlaying: unknown) => void; + deleteVastAdElements: () => void; + renderLinearAd: (ad: unknown, backupTheVideoTime: unknown) => void; + playRoll: (adList: unknown) => void; + backupMainVideoContentTime: (rollListId: unknown) => void; + getSupportedMediaFileObject: (mediaFiles: unknown) => unknown; + getMediaFileTypeSupportLevel: ( + mediaType: unknown + ) => "maybe" | "probably" | "no" | null; + scheduleTrackingEvent: (currentTime: unknown, duration: unknown) => void; + trackSingleEvent: (eventType: unknown, eventSubType: unknown) => void; + completeNonLinearStatic: (ad: unknown) => void; + createNonLinearStatic: (ad: unknown) => void; + createVpaidNonLinearBoard: (ad: unknown) => void; + createNonLinearBoard: (ad: unknown) => void; + createBoard: (ad: unknown) => void; + closeNonLinear: (adId: unknown) => void; + rollGroupContainsLinear: (groupedRolls: unknown) => boolean; + rollGroupContainsNonlinear: (groupedRolls: unknown) => boolean; + preRollFail: () => void; + preRollSuccess: () => void; + preRollAdsPlay: () => void; + preRoll: (event: unknown) => void; + createAdMarker: (adListId: unknown, time: unknown) => void; + hideAdMarker: (adListId: unknown) => void; + showAdMarkers: () => void; + hideAdMarkers: () => void; + midRoll: (event: unknown) => void; + postRoll: (event: unknown) => void; + onPauseRoll: (event: unknown) => void; + hasValidOnPauseAd: () => unknown; + toggleOnPauseAd: () => void; + trackingOnPauseNonLinearAd: (ad: unknown, status: unknown) => void; + getLinearAdsFromKeyTime: (keyTimeLinearObj: unknown) => unknown[]; + adTimer: () => void; + scheduleTask: (task: { + time: number; + rollListId: unknown; + loadVast: unknown; + }) => void; + scheduleOnDemandRolls: () => void; + getNextAdPod: () => unknown; + checkForNextAd: () => void; + addSkipButton: () => void; + addAdCountdown: () => void; + decreaseAdCountdown: () => void; + removeAdCountdown: () => void; + toggleAdCountdown: (showing: unknown) => void; + addAdPlayingText: (textToShow: unknown) => void; + positionTextElements: (adListData: unknown) => void; + removeAdPlayingText: () => void; + addCTAButton: (landingPage: string) => unknown; + createAndAppendCTAButton: ( + adCTAText: string, + displayUrl: string, + trackingUrl: string + ) => void; + removeCTAButton: () => void; + decreaseSkipOffset: () => void; + pressSkipButton: () => void; + removeSkipButton: () => void; + addClickthroughLayer: () => void; + removeClickthrough: () => void; + isTimer: unknown; + vrROTATION_POSITION: number; + vrROTATION_SPEED: number; + vrMode: boolean; + vrPanorama: null; + vrViewer: null; + vpaidTimer: null; + vpaidAdUnit: null; + vastOptions: null; + videoPlayerId: string; + originalSrc: string | null; + firstPlayLaunched: boolean; + suppressClickthrough: boolean; + timelinePreviewData: unknown[]; + timerPool: Record; + rollsById: Record; + adPool: Record; + adGroupedByRolls: Record; + onPauseRollAdPods: unknown[]; + currentOnPauseRollAd: string; + preRollAdsResolved: boolean; + preRollAdPods: unknown[]; + preRollAdPodsLength: number; + preRollVastResolved: number; + temporaryAdPods: unknown[]; + availableRolls: ["preRoll", "midRoll", "postRoll", "onPauseRoll"]; + supportedNonLinearAd: ["300x250", "468x60", "728x90"]; + nonLinearDuration: number; + supportedStaticTypes: ["image/gif", "image/jpeg", "image/png"]; + inactivityTimeout: null; + isUserActive: null; + nonLinearVerticalAlign: "bottom"; + vpaidNonLinearCloseButton: boolean; + showTimeOnHover: boolean; + initialAnimationSet: boolean; + theatreMode: boolean; + theatreModeAdvanced: boolean; + originalWidth: number; + originalHeight: number; + dashPlayer: boolean; + dashScriptLoaded: boolean; + hlsScriptLoaded: boolean; + isPlayingMedia: boolean; + isLoading: boolean; + isInIframe: boolean; + mainVideoReadyState: boolean; + xmlCollection: unknown[]; + inLineFound: null; + fluidPseudoPause: boolean; + mobileInfo: { userOS: string }; + events: Record; + timeSkipOffsetAmount: number; + currentMediaSourceType: "source"; +} + +export interface DomRef { + player: null | HTMLVideoElement; + wrapper?: null | HTMLDivElement; +} + +export interface DisplayOptions { + onBeforeXMLHttpRequestOpen: (request: XMLHttpRequest) => void; + onBeforeXMLHttpRequest: (request: XMLHttpRequest) => void; + debug: boolean; + layoutControls: ILayoutControls; + suggestedVideos: { configUrl: null }; + vastOptions: IVastOptions; + captions: ICaptions; + hls: IHLSOptions; + modules: { + configureHls: (options: unknown) => typeof options; + onBeforeInitHls: (hls: unknown) => unknown; + onAfterInitHls: (hls: unknown) => unknown; + configureDash: (options: unknown) => typeof options; + onBeforeInitDash: (dash: unknown) => void; + onAfterInitDash: (dash: unknown) => void; + }; +} + +interface ILayoutControls { + mediaType: string | null; + primaryColor: boolean; + posterImage: boolean; + posterImageSize: "contain"; + adProgressColor: string; + playButtonShowing: boolean; + playPauseAnimation: boolean; + closeButtonCaption: "Close"; // Remove? + fillToContainer: boolean; + autoPlay: boolean; + preload: "auto"; + mute: boolean; + loop: null; + keyboardControl: boolean; + allowDownload: boolean; + playbackRateEnabled: boolean; + subtitlesEnabled: boolean; + subtitlesOnByDefault: boolean; + showCardBoardView: boolean; + showCardBoardJoystick: boolean; + allowTheatre: boolean; + doubleclickFullscreen: boolean; + autoRotateFullScreen: boolean; + theatreSettings: ITheatreOptions; + theatreAdvanced: { + theatreElement: null; + }; + title: null; + logo: ILogoOptions; + controlBar: { + autoHide: false; + autoHideTimeout: 3; + animated: true; + playbackRates: ["x2", "x1.5", "x1", "x0.5"]; + }; + timelinePreview: { + spriteImage: false; + spriteRelativePath: false; + }; + htmlOnPauseBlock: { + html: null; + height: null; + width: null; + }; + layout: "default"; //options: 'default', '' + playerInitCallback: () => void; + persistentSettings: { + volume: true; + quality: true; + speed: true; + theatre: true; + }; + controlForwardBackward: { + show: false; + doubleTapMobile: true; + }; + contextMenu: { + controls: true; + links: []; + }; + miniPlayer: { + enabled: true; + width: 400; + height: 225; + widthMobile: 50; + placeholderText: "Playing in Miniplayer"; + position: "bottom right"; + autoToggle: false; + }; + roundedCorners: 0; +} + +interface ITheatreOptions { + width: "100%"; + height: "60%"; + marginTop: number; + horizontalAlign: "center"; + keepPosition: boolean; +} + +interface ILogoOptions { + imageUrl: null; + position: "top left"; + clickUrl: null; + opacity: 1; + mouseOverImageUrl: null; + imageMargin: "2px"; + hideWithControls: false; + showOverAds: false; +} + +interface ICaptions { + play: "Play"; + pause: "Pause"; + mute: "Mute"; + unmute: "Unmute"; + fullscreen: "Fullscreen"; + subtitles: "Subtitles"; + exitFullscreen: "Exit Fullscreen"; +} + +interface IVastOptions { + adList: Record; + skipButtonCaption: "Skip ad in [seconds]"; + skipButtonClickCaption: 'Skip Ad '; + adText: null; + adTextPosition: "top left"; + adCTAText: "Visit now!"; + adCTATextPosition: "bottom right"; + adCTATextVast: boolean; + adClickable: boolean; + vastTimeout: number; + showProgressbarMarkers: boolean; + allowVPAID: boolean; + showPlayButton: boolean; + maxAllowedVastTagRedirects: number; + vpaidTimeout: number; + + vastAdvanced: { + vastLoadedCallback: () => void; + noVastVideoCallback: () => void; + vastVideoSkippedCallback: () => void; + vastVideoEndedCallback: () => void; + }; +} + +interface CustomControls { + loader: HTMLDivElement; + root: HTMLDivElement; + leftContainer: HTMLDivElement; + playPause: HTMLDivElement; + skipBack: HTMLDivElement; + skipForward: HTMLDivElement; + progressContainer: HTMLDivElement; + progressWrapper: HTMLDivElement; + progressCurrent: HTMLDivElement; + progress_current_marker: HTMLDivElement; + bufferedIndicator: HTMLDivElement; + adMarkers: HTMLDivElement; + rightContainer: HTMLDivElement; + fullscreen: HTMLDivElement; + miniPlayer: HTMLDivElement; + theatre: HTMLDivElement; + cardboard: HTMLDivElement; + subtitles: HTMLDivElement; + videoSource: HTMLDivElement; + playbackRate: HTMLDivElement; + download: HTMLDivElement; + volumeContainer: HTMLDivElement; + volume: HTMLDivElement; + volumeCurrent: HTMLDivElement; + volumeCurrentPos: HTMLDivElement; + mute: HTMLDivElement; + durationContainer: HTMLDivElement; + duration: HTMLDivElement; + live_indicator: HTMLDivElement; +} + +interface IHLSOptions { + overrideNative: boolean; +} + +interface HLSPlayer { + levels: { details: { live: unknown } }[]; +} diff --git a/client/fluid-player/test/html/custom_context.tpl.html b/client/fluid-player/test/html/custom_context.tpl.html new file mode 100644 index 0000000..7fe3938 --- /dev/null +++ b/client/fluid-player/test/html/custom_context.tpl.html @@ -0,0 +1,43 @@ + + + + + + + Custom context menu + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/dash_live.tpl.html b/client/fluid-player/test/html/dash_live.tpl.html new file mode 100644 index 0000000..eafaa76 --- /dev/null +++ b/client/fluid-player/test/html/dash_live.tpl.html @@ -0,0 +1,29 @@ + + + + + + + DASH Live Stream + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/dash_live_vast.tpl.html b/client/fluid-player/test/html/dash_live_vast.tpl.html new file mode 100644 index 0000000..d602183 --- /dev/null +++ b/client/fluid-player/test/html/dash_live_vast.tpl.html @@ -0,0 +1,44 @@ + + + + + + + DASH Live Stream with VAST + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/dash_vod.tpl.html b/client/fluid-player/test/html/dash_vod.tpl.html new file mode 100644 index 0000000..aa07ccc --- /dev/null +++ b/client/fluid-player/test/html/dash_vod.tpl.html @@ -0,0 +1,29 @@ + + + + + + + DASH VOD + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/dash_vod_vast.tpl.html b/client/fluid-player/test/html/dash_vod_vast.tpl.html new file mode 100644 index 0000000..897a590 --- /dev/null +++ b/client/fluid-player/test/html/dash_vod_vast.tpl.html @@ -0,0 +1,44 @@ + + + + + + + DASH VOD with VAST + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/e2e/ads_linear.html b/client/fluid-player/test/html/e2e/ads_linear.html new file mode 100644 index 0000000..67d6fe0 --- /dev/null +++ b/client/fluid-player/test/html/e2e/ads_linear.html @@ -0,0 +1,48 @@ + + + + + + + E2E with VAST Linear + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/e2e/controls.html b/client/fluid-player/test/html/e2e/controls.html new file mode 100644 index 0000000..661c5c4 --- /dev/null +++ b/client/fluid-player/test/html/e2e/controls.html @@ -0,0 +1,37 @@ + + + + + + + Controls + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/e2e/suggested_videos_e2e.tpl.html b/client/fluid-player/test/html/e2e/suggested_videos_e2e.tpl.html new file mode 100644 index 0000000..45d0e1d --- /dev/null +++ b/client/fluid-player/test/html/e2e/suggested_videos_e2e.tpl.html @@ -0,0 +1,42 @@ + + + + + + + Suggested videos with subtitles + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/hls_live.tpl.html b/client/fluid-player/test/html/hls_live.tpl.html new file mode 100644 index 0000000..abdf42e --- /dev/null +++ b/client/fluid-player/test/html/hls_live.tpl.html @@ -0,0 +1,29 @@ + + + + + + + HLS Live Stream + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/hls_live_vast.tpl.html b/client/fluid-player/test/html/hls_live_vast.tpl.html new file mode 100644 index 0000000..f3f120f --- /dev/null +++ b/client/fluid-player/test/html/hls_live_vast.tpl.html @@ -0,0 +1,44 @@ + + + + + + + HLS Live Stream with VAST + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/hls_vod.tpl.html b/client/fluid-player/test/html/hls_vod.tpl.html new file mode 100644 index 0000000..50c3902 --- /dev/null +++ b/client/fluid-player/test/html/hls_vod.tpl.html @@ -0,0 +1,29 @@ + + + + + + + HLS VOD + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/hls_vod_suggested_videos.html b/client/fluid-player/test/html/hls_vod_suggested_videos.html new file mode 100644 index 0000000..3eff57e --- /dev/null +++ b/client/fluid-player/test/html/hls_vod_suggested_videos.html @@ -0,0 +1,51 @@ + + + + + + + Hls VOD suggested videos + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

Hls with suggested videos

+ +
    +
  • Initial video is an mp4
  • +
      +
    • no auto option
    • +
    +
  • All suggested videos in the initial video are Hls
  • +
  • Suggested videos at the end of the second video are mp4's
  • +
      +
    • no auto option
    • +
    +
+ + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/hls_vod_vast.tpl.html b/client/fluid-player/test/html/hls_vod_vast.tpl.html new file mode 100644 index 0000000..9639690 --- /dev/null +++ b/client/fluid-player/test/html/hls_vod_vast.tpl.html @@ -0,0 +1,44 @@ + + + + + + + HLS VOD with VAST + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/player-reinitialization.tpl.html b/client/fluid-player/test/html/player-reinitialization.tpl.html new file mode 100644 index 0000000..9e0fecb --- /dev/null +++ b/client/fluid-player/test/html/player-reinitialization.tpl.html @@ -0,0 +1,82 @@ + + + + + + + Player Reinitialization + <%= htmlWebpackPlugin.tags.headTags %> + + + + +
+ +
+ + + + + +
+ +

This test is meant to check if the Fluid Player instance is properly clean up after destroying it

+ +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/skip_return.tpl.html b/client/fluid-player/test/html/skip_return.tpl.html new file mode 100644 index 0000000..4fcf410 --- /dev/null +++ b/client/fluid-player/test/html/skip_return.tpl.html @@ -0,0 +1,42 @@ + + + + + + + Skip & Return + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/special-cases/_README.md b/client/fluid-player/test/html/special-cases/_README.md new file mode 100644 index 0000000..21d95fc --- /dev/null +++ b/client/fluid-player/test/html/special-cases/_README.md @@ -0,0 +1,18 @@ +# Special cases + +Templates put here will not appear in the e2e file list, but will be accessible directly by URL in the root of the test server. +These templates should be used to share a link of an issue to an issue when creating a PR. + +To avoid conflicts with any naming, always use identifiers that point to the cause, for example: + +* Internal issues: `internal-vast-click-tracking-issue.tpl.html` +* Github issues: `issue-215.tpl.html` + +These files would be accessible in the following URLs when running the dev server: + +* http://localhost:8080/internal-vast-click-tracking-issue.html +* http://localhost:8080/issues-215.html + +To use specific static files, please create them in `test/static/special-cases`, no special naming is required. + +For more information about how these files are being loaded, check `webpack.config.js` diff --git a/client/fluid-player/test/html/special-cases/internal-vast-click-tracking-issue.html b/client/fluid-player/test/html/special-cases/internal-vast-click-tracking-issue.html new file mode 100644 index 0000000..3bc64cd --- /dev/null +++ b/client/fluid-player/test/html/special-cases/internal-vast-click-tracking-issue.html @@ -0,0 +1,38 @@ + + + + + + + Fluid player doesn't track clicks on some sites + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/special-cases/issue-702.tpl.html b/client/fluid-player/test/html/special-cases/issue-702.tpl.html new file mode 100644 index 0000000..ad76428 --- /dev/null +++ b/client/fluid-player/test/html/special-cases/issue-702.tpl.html @@ -0,0 +1,34 @@ + + + + + + + Issue 702 - CurrentTime reset after switch HLS source on IOS + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/suggested_videos_ads.tpl.html b/client/fluid-player/test/html/suggested_videos_ads.tpl.html new file mode 100644 index 0000000..6104711 --- /dev/null +++ b/client/fluid-player/test/html/suggested_videos_ads.tpl.html @@ -0,0 +1,66 @@ + + + + + + + Suggested videos with ads + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/suggested_videos_subtitles.tpl.html b/client/fluid-player/test/html/suggested_videos_subtitles.tpl.html new file mode 100644 index 0000000..cc2d5d3 --- /dev/null +++ b/client/fluid-player/test/html/suggested_videos_subtitles.tpl.html @@ -0,0 +1,57 @@ + + + + + + + Suggested videos with subtitles + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

Video with no subtitles initially, two video sources

+ + +

Video with no subtitles initially, only 1 video source

+ + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + + + diff --git a/client/fluid-player/test/html/vod_basic.tpl.html b/client/fluid-player/test/html/vod_basic.tpl.html new file mode 100644 index 0000000..b342e5e --- /dev/null +++ b/client/fluid-player/test/html/vod_basic.tpl.html @@ -0,0 +1,31 @@ + + + + + + + VOD + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_autohide.tpl.html b/client/fluid-player/test/html/vod_basic_autohide.tpl.html new file mode 100644 index 0000000..de144a1 --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_autohide.tpl.html @@ -0,0 +1,39 @@ + + + + + + + VOD auto-hide toolbar + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_by_ref.tpl.html b/client/fluid-player/test/html/vod_basic_by_ref.tpl.html new file mode 100644 index 0000000..bea153b --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_by_ref.tpl.html @@ -0,0 +1,32 @@ + + + + + + + VOD using reference + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_cta_from_config.tpl.html b/client/fluid-player/test/html/vod_basic_cta_from_config.tpl.html new file mode 100644 index 0000000..083c937 --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_cta_from_config.tpl.html @@ -0,0 +1,42 @@ + + + + + + + VOD with CTA set from Player Configurations + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_cta_from_vast.tpl.html b/client/fluid-player/test/html/vod_basic_cta_from_vast.tpl.html new file mode 100644 index 0000000..1a2b4be --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_cta_from_vast.tpl.html @@ -0,0 +1,50 @@ + + + + + + + VOD with CTA set from VAST + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_cta_from_vast_no_friendly_url.tpl.html b/client/fluid-player/test/html/vod_basic_cta_from_vast_no_friendly_url.tpl.html new file mode 100644 index 0000000..05b4ecb --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_cta_from_vast_no_friendly_url.tpl.html @@ -0,0 +1,41 @@ + + + + + + + VOD with CTA set from VAST with no friendly url + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_multiple.tpl.html b/client/fluid-player/test/html/vod_basic_multiple.tpl.html new file mode 100644 index 0000000..d8d1c05 --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_multiple.tpl.html @@ -0,0 +1,43 @@ + + + + + + + VOD two players + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_subtitles.tpl.html b/client/fluid-player/test/html/vod_basic_subtitles.tpl.html new file mode 100644 index 0000000..b61a236 --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_subtitles.tpl.html @@ -0,0 +1,35 @@ + + + + + + + VOD with subtitles + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_vr.tpl.html b/client/fluid-player/test/html/vod_basic_vr.tpl.html new file mode 100644 index 0000000..cd28953 --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_vr.tpl.html @@ -0,0 +1,34 @@ + + + + + + + VOD VR + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_vr_autoplay.tpl.html b/client/fluid-player/test/html/vod_basic_vr_autoplay.tpl.html new file mode 100644 index 0000000..f3aa9ea --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_vr_autoplay.tpl.html @@ -0,0 +1,35 @@ + + + + + + + VOD VR with Auto Play + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_vtt.tpl.html b/client/fluid-player/test/html/vod_basic_vtt.tpl.html new file mode 100644 index 0000000..5278bf6 --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_vtt.tpl.html @@ -0,0 +1,38 @@ + + + + + + + VOD with timeline thumbnails + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_basic_vtt_static.tpl.html b/client/fluid-player/test/html/vod_basic_vtt_static.tpl.html new file mode 100644 index 0000000..013db4b --- /dev/null +++ b/client/fluid-player/test/html/vod_basic_vtt_static.tpl.html @@ -0,0 +1,1857 @@ + + + + + + + VOD with timeline static-thumbnails + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_event_api.html b/client/fluid-player/test/html/vod_event_api.html new file mode 100644 index 0000000..f041938 --- /dev/null +++ b/client/fluid-player/test/html/vod_event_api.html @@ -0,0 +1,72 @@ + + + + + + + VOD with Event API callbacks + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +

Check your console for the messages beginning with typeCallback

+ +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_extended.tpl.html b/client/fluid-player/test/html/vod_extended.tpl.html new file mode 100644 index 0000000..acc8cef --- /dev/null +++ b/client/fluid-player/test/html/vod_extended.tpl.html @@ -0,0 +1,75 @@ + + + + + + + VOD extended + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_live_ad.tpl.html b/client/fluid-player/test/html/vod_live_ad.tpl.html new file mode 100644 index 0000000..63cb986 --- /dev/null +++ b/client/fluid-player/test/html/vod_live_ad.tpl.html @@ -0,0 +1,49 @@ + + + + + + + VOD with VAST Live Ad Creative + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_miniplayer.tpl.html b/client/fluid-player/test/html/vod_miniplayer.tpl.html new file mode 100644 index 0000000..bdf569d --- /dev/null +++ b/client/fluid-player/test/html/vod_miniplayer.tpl.html @@ -0,0 +1,136 @@ + + + + + + + VOD with Mini Player + <%= htmlWebpackPlugin.tags.headTags %> + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + +
+
+
+
+
+
+
+
+
+
+ +

+
+ +
+ + + + + diff --git a/client/fluid-player/test/html/vod_responsive.tpl.html b/client/fluid-player/test/html/vod_responsive.tpl.html new file mode 100644 index 0000000..1115a3b --- /dev/null +++ b/client/fluid-player/test/html/vod_responsive.tpl.html @@ -0,0 +1,69 @@ + + + + + + + VOD responsive + <%= htmlWebpackPlugin.tags.headTags %> + + + +
+

+ +

+
+
+
+
+ +
+
+ +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast__linear.html b/client/fluid-player/test/html/vod_vast__linear.html new file mode 100644 index 0000000..565c22f --- /dev/null +++ b/client/fluid-player/test/html/vod_vast__linear.html @@ -0,0 +1,60 @@ + + + + + + + VOD with VAST Linear + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast__non_linear.html b/client/fluid-player/test/html/vod_vast__non_linear.html new file mode 100644 index 0000000..f771031 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast__non_linear.html @@ -0,0 +1,60 @@ + + + + + + + VOD with VAST Non Linear + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_ad_buffet.tpl.html b/client/fluid-player/test/html/vod_vast_ad_buffet.tpl.html new file mode 100644 index 0000000..4340c3a --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_ad_buffet.tpl.html @@ -0,0 +1,42 @@ + + + + + + + VOD with VAST Ad Buffet + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_ad_buffet_with_error.tpl.html b/client/fluid-player/test/html/vod_vast_ad_buffet_with_error.tpl.html new file mode 100644 index 0000000..c379f19 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_ad_buffet_with_error.tpl.html @@ -0,0 +1,44 @@ + + + + + + + VOD with VAST Ad Buffet With Error + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

First AD doesn't have a Creative, fallbacks to second ad.

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_ad_pod.tpl.html b/client/fluid-player/test/html/vod_vast_ad_pod.tpl.html new file mode 100644 index 0000000..92045d5 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_ad_pod.tpl.html @@ -0,0 +1,42 @@ + + + + + + + VOD with VAST Ad Pod + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_ad_pod_from_wrapper.tpl.html b/client/fluid-player/test/html/vod_vast_ad_pod_from_wrapper.tpl.html new file mode 100644 index 0000000..534941d --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_ad_pod_from_wrapper.tpl.html @@ -0,0 +1,42 @@ + + + + + + + VOD with VAST Ad Pod - Loaded from a VAST Wrapper + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_ad_pod_truncated.tpl.html b/client/fluid-player/test/html/vod_vast_ad_pod_truncated.tpl.html new file mode 100644 index 0000000..e59b1b8 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_ad_pod_truncated.tpl.html @@ -0,0 +1,46 @@ + + + + + + + VOD with VAST Ad Pod - Truncated by maxTotalQuantity + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

In this scenario VAST returns an Ad Pod with 3 Ads, but we limit the Ad Pods to play only 2 Ads

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_followAdditionalWrappers_false.tpl.html b/client/fluid-player/test/html/vod_vast_followAdditionalWrappers_false.tpl.html new file mode 100644 index 0000000..1dec9b0 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_followAdditionalWrappers_false.tpl.html @@ -0,0 +1,43 @@ + + + + + + + VOD with VAST Wrapper - followAdditionalWrappers: false + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

Expected: No AD should play, as it won't follow the nested VAST Wrappers

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_followAdditionalWrappers_true.tpl.html b/client/fluid-player/test/html/vod_vast_followAdditionalWrappers_true.tpl.html new file mode 100644 index 0000000..63a3aeb --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_followAdditionalWrappers_true.tpl.html @@ -0,0 +1,41 @@ + + + + + + + VOD with VAST Wrapper - followAdditionalWrappers: true + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_on-demand.html b/client/fluid-player/test/html/vod_vast_on-demand.html new file mode 100644 index 0000000..bbffafc --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_on-demand.html @@ -0,0 +1,65 @@ + + + + + + + VOD with VAST loaded on demand + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +
    +
  • midRoll 1 (Linear) - Will be loaded on video start, since its timer is set to 3
  • +
  • midRoll 2 (Linear) - Will be loaded at 10 seconds, since its timer is set to 15
  • +
  • midRoll 3 (Non-Linear) - Will be loaded at 25 seconds, since its timer is set to 30
  • +
  • postRoll (Linear) - Will be loaded in any of the 5 last seconds of the video
  • +
+ +

Check DevTools to see the VAST requests being loaded on demand

+ +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_waterfall_false.tpl.html b/client/fluid-player/test/html/vod_vast_waterfall_false.tpl.html new file mode 100644 index 0000000..6bc8a28 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_waterfall_false.tpl.html @@ -0,0 +1,47 @@ + + + + + + + VOD with VAST Wrapper - VAST Waterfall Off (fallbackOnNoAd=false) + <%= htmlWebpackPlugin.tags.headTags %> + + + + +VOD with VAST Wrapper - VAST Waterfall Off (fallbackOnNoAd=false) +

This scenario represents the following statement from the fallbackOnNoAd description: "(...) instruction for using an available Ad when the requested VAST response returns no ads. (...) If false and the Wrapper represents an Ad in a Pod, the video player should move on to the next Ad in a Pod;".

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_waterfall_false_http_error.tpl.html b/client/fluid-player/test/html/vod_vast_waterfall_false_http_error.tpl.html new file mode 100644 index 0000000..bc87dff --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_waterfall_false_http_error.tpl.html @@ -0,0 +1,47 @@ + + + + + + + VOD with VAST Wrapper - VAST Waterfall Off (fallbackOnNoAd=false) - Http Error + <%= htmlWebpackPlugin.tags.headTags %> + + + + +VOD with VAST Wrapper - VAST Waterfall Off (fallbackOnNoAd=false) +

This scenario represents the following statement from the fallbackOnNoAd description: "(...) instruction for using an available Ad when the requested VAST response returns no ads. (...) If false and the Wrapper represents an Ad in a Pod, the video player should move on to the next Ad in a Pod;".

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_waterfall_true.tpl.html b/client/fluid-player/test/html/vod_vast_waterfall_true.tpl.html new file mode 100644 index 0000000..a3ea703 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_waterfall_true.tpl.html @@ -0,0 +1,47 @@ + + + + + + + VOD with VAST Wrapper - VAST Waterfall On (fallbackOnNoAd=true) + <%= htmlWebpackPlugin.tags.headTags %> + + + + +VOD with VAST Wrapper - VAST Waterfall On (fallbackOnNoAd=true) +

This scenario represents the following statement from the fallbackOnNoAd description: "(...) instruction for using an available Ad when the requested VAST response returns no ads. If true, the video player should select from any stand-alone ads available.".

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_waterfall_true_http_error.tpl.html b/client/fluid-player/test/html/vod_vast_waterfall_true_http_error.tpl.html new file mode 100644 index 0000000..98aeb93 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_waterfall_true_http_error.tpl.html @@ -0,0 +1,47 @@ + + + + + + + VOD with VAST Wrapper - VAST Waterfall On (fallbackOnNoAd=true) - Http Error + <%= htmlWebpackPlugin.tags.headTags %> + + + + +VOD with VAST Wrapper - VAST Waterfall On (fallbackOnNoAd=true) +

This scenario represents the following statement from the fallbackOnNoAd description: "(...) instruction for using an available Ad when the requested VAST response returns no ads. If true, the video player should select from any stand-alone ads available.".

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_wrapper.tpl.html b/client/fluid-player/test/html/vod_vast_wrapper.tpl.html new file mode 100644 index 0000000..f34fa05 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_wrapper.tpl.html @@ -0,0 +1,41 @@ + + + + + + + VOD with VAST Wrapper + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vast_wrapper_cyclical.tpl.html b/client/fluid-player/test/html/vod_vast_wrapper_cyclical.tpl.html new file mode 100644 index 0000000..0cd34b7 --- /dev/null +++ b/client/fluid-player/test/html/vod_vast_wrapper_cyclical.tpl.html @@ -0,0 +1,43 @@ + + + + + + + VOD with VAST Wrapper - Cyclical reference + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

Expected: No AD should play, as it will stop following the cyclical wrapper references

+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vpaid_linear.html b/client/fluid-player/test/html/vod_vpaid_linear.html new file mode 100644 index 0000000..deae0b8 --- /dev/null +++ b/client/fluid-player/test/html/vod_vpaid_linear.html @@ -0,0 +1,41 @@ + + + + + + + VOD with VPAID Linear + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vpaid_linear_viewableImpresson.html b/client/fluid-player/test/html/vod_vpaid_linear_viewableImpresson.html new file mode 100644 index 0000000..09ea309 --- /dev/null +++ b/client/fluid-player/test/html/vod_vpaid_linear_viewableImpresson.html @@ -0,0 +1,80 @@ + + + + + + + VOD with VPAID Linear Viewable Impression + <%= htmlWebpackPlugin.tags.headTags %> + + + + +

Viewable impression tests

+ +

Regular scenario: Here you should see the viewable impression being called in the network tab

+ + + +

Player off screen, so no viewable impression is going to be called

+

Order of what's happening

+
    +
  • Player starts -> 2 seconds later it will go off screen
  • +
  • Player starts playing ad after 5 seconds, there should be no viewable impression
  • +
  • The moment you scroll down to the player, the viewable impression should be called in the network tab (if the ad is still rolling)
  • +
+ + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/html/vod_vpaid_non_linear.html b/client/fluid-player/test/html/vod_vpaid_non_linear.html new file mode 100644 index 0000000..1cf6dfd --- /dev/null +++ b/client/fluid-player/test/html/vod_vpaid_non_linear.html @@ -0,0 +1,41 @@ + + + + + + + VOD with VPAID Non Linear + <%= htmlWebpackPlugin.tags.headTags %> + + + + + + +<%= htmlWebpackPlugin.tags.bodyTags %> + + + + + diff --git a/client/fluid-player/test/index.html b/client/fluid-player/test/index.html new file mode 100644 index 0000000..984a07b --- /dev/null +++ b/client/fluid-player/test/index.html @@ -0,0 +1,57 @@ + + + + + + + Fluid Player E2E test case index listing + + + +
+
+

+ Fluid Player E2E cases +

+

+ Note: you need to restart the dev-server for new cases to be listed here. +

+
+
+ +
+
+ + diff --git a/client/fluid-player/test/static/logo.png b/client/fluid-player/test/static/logo.png new file mode 100644 index 0000000..e001c5a Binary files /dev/null and b/client/fluid-player/test/static/logo.png differ diff --git a/client/fluid-player/test/static/special-cases/fp-215-2.xml b/client/fluid-player/test/static/special-cases/fp-215-2.xml new file mode 100644 index 0000000..757e314 --- /dev/null +++ b/client/fluid-player/test/static/special-cases/fp-215-2.xml @@ -0,0 +1,108 @@ + + + + Fluid Player + Exclusive Offer - Join Now + + + + + + + + + 00:00:30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + <Subtitle/> + <Button/> + <Url/> + <Expand>false</Expand> + </CTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/special-cases/fp-215.xml b/client/fluid-player/test/static/special-cases/fp-215.xml new file mode 100644 index 0000000..11a8e73 --- /dev/null +++ b/client/fluid-player/test/static/special-cases/fp-215.xml @@ -0,0 +1,37 @@ +<VAST version="3.0"> + <Ad id="6"> + <Wrapper> + <AdSystem>Fluid Player</AdSystem> + <VASTAdTagURI> + <![CDATA[ static/special-cases/fp-215-2.xml ]]> + </VASTAdTagURI> + <Impression id="exotr"> + <![CDATA[ /wrapper-impression ]]> + </Impression> + <Error> + <![CDATA[ /wrapper-error ]]> + </Error> + <Creatives> + <Creative sequence="1" id="10"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"> + <![CDATA[ /wrapper-progress ]]> + </Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking> + <![CDATA[ /wrapper-click ]]> + </ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension type="waterfall" fallback_index="0"> + <Extension/> + </Extension> + </Extensions> + </Wrapper> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/subtitles/deutsch.vtt b/client/fluid-player/test/static/subtitles/deutsch.vtt new file mode 100644 index 0000000..f7d8e40 --- /dev/null +++ b/client/fluid-player/test/static/subtitles/deutsch.vtt @@ -0,0 +1,309 @@ +WEBVTT + +1 +00:00:15.042 --> 00:00:18.042 align:start +<v Proog>Auf der linken Seite sehen wir...</v> + +2 +00:00:18.750 --> 00:00:20.333 align:middle +<v Proog>Auf der rechten Seite sehen wir die...</v> + +3 +00:00:20.417 --> 00:00:21.917 +<v Proog>...die <c.highlight>Enthaupter</c>.</v> + +4 +00:00:22.000 --> 00:00:24.625 align:end +<v Proog>Alles ist sicher. Vollkommen sicher.</v> + +5 +00:00:26.333 --> 00:00:27.333 +<v Proog>Emo?</v> + +6 +00:00:28.875 --> 00:00:30.250 line:6% size:110% +<v Proog>Pass auf!</v> + +7 +00:00:47.125 --> 00:00:48.250 +<v Proog>Bist du verletzt?</v> + +8 +00:00:51.917 --> 00:00:53.917 +<v Emo>Ich glaube nicht. Und du?</v> + +9 +00:00:55.625 --> 00:00:57.125 +<v Proog>Mir fehlt nichts.</v> + +10 +00:00:57.583 --> 00:01:01.667 +<v Proog>Steh auf! Emo, es ist gefährlich hier.</v> + +11 +00:01:02.208 --> 00:01:03.667 +<v Proog>Weiter!</v> + +12 +00:01:03.750 --> 00:01:05.750 +<v Emo>Was jetzt?</v> + +13 +00:01:05.875 --> 00:01:07.875 +<v Proog>Du wirst es sehen.</v> + +14 +00:01:16.167 --> 00:01:18.375 +<v Proog>Emo, hier lang.</v> + +15 +00:01:34.958 --> 00:01:35.792 +<v Proog>Mir nach!</v> + +16 +00:02:11.583 --> 00:02:12.792 +<v Proog>Schneller, Emo!</v> + +17 +00:02:48.375 --> 00:02:50.083 +<v Proog>Du bist unaufmerksam!</v> + +18 +00:02:50.750 --> 00:02:54.500 +<v Emo>Ich wollte doch nur an... ...ans Telefon gehen.</v> + +19 +00:02:55.000 --> 00:02:58.208 +<v Proog>Emo, schau, ich meine, hör zu.</v> + +20 +00:02:59.750 --> 00:03:02.292 +<v Proog>Du musst lernen zuzuhören.</v> + +21 +00:03:03.625 --> 00:03:05.125 +<v Proog>Das hier ist kein Spiel.</v> + +22 +00:03:06.167 --> 00:03:08.750 +<v Proog>u, wir, könnten hier draußen leicht sterben.</v> + +23 +00:03:10.208 --> 00:03:14.125 +<v Proog>Hör zu... Hör dem Klang der Maschine zu.</v> + +24 +00:03:18.333 --> 00:03:20.417 +<v Proog>Hör auf deinen Atem.</v> + +25 +00:04:27.208 --> 00:04:29.250 +<v Emo>Hast du nie genug davon?</v> + +26 +00:04:29.583 --> 00:04:31.083 +<v Proog>Genug?!?</v> + +27 +00:04:31.750 --> 00:04:34.667 +<v Proog>Die Maschine ist wie ein Uhrwerk.</v> + +28 +00:04:35.500 --> 00:04:37.708 +<v Proog>Ein falscher Schritt...</v> + +29 +00:04:37.833 --> 00:04:39.792 +<v Proog>...und du wirst zerquetscht.</v> + +30 +00:04:41.042 --> 00:04:42.375 +<v Emo>Aber ist es nicht...</v> + +31 +00:04:42.417 --> 00:04:46.542 +<v Proog>Zerquetscht, Emo! Willst du das? Zerquetscht werden?</v> + +32 +00:04:48.083 --> 00:04:50.000 +<v Proog>Dein Lebensziel?</v> + +33 +00:04:50.583 --> 00:04:52.250 +<v Proog>Zerquetscht!</v> + +34 +00:05:41.833 --> 00:05:43.458 +<v Proog>Emo, schließ die Augen.</v> + +35 +00:05:44.917 --> 00:05:46.583 +<v Emo>Warum? - </v><v Proog>Sofort!</v> + +36 +00:05:53.750 --> 00:05:56.042 +<v Emo>Gut.</v> + +37 +00:05:59.542 --> 00:06:02.792 +<v Proog>Was siehst du zu deiner Linken, Emo?</v> + +38 +00:06:04.417 --> 00:06:06.000 +<v Emo>Nichts. - </v><v Proog>Wirklich?</v> + +39 +00:06:06.333 --> 00:06:07.917 +<v Emo>Überhaupt nichts.</v> + +40 +00:06:08.042 --> 00:06:12.417 +<v Proog>Und zu deiner Rechten, was siehst du zu deiner Rechten, Emo?</v> + +41 +00:06:13.875 --> 00:06:16.917 +<v Proog>Dasselbe, Proog, genau dasselbe...</v> + +42 +00:06:17.083 --> 00:06:18.583 +<v Emo>Nichts!</v> + +43 +00:06:40.625 --> 00:06:42.958 +<v Emo>Hör mal, Proog! Hörst du das?</v> + +44 +00:06:43.625 --> 00:06:45.042 +<v Emo>Können wir hier hingehen?</v> + +45 +00:06:45.208 --> 00:06:48.042 +<v Proog>Dorthin? Das ist gefährlich.</v> + +46 +00:06:49.917 --> 00:06:52.500 +<v Emo>Aber... - </v><v Proog>Vertrau mir, es ist gefährlich.</v> + +47 +00:06:53.292 --> 00:06:54.792 +<v Emo>Vielleicht könnte ich...</v> + +48 +00:06:54.833 --> 00:06:56.333 +<v Proog>Nein.</v> + +49 +00:06:57.667 --> 00:07:00.167 +<v Proog>NEIN!</v> + +50 +00:07:00.875 --> 00:07:03.750 +<v Proog>Sonst noch Fragen, Emo?</v> + +51 +00:07:04.250 --> 00:07:05.917 +<v Emo>Nein.</v> + +52 +00:07:09.458 --> 00:07:10.833 +<v Proog>Emo.</v> + +53 +00:07:11.875 --> 00:07:13.542 +<v Proog>Emo, warum...</v> + +54 +00:07:13.583 --> 00:07:14.458 +<v Proog>Emo...</v> + +55 +00:07:14.500 --> 00:07:18.500 +<v Proog>...warum erkennst du nicht die Schönheit dieses Ortes?</v> + +56 +00:07:18.833 --> 00:07:20.750 +<v Proog>ie alles funktioniert.</v> + +57 +00:07:20.875 --> 00:07:24.000 +<v Proog>Wie vollkommen es ist.</v> + +58 +00:07:24.083 --> 00:07:27.417 +<v Emo>Nein, Proog, ich erkenne nichts.</v> + +59 +00:07:27.542 --> 00:07:30.333 +<v Emo>Ich erkenne nichts, weil da nichts ist.</v> + +60 +00:07:31.500 --> 00:07:35.333 +<v Emo>Und warum sollte ich mein Leben etwas anvertrauen, das gar nicht da ist?</v> + +61 +00:07:35.583 --> 00:07:37.042 +<v Emo>Kannst du mir das sagen? - </v><v Proog>Emo...</v> + +62 +00:07:37.500 --> 00:07:39.167 +<v Emo>Antworte mir!</v> + +63 +00:07:43.208 --> 00:07:44.583 +<v Emo>Proog...</v> + +64 +00:07:45.500 --> 00:07:47.333 +<v Emo>Du bist krank, Mann!</v> + +65 +00:07:47.375 --> 00:07:49.208 +<v Emo>Bleib weg von mir!</v> + +66 +00:07:52.583 --> 00:07:55.083 +<v Proog>Nein! Emo! Das ist eine Falle!</v> + +67 +00:07:55.833 --> 00:07:57.167 +<v Emo>Haha, eine Falle.</v> + +68 +00:07:57.208 --> 00:08:01.750 +<v Emo>Auf der linken Seite sieht man die Hängenden Gärten von Babylon.</v> + +69 +00:08:02.250 --> 00:08:04.292 +<v Emo>Wie wär das als Falle?</v> + +70 +00:08:05.458 --> 00:08:07.125 +<v Proog>Nein, Emo.</v> + +71 +00:08:09.417 --> 00:08:12.792 +<v Emo>Auf der rechten Seite sieht man... ...rate mal...</v> + +72 +00:08:13.000 --> 00:08:14.750 +<v Emo>...den Koloss von Rhodos!</v> + +73 +00:08:15.833 --> 00:08:16.708 +<v Proog>Nein!</v> + +74 +00:08:16.750 --> 00:08:22.167 +<v Emo>Den Koloss von Rhodos und er ist nur für dich hier, Proog.</v> + +75 +00:08:51.333 --> 00:08:53.167 +<v Proog>Es ist da...</v> + +76 +00:08:53.208 --> 00:08:55.500 +<v Proog>Wenn ich es dir doch sage, Emo...</v> + +77 +00:08:57.333 --> 00:09:00.000 +<v Proog>...es ist da.</v> \ No newline at end of file diff --git a/client/fluid-player/test/static/subtitles/english.vtt b/client/fluid-player/test/static/subtitles/english.vtt new file mode 100644 index 0000000..7d3e2cc --- /dev/null +++ b/client/fluid-player/test/static/subtitles/english.vtt @@ -0,0 +1,357 @@ +WEBVTT + +1 +00:00:15.000 --> 00:00:18.000 align:start +<v Proog>At the left we can see...</v> + +2 +00:00:18.167 --> 00:00:20.083 align:middle +<v Proog>At the right we can see the...</v> + +3 +00:00:20.083 --> 00:00:22.000 +<v Proog>...the <c.highlight>head-snarlers</c></v> + +4 +00:00:22.000 --> 00:00:24.417 align:end +<v Proog>Everything is safe. Perfectly safe.</v> + +5 +00:00:24.583 --> 00:00:27.083 +<v Proog>Emo?</v> + +6 +00:00:28.208 --> 00:00:30.042 line:6% size:110% +<v Proog><b>Watch out!</b></v> + +7 +00:00:47.042 --> 00:00:48.542 +<v Proog>Are you hurt?</v> + +8 +00:00:52.000 --> 00:00:54.000 +<v Emo>I don't think so. You?</v> + +9 +00:00:55.167 --> 00:00:57.042 +<v Proog>I'm Ok.</v> + +10 +00:00:57.125 --> 00:01:01.167 +<v Proog>Get up. Emo, it's not safe here.</v> + +11 +00:01:02.042 --> 00:01:03.167 +<v Proog>Let's go.</v> + +12 +00:01:03.167 --> 00:01:05.167 +<v Emo>What's next?</v> + +13 +00:01:05.208 --> 00:01:09.208 +<v Proog>You'll see!</v> + +14 +00:01:12.000 --> 00:01:14.000 +(howling wind) + +15 +00:01:16.042 --> 00:01:18.083 +<v Proog>Emo. This way.</v> + +16 +00:01:34.250 --> 00:01:35.542 +<v Proog>Follow me!</v> + +17 +00:01:39.000 --> 00:01:42.000 +(buzzing wires and chattery conversations) + +18 +00:02:11.125 --> 00:02:12.542 +<v Proog>Hurry Emo!</v> + +19 +00:02:20.292 --> 00:02:22.792 +(louder telephone voices) + +20 +00:02:32.000 --> 00:02:34.500 +(phone ringing) + +21 +00:02:48.083 --> 00:02:50.000 +<v Proog>You're not paying attention!</v> + +22 +00:02:50.167 --> 00:02:54.125 +<v Emo>I just want to answer the... ...phone.</v> + +23 +00:02:55.000 --> 00:02:58.042 +<v Proog>Emo, look, I mean listen.</v> + +24 +00:02:59.167 --> 00:03:02.083 +<v Proog>You have to learn to listen.</v> + +25 +00:03:03.167 --> 00:03:05.042 +<v Proog>This is not some game.</v> + +26 +00:03:05.083 --> 00:03:09.417 +<v Proog>You, I mean we, we could easily die out here.</v> + +27 +00:03:10.042 --> 00:03:14.042 +<v Proog>Listen, listen to the sounds of the machine.</v> + +28 +00:03:18.083 --> 00:03:20.083 +<v Proog>Listen to your breathing.</v> + +29 +00:03:27.000 --> 00:03:29.000 +(Buzzing wires) + +30 +00:03:34.500 --> 00:03:36.500 +(laughing) + +31 +00:04:13.417 --> 00:04:15.417 +(oriental dance music) + +32 +00:04:27.042 --> 00:04:29.042 +<v Emo>Well, don't you ever get tired of this?</v> + +33 +00:04:29.125 --> 00:04:31.000 +<v Proog>Tired?!?</v> + +34 +00:04:31.167 --> 00:04:34.583 +<v Proog>Emo, the machine is like clockwork.</v> + +35 +00:04:35.125 --> 00:04:37.167 +<v Proog>One move out of place...</v> + +36 +00:04:37.208 --> 00:04:39.208 +<v Proog>...and you're ground to a pulp.</v> + +37 +00:04:41.000 --> 00:04:42.083 +<v Emo>But isn't it -</v> + +38 +00:04:42.083 --> 00:04:46.125 +<v Proog>Pulp, Emo! Is that what you want, pulp?</v> + +39 +00:04:47.083 --> 00:04:49.083 +<v Proog>Emo, your goal in life...</v> + +40 +00:04:50.125 --> 00:04:52.042 +<v Proog>...pulp?</v> + +41 +00:05:08.000 --> 00:05:10.500 +(loud metal sounds) + +42 +00:05:41.208 --> 00:05:43.125 +<v Proog>Emo, close your eyes.</v> + +43 +00:05:44.208 --> 00:05:46.125 +<v Emo>Why?</v> - <v Proog>Now!</v> + +44 +00:05:51.208 --> 00:05:52.208 +<v Emo>Ok.</v> + +45 +00:05:53.167 --> 00:05:54.792 +<v Proog>Good.</v> + +46 +00:05:59.125 --> 00:06:02.208 +<v Proog>What do you see at your left side, Emo?</v> + +47 +00:06:04.083 --> 00:06:06.000 +<v Emo>Nothing.</v> - <v Proog>Really?</v> + +48 +00:06:06.083 --> 00:06:07.208 +<v Emo>No, nothing at all.</v> + +49 +00:06:08.000 --> 00:06:12.083 +<v Proog>And at your right, what do you see at your right side, Emo?</v> + +50 +00:06:13.208 --> 00:06:16.208 +<v Emo>The same Proog, exactly the same...</v> + +51 +00:06:17.000 --> 00:06:19.208 +<v Emo>...nothing!</v> - <v Proog>Great.</v> + +52 +00:06:25.208 --> 00:06:27.208 +(sound of camera flash) + +53 +00:06:29.792 --> 00:06:31.792 +(engine drone) + +54 +00:06:40.167 --> 00:06:42.833 +<v Emo>Listen Proog! Do you hear that!</v> (amusement park music) + +55 +00:06:43.167 --> 00:06:45.000 +<v Emo>Can we go here?</v> + +56 +00:06:45.042 --> 00:06:48.000 +<v Proog>There? It isn't safe, Emo.</v> + +57 +00:06:49.208 --> 00:06:52.125 +<v Emo>But...</v> - <v Proog>Trust me, it's not.</v> + +58 +00:06:53.083 --> 00:06:54.208 +<v Emo>Maybe I could...</v> + +59 +00:06:54.208 --> 00:06:56.083 +<v Proog>No.</v> + +60 +00:06:57.167 --> 00:07:00.042 +<v Proog>NO!</v> + +61 +00:07:00.208 --> 00:07:03.167 +<v Proog>Any further questions, Emo?</v> + +62 +00:07:04.042 --> 00:07:05.208 +<v Emo>No.</v> + +63 +00:07:09.125 --> 00:07:10.208 +<v Proog>Emo?</v> + +64 +00:07:11.208 --> 00:07:13.125 +<v Proog>Emo, why...</v> + +65 +00:07:13.125 --> 00:07:14.125 +<v Proog>Emo...</v> + +66 +00:07:14.125 --> 00:07:18.125 +<v Proog>...why can't you see the beauty of this place?</v> + +67 +00:07:18.208 --> 00:07:20.167 +<v Proog>The way it works.</v> + +68 +00:07:20.208 --> 00:07:24.000 +<v Proog>How perfect it is.</v> + +69 +00:07:24.000 --> 00:07:27.083 +<v Emo>No, Proog, I don't see.</v> + +70 +00:07:27.125 --> 00:07:30.083 +<v Emo>I don't see because there's nothing there.</v> + +71 +00:07:31.125 --> 00:07:35.083 +<v Emo>And why should I trust my life to something that isn't there?</v> + +72 +00:07:35.125 --> 00:07:37.042 +<v Emo>Well can you tell me that?</v> - <v Proog>Emo...</v> + +73 +00:07:37.125 --> 00:07:39.042 +<v Emo>Answer me!</v> + +74 +00:07:43.042 --> 00:07:44.125 +<v Emo>Proog...</v> + +75 +00:07:45.125 --> 00:07:47.083 +<v Emo>...you're a sick man!</v> + +76 +00:07:47.083 --> 00:07:49.042 +<v Emo>Stay away from me!</v> + +77 +00:07:52.125 --> 00:07:55.000 +<v Proog>No! Emo! It's a trap!</v> + +78 +00:07:55.208 --> 00:07:57.042 +<v Emo>Hah, it's a trap.</v> + +79 +00:07:57.042 --> 00:08:01.167 +<v Emo>At the left side you can see the hanging gardens of Babylon!</v> + +80 +00:08:02.042 --> 00:08:04.083 +<v Emo>How's that for a trap?</v> + +81 +00:08:05.125 --> 00:08:07.042 +<v Proog>No, Emo.</v> + +82 +00:08:09.083 --> 00:08:12.208 +<v Emo>At the right side you can see... ...well guess what...</v> + +83 +00:08:13.000 --> 00:08:14.792 +<v Emo>...the colossus of Rhodes!</v> + +84 +00:08:15.208 --> 00:08:16.167 +<v Proog>No!</v> + +85 +00:08:16.167 --> 00:08:22.042 +<v Emo>The colossus of Rhodes and it is here just for you Proog.</v> + +86 +00:08:51.083 --> 00:08:53.042 +<v Proog>It is there...</v> + +87 +00:08:53.042 --> 00:08:56.167 +<v Proog>I'm telling you, Emo...</v> + +88 +00:08:57.083 --> 00:09:00.000 +<v Proog>...it is, it is.</v> + +89 +00:09:05.000 --> 00:09:07.500 +(howling wind) \ No newline at end of file diff --git a/client/fluid-player/test/static/suggested_videos_example_v1.json b/client/fluid-player/test/static/suggested_videos_example_v1.json new file mode 100644 index 0000000..5e6950a --- /dev/null +++ b/client/fluid-player/test/static/suggested_videos_example_v1.json @@ -0,0 +1,396 @@ +[ + { + "id": 0, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + }, + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "mimeType": "video/mp4", + "resolution": "480p" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "Big Buck Bunny", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 1, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + }, + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + "mimeType": "video/mp4", + "resolution": "1080p", + "hd": "true" + } + ], + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg", + "title": "Elephant Dream", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 2, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerBlazes.jpg", + "title": "For Bigger Blazes", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 3, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerEscapes.jpg", + "title": "For Bigger Escape", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en", + "default": true + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de" + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 4, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerFun.jpg", + "title": "For Bigger Fun", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 5, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerJoyrides.jpg", + "title": "For Bigger Joyrides", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 6, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerMeltdowns.jpg", + "title": "For Bigger Meltdowns", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 7, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/Sintel.jpg", + "title": "Sintel", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 8, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/SubaruOutbackOnStreetAndDirt.jpg", + "title": "Subaru Outback On Street And Dirt", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 9, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/TearsOfSteel.jpg", + "title": "Tears of Steel", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 10, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/VolkswagenGTIReview.jpg", + "title": "Volkswagen GTI Review", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 11, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/WeAreGoingOnBullrun.jpg", + "title": "We Are Going On Bullrun", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 12, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/WhatCarCanYouGetForAGrand.jpg", + "title": "What care can you get for a grand?", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + } +] \ No newline at end of file diff --git a/client/fluid-player/test/static/suggested_videos_example_v2.json b/client/fluid-player/test/static/suggested_videos_example_v2.json new file mode 100644 index 0000000..706f9e0 --- /dev/null +++ b/client/fluid-player/test/static/suggested_videos_example_v2.json @@ -0,0 +1,383 @@ +[ + { + "id": 3, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerEscapes.jpg", + "title": "For Bigger Escape", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en", + "default": true + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de" + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 10, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/VolkswagenGTIReview.jpg", + "title": "Volkswagen GTI Review", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 2, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerBlazes.jpg", + "title": "For Bigger Blazes", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 4, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerFun.jpg", + "title": "For Bigger Fun", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 11, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/WeAreGoingOnBullrun.jpg", + "title": "We Are Going On Bullrun", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 6, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerMeltdowns.jpg", + "title": "For Bigger Meltdowns", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 7, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/Sintel.jpg", + "title": "Sintel", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 0, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + }, + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", + "mimeType": "video/mp4", + "resolution": "480p" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "Big Buck Bunny", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 8, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/SubaruOutbackOnStreetAndDirt.jpg", + "title": "Subaru Outback On Street And Dirt", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 9, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/TearsOfSteel.jpg", + "title": "Tears of Steel", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 12, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/WhatCarCanYouGetForAGrand.jpg", + "title": "What care can you get for a grand?", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl" + }, + { + "label": "Deutsch", + "url": "/static/subtitles/deutsch.vtt", + "lang": "de", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 1, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + }, + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", + "mimeType": "video/mp4", + "resolution": "1080p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg", + "title": "Elephant Dream", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 5, + "sources": [ + { + "url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + "mimeType": "video/mp4", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ForBiggerJoyrides.jpg", + "title": "For Bigger Joyrides", + "subtitles": [ + { + "label": "English", + "url": "/static/subtitles/english.vtt", + "lang": "en" + }, + { + "label": "Nederlands", + "url": "/static/subtitles/deutsch.vtt", + "lang": "nl", + "default": true + } + ], + "configUrl" : "/static/suggested_videos_example_v2.json" + } +] \ No newline at end of file diff --git a/client/fluid-player/test/static/suggested_videos_example_v3.json b/client/fluid-player/test/static/suggested_videos_example_v3.json new file mode 100644 index 0000000..8c74832 --- /dev/null +++ b/client/fluid-player/test/static/suggested_videos_example_v3.json @@ -0,0 +1,170 @@ +[ + { + "id": 0, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 1, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 2, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 3, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 4, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 5, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 6, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 7, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 8, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 9, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 10, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + }, + { + "id": 11, + "sources": [ + { + "url": "https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.ism/.m3u8", + "mimeType": "application/x-mpegurl", + "resolution": "720p", + "hd": "true" + } + ], + "thumbnailUrl": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg", + "title": "HLS VOD", + "configUrl" : "/static/suggested_videos_example_v2.json" + } +] \ No newline at end of file diff --git a/client/fluid-player/test/static/thumbnails.jpg b/client/fluid-player/test/static/thumbnails.jpg new file mode 100644 index 0000000..32269b1 Binary files /dev/null and b/client/fluid-player/test/static/thumbnails.jpg differ diff --git a/client/fluid-player/test/static/thumbnails.vtt b/client/fluid-player/test/static/thumbnails.vtt new file mode 100644 index 0000000..8b95e4c --- /dev/null +++ b/client/fluid-player/test/static/thumbnails.vtt @@ -0,0 +1,607 @@ +WEBVTT + +00:00:00.000 --> 00:00:00.500 +/static/thumbnails.jpg#xywh=0,0,200,84 + +00:00:00.500 --> 00:00:01.000 +/static/thumbnails.jpg#xywh=200,0,200,84 + +00:00:01.000 --> 00:00:01.500 +/static/thumbnails.jpg#xywh=400,0,200,84 + +00:00:01.500 --> 00:00:02.000 +/static/thumbnails.jpg#xywh=600,0,200,84 + +00:00:02.000 --> 00:00:02.500 +/static/thumbnails.jpg#xywh=800,0,200,84 + +00:00:02.500 --> 00:00:03.000 +/static/thumbnails.jpg#xywh=1000,0,200,84 + +00:00:03.000 --> 00:00:03.500 +/static/thumbnails.jpg#xywh=1200,0,200,84 + +00:00:03.500 --> 00:00:04.000 +/static/thumbnails.jpg#xywh=1400,0,200,84 + +00:00:04.000 --> 00:00:04.500 +/static/thumbnails.jpg#xywh=1600,0,200,84 + +00:00:04.500 --> 00:00:05.000 +/static/thumbnails.jpg#xywh=1800,0,200,84 + +00:00:05.000 --> 00:00:05.500 +/static/thumbnails.jpg#xywh=2000,0,200,84 + +00:00:05.500 --> 00:00:06.000 +/static/thumbnails.jpg#xywh=2200,0,200,84 + +00:00:06.000 --> 00:00:06.500 +/static/thumbnails.jpg#xywh=2400,0,200,84 + +00:00:06.500 --> 00:00:07.000 +/static/thumbnails.jpg#xywh=2600,0,200,84 + +00:00:07.000 --> 00:00:07.500 +/static/thumbnails.jpg#xywh=2800,0,200,84 + +00:00:07.500 --> 00:00:08.000 +/static/thumbnails.jpg#xywh=3000,0,200,84 + +00:00:08.000 --> 00:00:08.500 +/static/thumbnails.jpg#xywh=3200,0,200,84 + +00:00:08.500 --> 00:00:09.000 +/static/thumbnails.jpg#xywh=3400,0,200,84 + +00:00:09.000 --> 00:00:09.500 +/static/thumbnails.jpg#xywh=3600,0,200,84 + +00:00:09.500 --> 00:00:10.000 +/static/thumbnails.jpg#xywh=3800,0,200,84 + +00:00:10.000 --> 00:00:10.500 +/static/thumbnails.jpg#xywh=4000,0,200,84 + +00:00:10.500 --> 00:00:11.000 +/static/thumbnails.jpg#xywh=4200,0,200,84 + +00:00:11.000 --> 00:00:11.500 +/static/thumbnails.jpg#xywh=4400,0,200,84 + +00:00:11.500 --> 00:00:12.000 +/static/thumbnails.jpg#xywh=4600,0,200,84 + +00:00:12.000 --> 00:00:12.500 +/static/thumbnails.jpg#xywh=4800,0,200,84 + +00:00:12.500 --> 00:00:13.000 +/static/thumbnails.jpg#xywh=5000,0,200,84 + +00:00:13.000 --> 00:00:13.500 +/static/thumbnails.jpg#xywh=5200,0,200,84 + +00:00:13.500 --> 00:00:14.000 +/static/thumbnails.jpg#xywh=5400,0,200,84 + +00:00:14.000 --> 00:00:14.500 +/static/thumbnails.jpg#xywh=5600,0,200,84 + +00:00:14.500 --> 00:00:15.000 +/static/thumbnails.jpg#xywh=5800,0,200,84 + +00:00:15.000 --> 00:00:15.500 +/static/thumbnails.jpg#xywh=6000,0,200,84 + +00:00:15.500 --> 00:00:16.000 +/static/thumbnails.jpg#xywh=6200,0,200,84 + +00:00:16.000 --> 00:00:16.500 +/static/thumbnails.jpg#xywh=6400,0,200,84 + +00:00:16.500 --> 00:00:17.000 +/static/thumbnails.jpg#xywh=6600,0,200,84 + +00:00:17.000 --> 00:00:17.500 +/static/thumbnails.jpg#xywh=6800,0,200,84 + +00:00:17.500 --> 00:00:18.000 +/static/thumbnails.jpg#xywh=7000,0,200,84 + +00:00:18.000 --> 00:00:18.500 +/static/thumbnails.jpg#xywh=7200,0,200,84 + +00:00:18.500 --> 00:00:19.000 +/static/thumbnails.jpg#xywh=7400,0,200,84 + +00:00:19.000 --> 00:00:19.500 +/static/thumbnails.jpg#xywh=7600,0,200,84 + +00:00:19.500 --> 00:00:20.000 +/static/thumbnails.jpg#xywh=7800,0,200,84 + +00:00:20.000 --> 00:00:20.500 +/static/thumbnails.jpg#xywh=8000,0,200,84 + +00:00:20.500 --> 00:00:21.000 +/static/thumbnails.jpg#xywh=8200,0,200,84 + +00:00:21.000 --> 00:00:21.500 +/static/thumbnails.jpg#xywh=8400,0,200,84 + +00:00:21.500 --> 00:00:22.000 +/static/thumbnails.jpg#xywh=8600,0,200,84 + +00:00:22.000 --> 00:00:22.500 +/static/thumbnails.jpg#xywh=8800,0,200,84 + +00:00:22.500 --> 00:00:23.000 +/static/thumbnails.jpg#xywh=9000,0,200,84 + +00:00:23.000 --> 00:00:23.500 +/static/thumbnails.jpg#xywh=9200,0,200,84 + +00:00:23.500 --> 00:00:24.000 +/static/thumbnails.jpg#xywh=9400,0,200,84 + +00:00:24.000 --> 00:00:24.500 +/static/thumbnails.jpg#xywh=9600,0,200,84 + +00:00:24.500 --> 00:00:25.000 +/static/thumbnails.jpg#xywh=9800,0,200,84 + +00:00:25.000 --> 00:00:25.500 +/static/thumbnails.jpg#xywh=10000,0,200,84 + +00:00:25.500 --> 00:00:26.000 +/static/thumbnails.jpg#xywh=10200,0,200,84 + +00:00:26.000 --> 00:00:26.500 +/static/thumbnails.jpg#xywh=10400,0,200,84 + +00:00:26.500 --> 00:00:27.000 +/static/thumbnails.jpg#xywh=10600,0,200,84 + +00:00:27.000 --> 00:00:27.500 +/static/thumbnails.jpg#xywh=10800,0,200,84 + +00:00:27.500 --> 00:00:28.000 +/static/thumbnails.jpg#xywh=11000,0,200,84 + +00:00:28.000 --> 00:00:28.500 +/static/thumbnails.jpg#xywh=11200,0,200,84 + +00:00:28.500 --> 00:00:29.000 +/static/thumbnails.jpg#xywh=11400,0,200,84 + +00:00:29.000 --> 00:00:29.500 +/static/thumbnails.jpg#xywh=11600,0,200,84 + +00:00:29.500 --> 00:00:30.000 +/static/thumbnails.jpg#xywh=11800,0,200,84 + +00:00:30.000 --> 00:00:30.500 +/static/thumbnails.jpg#xywh=12000,0,200,84 + +00:00:30.500 --> 00:00:31.000 +/static/thumbnails.jpg#xywh=12200,0,200,84 + +00:00:31.000 --> 00:00:31.500 +/static/thumbnails.jpg#xywh=12400,0,200,84 + +00:00:31.500 --> 00:00:32.000 +/static/thumbnails.jpg#xywh=12600,0,200,84 + +00:00:32.000 --> 00:00:32.500 +/static/thumbnails.jpg#xywh=12800,0,200,84 + +00:00:32.500 --> 00:00:33.000 +/static/thumbnails.jpg#xywh=13000,0,200,84 + +00:00:33.000 --> 00:00:33.500 +/static/thumbnails.jpg#xywh=13200,0,200,84 + +00:00:33.500 --> 00:00:34.000 +/static/thumbnails.jpg#xywh=13400,0,200,84 + +00:00:34.000 --> 00:00:34.500 +/static/thumbnails.jpg#xywh=13600,0,200,84 + +00:00:34.500 --> 00:00:35.000 +/static/thumbnails.jpg#xywh=13800,0,200,84 + +00:00:35.000 --> 00:00:35.500 +/static/thumbnails.jpg#xywh=14000,0,200,84 + +00:00:35.500 --> 00:00:36.000 +/static/thumbnails.jpg#xywh=14200,0,200,84 + +00:00:36.000 --> 00:00:36.500 +/static/thumbnails.jpg#xywh=14400,0,200,84 + +00:00:36.500 --> 00:00:37.000 +/static/thumbnails.jpg#xywh=14600,0,200,84 + +00:00:37.000 --> 00:00:37.500 +/static/thumbnails.jpg#xywh=14800,0,200,84 + +00:00:37.500 --> 00:00:38.000 +/static/thumbnails.jpg#xywh=15000,0,200,84 + +00:00:38.000 --> 00:00:38.500 +/static/thumbnails.jpg#xywh=15200,0,200,84 + +00:00:38.500 --> 00:00:39.000 +/static/thumbnails.jpg#xywh=15400,0,200,84 + +00:00:39.000 --> 00:00:39.500 +/static/thumbnails.jpg#xywh=15600,0,200,84 + +00:00:39.500 --> 00:00:40.000 +/static/thumbnails.jpg#xywh=15800,0,200,84 + +00:00:40.000 --> 00:00:40.500 +/static/thumbnails.jpg#xywh=16000,0,200,84 + +00:00:40.500 --> 00:00:41.000 +/static/thumbnails.jpg#xywh=16200,0,200,84 + +00:00:41.000 --> 00:00:41.500 +/static/thumbnails.jpg#xywh=16400,0,200,84 + +00:00:41.500 --> 00:00:42.000 +/static/thumbnails.jpg#xywh=16600,0,200,84 + +00:00:42.000 --> 00:00:42.500 +/static/thumbnails.jpg#xywh=16800,0,200,84 + +00:00:42.500 --> 00:00:43.000 +/static/thumbnails.jpg#xywh=17000,0,200,84 + +00:00:43.000 --> 00:00:43.500 +/static/thumbnails.jpg#xywh=17200,0,200,84 + +00:00:43.500 --> 00:00:44.000 +/static/thumbnails.jpg#xywh=17400,0,200,84 + +00:00:44.000 --> 00:00:44.500 +/static/thumbnails.jpg#xywh=17600,0,200,84 + +00:00:44.500 --> 00:00:45.000 +/static/thumbnails.jpg#xywh=17800,0,200,84 + +00:00:45.000 --> 00:00:45.500 +/static/thumbnails.jpg#xywh=18000,0,200,84 + +00:00:45.500 --> 00:00:46.000 +/static/thumbnails.jpg#xywh=18200,0,200,84 + +00:00:46.000 --> 00:00:46.500 +/static/thumbnails.jpg#xywh=18400,0,200,84 + +00:00:46.500 --> 00:00:47.000 +/static/thumbnails.jpg#xywh=18600,0,200,84 + +00:00:47.000 --> 00:00:47.500 +/static/thumbnails.jpg#xywh=18800,0,200,84 + +00:00:47.500 --> 00:00:48.000 +/static/thumbnails.jpg#xywh=19000,0,200,84 + +00:00:48.000 --> 00:00:48.500 +/static/thumbnails.jpg#xywh=19200,0,200,84 + +00:00:48.500 --> 00:00:49.000 +/static/thumbnails.jpg#xywh=19400,0,200,84 + +00:00:49.000 --> 00:00:49.500 +/static/thumbnails.jpg#xywh=19600,0,200,84 + +00:00:49.500 --> 00:00:50.000 +/static/thumbnails.jpg#xywh=19800,0,200,84 + +00:00:50.000 --> 00:00:50.500 +/static/thumbnails.jpg#xywh=20000,0,200,84 + +00:00:50.500 --> 00:00:51.000 +/static/thumbnails.jpg#xywh=20200,0,200,84 + +00:00:51.000 --> 00:00:51.500 +/static/thumbnails.jpg#xywh=20400,0,200,84 + +00:00:51.500 --> 00:00:52.000 +/static/thumbnails.jpg#xywh=20600,0,200,84 + +00:00:52.000 --> 00:00:52.500 +/static/thumbnails.jpg#xywh=20800,0,200,84 + +00:00:52.500 --> 00:00:53.000 +/static/thumbnails.jpg#xywh=21000,0,200,84 + +00:00:53.000 --> 00:00:53.500 +/static/thumbnails.jpg#xywh=21200,0,200,84 + +00:00:53.500 --> 00:00:54.000 +/static/thumbnails.jpg#xywh=21400,0,200,84 + +00:00:54.000 --> 00:00:54.500 +/static/thumbnails.jpg#xywh=21600,0,200,84 + +00:00:54.500 --> 00:00:55.000 +/static/thumbnails.jpg#xywh=21800,0,200,84 + +00:00:55.000 --> 00:00:55.500 +/static/thumbnails.jpg#xywh=22000,0,200,84 + +00:00:55.500 --> 00:00:56.000 +/static/thumbnails.jpg#xywh=22200,0,200,84 + +00:00:56.000 --> 00:00:56.500 +/static/thumbnails.jpg#xywh=22400,0,200,84 + +00:00:56.500 --> 00:00:57.000 +/static/thumbnails.jpg#xywh=22600,0,200,84 + +00:00:57.000 --> 00:00:57.500 +/static/thumbnails.jpg#xywh=22800,0,200,84 + +00:00:57.500 --> 00:00:58.000 +/static/thumbnails.jpg#xywh=23000,0,200,84 + +00:00:58.000 --> 00:00:58.500 +/static/thumbnails.jpg#xywh=23200,0,200,84 + +00:00:58.500 --> 00:00:59.000 +/static/thumbnails.jpg#xywh=23400,0,200,84 + +00:00:59.000 --> 00:00:59.500 +/static/thumbnails.jpg#xywh=23600,0,200,84 + +00:00:59.500 --> 00:01:00.000 +/static/thumbnails.jpg#xywh=23800,0,200,84 + +00:01:00.000 --> 00:01:00.500 +/static/thumbnails.jpg#xywh=24000,0,200,84 + +00:01:00.500 --> 00:01:01.000 +/static/thumbnails.jpg#xywh=24200,0,200,84 + +00:01:01.000 --> 00:01:01.500 +/static/thumbnails.jpg#xywh=24400,0,200,84 + +00:01:01.500 --> 00:01:02.000 +/static/thumbnails.jpg#xywh=24600,0,200,84 + +00:01:02.000 --> 00:01:02.500 +/static/thumbnails.jpg#xywh=24800,0,200,84 + +00:01:02.500 --> 00:01:03.000 +/static/thumbnails.jpg#xywh=25000,0,200,84 + +00:01:03.000 --> 00:01:03.500 +/static/thumbnails.jpg#xywh=25200,0,200,84 + +00:01:03.500 --> 00:01:04.000 +/static/thumbnails.jpg#xywh=25400,0,200,84 + +00:01:04.000 --> 00:01:04.500 +/static/thumbnails.jpg#xywh=25600,0,200,84 + +00:01:04.500 --> 00:01:05.000 +/static/thumbnails.jpg#xywh=25800,0,200,84 + +00:01:05.000 --> 00:01:05.500 +/static/thumbnails.jpg#xywh=26000,0,200,84 + +00:01:05.500 --> 00:01:06.000 +/static/thumbnails.jpg#xywh=26200,0,200,84 + +00:01:06.000 --> 00:01:06.500 +/static/thumbnails.jpg#xywh=26400,0,200,84 + +00:01:06.500 --> 00:01:07.000 +/static/thumbnails.jpg#xywh=26600,0,200,84 + +00:01:07.000 --> 00:01:07.500 +/static/thumbnails.jpg#xywh=26800,0,200,84 + +00:01:07.500 --> 00:01:08.000 +/static/thumbnails.jpg#xywh=27000,0,200,84 + +00:01:08.000 --> 00:01:08.500 +/static/thumbnails.jpg#xywh=27200,0,200,84 + +00:01:08.500 --> 00:01:09.000 +/static/thumbnails.jpg#xywh=27400,0,200,84 + +00:01:09.000 --> 00:01:09.500 +/static/thumbnails.jpg#xywh=27600,0,200,84 + +00:01:09.500 --> 00:01:10.000 +/static/thumbnails.jpg#xywh=27800,0,200,84 + +00:01:10.000 --> 00:01:10.500 +/static/thumbnails.jpg#xywh=28000,0,200,84 + +00:01:10.500 --> 00:01:10.000 +/static/thumbnails.jpg#xywh=28200,0,200,84 + +00:01:11.000 --> 00:01:11.500 +/static/thumbnails.jpg#xywh=28400,0,200,84 + +00:01:11.500 --> 00:01:12.000 +/static/thumbnails.jpg#xywh=28600,0,200,84 + +00:01:12.000 --> 00:01:12.500 +/static/thumbnails.jpg#xywh=28800,0,200,84 + +00:01:12.500 --> 00:01:13.000 +/static/thumbnails.jpg#xywh=29000,0,200,84 + +00:01:13.000 --> 00:01:13.500 +/static/thumbnails.jpg#xywh=29200,0,200,84 + +00:01:13.500 --> 00:01:14.000 +/static/thumbnails.jpg#xywh=29400,0,200,84 + +00:01:14.000 --> 00:01:14.500 +/static/thumbnails.jpg#xywh=29600,0,200,84 + +00:01:14.500 --> 00:01:15.000 +/static/thumbnails.jpg#xywh=29800,0,200,84 + +00:01:15.000 --> 00:01:15.500 +/static/thumbnails.jpg#xywh=30000,0,200,84 + +00:01:15.500 --> 00:01:16.000 +/static/thumbnails.jpg#xywh=30200,0,200,84 + +00:01:16.000 --> 00:01:16.500 +/static/thumbnails.jpg#xywh=30400,0,200,84 + +00:01:16.500 --> 00:01:17.000 +/static/thumbnails.jpg#xywh=30600,0,200,84 + +00:01:17.000 --> 00:01:17.500 +/static/thumbnails.jpg#xywh=30800,0,200,84 + +00:01:17.500 --> 00:01:18.000 +/static/thumbnails.jpg#xywh=31000,0,200,84 + +00:01:18.000 --> 00:01:18.500 +/static/thumbnails.jpg#xywh=31200,0,200,84 + +00:01:18.500 --> 00:01:19.000 +/static/thumbnails.jpg#xywh=31400,0,200,84 + +00:01:19.000 --> 00:01:19.500 +/static/thumbnails.jpg#xywh=31600,0,200,84 + +00:01:19.500 --> 00:01:20.000 +/static/thumbnails.jpg#xywh=31800,0,200,84 + +00:01:20.000 --> 00:01:20.500 +/static/thumbnails.jpg#xywh=32000,0,200,84 + +00:01:20.500 --> 00:01:21.000 +/static/thumbnails.jpg#xywh=32200,0,200,84 + +00:01:21.000 --> 00:01:21.500 +/static/thumbnails.jpg#xywh=32400,0,200,84 + +00:01:21.500 --> 00:01:22.000 +/static/thumbnails.jpg#xywh=32600,0,200,84 + +00:01:22.000 --> 00:01:22.500 +/static/thumbnails.jpg#xywh=32800,0,200,84 + +00:01:22.500 --> 00:01:23.000 +/static/thumbnails.jpg#xywh=33000,0,200,84 + +00:01:23.000 --> 00:01:23.500 +/static/thumbnails.jpg#xywh=33200,0,200,84 + +00:01:23.500 --> 00:01:24.000 +/static/thumbnails.jpg#xywh=33400,0,200,84 + +00:01:24.000 --> 00:01:24.500 +/static/thumbnails.jpg#xywh=33600,0,200,84 + +00:01:24.500 --> 00:01:25.000 +/static/thumbnails.jpg#xywh=33800,0,200,84 + +00:01:25.000 --> 00:01:25.500 +/static/thumbnails.jpg#xywh=34000,0,200,84 + +00:01:25.500 --> 00:01:26.000 +/static/thumbnails.jpg#xywh=34200,0,200,84 + +00:01:26.000 --> 00:01:26.500 +/static/thumbnails.jpg#xywh=34400,0,200,84 + +00:01:26.500 --> 00:01:27.000 +/static/thumbnails.jpg#xywh=34600,0,200,84 + +00:01:27.000 --> 00:01:27.500 +/static/thumbnails.jpg#xywh=34800,0,200,84 + +00:01:27.500 --> 00:01:28.000 +/static/thumbnails.jpg#xywh=35000,0,200,84 + +00:01:28.000 --> 00:01:28.500 +/static/thumbnails.jpg#xywh=35200,0,200,84 + +00:01:28.500 --> 00:01:29.000 +/static/thumbnails.jpg#xywh=35400,0,200,84 + +00:01:29.000 --> 00:01:29.500 +/static/thumbnails.jpg#xywh=35600,0,200,84 + +00:01:29.500 --> 00:01:30.000 +/static/thumbnails.jpg#xywh=35800,0,200,84 + +00:01:30.000 --> 00:01:30.500 +/static/thumbnails.jpg#xywh=36000,0,200,84 + +00:01:30.500 --> 00:01:31.000 +/static/thumbnails.jpg#xywh=36200,0,200,84 + +00:01:31.000 --> 00:01:31.500 +/static/thumbnails.jpg#xywh=36400,0,200,84 + +00:01:31.500 --> 00:01:32.000 +/static/thumbnails.jpg#xywh=36600,0,200,84 + +00:01:32.000 --> 00:01:32.500 +/static/thumbnails.jpg#xywh=36800,0,200,84 + +00:01:32.500 --> 00:01:33.000 +/static/thumbnails.jpg#xywh=37000,0,200,84 + +00:01:33.000 --> 00:01:33.500 +/static/thumbnails.jpg#xywh=37200,0,200,84 + +00:01:33.500 --> 00:01:34.000 +/static/thumbnails.jpg#xywh=37400,0,200,84 + +00:01:34.000 --> 00:01:34.500 +/static/thumbnails.jpg#xywh=37600,0,200,84 + +00:01:34.500 --> 00:01:35.000 +/static/thumbnails.jpg#xywh=37800,0,200,84 + +00:01:35.000 --> 00:01:35.500 +/static/thumbnails.jpg#xywh=38000,0,200,84 + +00:01:35.500 --> 00:01:36.000 +/static/thumbnails.jpg#xywh=38200,0,200,84 + +00:01:36.000 --> 00:01:36.500 +/static/thumbnails.jpg#xywh=38400,0,200,84 + +00:01:36.500 --> 00:01:37.000 +/static/thumbnails.jpg#xywh=38600,0,200,84 + +00:01:37.000 --> 00:01:37.500 +/static/thumbnails.jpg#xywh=38800,0,200,84 + +00:01:37.500 --> 00:01:38.000 +/static/thumbnails.jpg#xywh=39000,0,200,84 + +00:01:38.000 --> 00:01:38.500 +/static/thumbnails.jpg#xywh=39200,0,200,84 + +00:01:38.500 --> 00:01:39.000 +/static/thumbnails.jpg#xywh=39400,0,200,84 + +00:01:39.000 --> 00:01:39.500 +/static/thumbnails.jpg#xywh=39600,0,200,84 + +00:01:39.500 --> 00:01:40.000 +/static/thumbnails.jpg#xywh=39800,0,200,84 + +00:01:40.000 --> 00:01:40.500 +/static/thumbnails.jpg#xywh=40000,0,200,84 + +00:01:40.500 --> 00:01:41.000 +/static/thumbnails.jpg#xywh=40200,0,200,84 diff --git a/client/fluid-player/test/static/vast4.xsd b/client/fluid-player/test/static/vast4.xsd new file mode 100644 index 0000000..64203c7 --- /dev/null +++ b/client/fluid-player/test/static/vast4.xsd @@ -0,0 +1,1284 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vast="http://www.iab.com/VAST" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.iab.com/VAST" version="4.2.0"> + <!-- =================== Begin root VAST document structure ============= --> + <xs:element name="VAST" > + <xs:annotation> + <xs:documentation>IAB VAST (Video Ad Serving Template), Version 4.2</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice> + <xs:element name="Ad" minOccurs="0" maxOccurs="unbounded" > + <xs:annotation> + <xs:documentation>Top-level element, wraps each ad in the response or ad unit in an ad pod. This MUST be present unless an Error element is present.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:choice minOccurs="1" maxOccurs="1"> + <xs:element name="InLine" minOccurs="0" maxOccurs="1" type="vast:Inline_type"> + <xs:annotation> + <xs:documentation>Second-level element surrounding complete ad data for a single ad</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Wrapper" minOccurs="0" maxOccurs="1" type="vast:Wrapper_type"> + <xs:annotation> + <xs:documentation>Second-level element surrounding wrapper ad pointing to Secondary ad server.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:choice> + <xs:attribute name="id" type="xs:string" use="optional" /> + <xs:attribute name="sequence" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Identifies the sequence of multiple Ads that are part of an Ad Pod.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="conditionalAd" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>[@Deprecated in VAST 4.1 with apiFramework] A Boolean value that identifies a conditional ad.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="adType" use="optional"> + <xs:annotation> + <xs:documentation>An optional string that identifies the type of ad. This allows VAST to support audio ad scenarios. The default value is video.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="video" /> + <xs:enumeration value="audio" /> + <xs:enumeration value="hybrid" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> + </xs:element> + <xs:element name="Error" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI" > + <xs:annotation> + <xs:documentation>Used when there is no ad response. When the ad server does not or cannot return an Ad. If included the video player must send a request to the URI provided (Sec 3.2.1).</xs:documentation> + </xs:annotation> + </xs:element> + </xs:choice> + <xs:attribute name="version" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation>Current version is 4.1</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + <!-- =================== End root VAST document structure ============= --> + <!-- ================= VAST internal type definitions ==================== --> + <xs:complexType name="IconClickTracking_type"> + <xs:annotation> + <xs:documentation>URLs to ping when icon action occurs.</xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="TrackingEvents_Verification_type" > + <xs:sequence> + <xs:element name="Tracking" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>Each <Tracking> element is used to define a single event to be tracked by the verification vendor. Multiple tracking elements may be used to define multiple events to be tracked, but may also be used to track events of the same type for multiple parties.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="event" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation>A string that defines the event being tracked. One event type is currently supported: verificationNotExecuted: the player did not or was not able to execute the provided verification code</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="TrackingEvents_type" > + <xs:sequence> + <xs:element name="Tracking" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>The name of the event to track for the element. The creativeView should always be requested when present.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="event" use="required"> + <xs:annotation> + <xs:documentation>The name of the event to track. For nonlinear ads these events should be recorded on the video within the ad.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="mute" /> + <xs:enumeration value="unmute" /> + <xs:enumeration value="pause" /> + <xs:enumeration value="resume" /> + <xs:enumeration value="rewind" /> + <xs:enumeration value="skip" /> + <xs:enumeration value="playerExpand" /> + <xs:enumeration value="playerCollapse" /> + <xs:enumeration value="loaded" /> + <xs:enumeration value="start" /> + <xs:enumeration value="firstQuartile" /> + <xs:enumeration value="midpoint" /> + <xs:enumeration value="thirdQuartile" /> + <xs:enumeration value="complete" /> + <xs:enumeration value="progress" /> + <xs:enumeration value="closeLinear" /> + <xs:enumeration value="creativeView" /> + <xs:enumeration value="acceptInvitation" /> + <xs:enumeration value="adExpand" /> + <xs:enumeration value="adCollapse" /> + <xs:enumeration value="minimize" /> + <xs:enumeration value="close" /> + <xs:enumeration value="overlayViewDuration" /> + <xs:enumeration value="otherAdInteraction" /> + <xs:enumeration value="interactiveStart" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="offset" use="optional"> + <xs:annotation> + <xs:documentation>The time during the video at which this url should be pinged. Must be present for progress event.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="(\d{2}:[0-5]\d:[0-5]\d(\.\d\d\d)?|1?\d?\d(\.?\d)*%)" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="VideoClicks_type" > + <xs:sequence> + <xs:element name="ClickTracking" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>URL to request for tracking purposes when user clicks on the video.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="ClickThrough" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation>URL to open as destination page when user clicks on the video. This can occur zero to many times. The XSD syntax can't represent that.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="CustomClick" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>URLs to request on custom events such as hotspotted video.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="Impression_type" > + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Ad server ID for the impression</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="CreativeResource_type"> + <xs:annotation> + <xs:documentation>A base creative resource type (sec 3.13) for non-video creative content. This specifies static, IFrame, or HTML content, or a combination thereof</xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="HTMLResource" minOccurs="0" maxOccurs="unbounded" type="vast:HTMLResource_type"> + <xs:annotation> + <xs:documentation>HTML to display the companion element. This can occur zero to many times, but order should not be important.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="IFrameResource" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>URI source for an IFrame to display the companion element. This can occur zero to many times, but order should not be important.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="StaticResource" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>URI to a static file, such as an image. This can occur zero to many times, but order should not be important.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="creativeType" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation>MIME type of static resource</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="Icon_type"> + <xs:complexContent> + <xs:extension base="vast:CreativeResource_type"> + <xs:sequence> + <xs:element name="IconClicks" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="IconClickFallbackImages" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="IconClickFallbackImage" minOccurs="1" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>Element used to display information when an icon click occurs.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:element name="AltText" minOccurs="0" maxOccurs="1" type="xs:string" > + </xs:element> + <xs:element name="StaticResource" minOccurs="0" maxOccurs="1" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>URI to a static file, such as an image.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="height" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel height of the image asset</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="width" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel width of the image asset</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="IconClickThrough" minOccurs="0" maxOccurs="1" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>URL to open as destination page when user clicks on the icon.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="IconClickTracking" minOccurs="0" maxOccurs="unbounded" type="vast:IconClickTracking_type"> + <xs:annotation> + <xs:documentation>URLs to ping when user clicks on the the icon.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="IconViewTracking" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>A URI for the tracking resource file to be called when the icon creative is displayed.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="program" type="xs:string"> + <xs:annotation> + <xs:documentation>Program represented in the Icon.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="width" type="xs:integer"> + <xs:annotation> + <xs:documentation>Pixel dimensions of icon.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="height" type="xs:integer"> + <xs:annotation> + <xs:documentation>Pixel dimensions of icon.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="xPosition"> + <xs:annotation> + <xs:documentation>The x-cooridinate of the top, left corner of the icon asset relative to the ad display area</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="([0-9]*|left|right)" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="yPosition"> + <xs:annotation> + <xs:documentation>The y-cooridinate of the top left corner of the icon asset relative to the ad display area.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="([0-9]*|top|bottom)" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="duration" type="xs:time"> + <xs:annotation> + <xs:documentation>The duration for which the player must display the icon. Expressed in standard time format hh:mm:ss.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="offset" type="xs:time"> + <xs:annotation> + <xs:documentation>Start time at which the player should display the icon. Expressed in standard time format hh:mm:ss.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="apiFramework" type="xs:string"> + <xs:annotation> + <xs:documentation>The apiFramework defines the method to use for communication with the icon element</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="pxratio" type="xs:decimal"> + <xs:annotation> + <xs:documentation>The pixel ratio for which the icon creative is intended. The pixel ratio is the ratio of physical pixels on the device to the device-independent pixels. An ad intended for display on a device with a pixel ratio that is twice that of a standard 1:1 pixel ratio would use the value "2" Default value is "1"</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="CreativeExtensions_type"> + <xs:sequence> + <xs:element name="CreativeExtension" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>Any valid XML may be included in the Extensions node. This can occur zero to many times.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##any" processContents="skip" /> + </xs:sequence> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>The MIME type of any code that might be included in the extension.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:anyAttribute namespace="##any" processContents="skip" /> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="AdParameters_type"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="xmlEncoded" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>Specifies whether the parameters are XML-encoded</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + <xs:complexType name="HTMLResource_type"> + <xs:annotation> + <xs:documentation>The URI to a static creative file to be used for the ad component identified in the parent element, which is either: <NonLinear>, <Companion>, or <Icon>.</xs:documentation> + </xs:annotation> + <xs:simpleContent> + <xs:extension base="xs:string" /> + </xs:simpleContent> + </xs:complexType> + <!-- ============= Linear, NonLinear, Companion - Child content classes for Inline and Wrapper========= --> + <xs:complexType name="Linear_Base_type"> + <xs:annotation> + <xs:documentation>Video formatted ad that plays linearly</xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="Icons" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="Icon" minOccurs="1" maxOccurs="unbounded" type="vast:Icon_type"> + <xs:annotation> + <xs:documentation>Any number of icons representing advertising industry initiatives.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="TrackingEvents" minOccurs="0" maxOccurs="1" type="vast:TrackingEvents_type" /> + </xs:sequence> + <xs:attribute name="skipoffset" use="optional"> + <xs:annotation> + <xs:documentation>The time at which the ad becomes skippable, if absent, the ad is not skippable.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="(\d{2}:[0-5]\d:[0-5]\d(\.\d\d\d)?|1?\d?\d(\.?\d)*%)" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> + <xs:complexType name="Linear_Wrapper_type"> + <xs:annotation> + <xs:documentation>Video formatted ad that plays linearly</xs:documentation> + </xs:annotation> + <xs:complexContent> + <xs:extension base="vast:Linear_Base_type"> + <xs:sequence> + <xs:element name="VideoClicks" minOccurs="0" maxOccurs="1" type="vast:VideoClicks_type" /> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="Linear_Inline_type"> + <xs:annotation> + <xs:documentation>Video formatted ad that plays linearly</xs:documentation> + </xs:annotation> + <xs:complexContent> + <xs:extension base="vast:Linear_Base_type"> + <xs:sequence> + <xs:element name="AdParameters" minOccurs="0" maxOccurs="1" type="vast:AdParameters_type"> + <xs:annotation> + <xs:documentation>[Note: SIMID is set to replace VPAID] [Note: VPAID has been deprecated in VAST 4.1 ] Data to be passed into the video ad. Used to pass VAST info to VPAID object. When a VAST response is used to serve a VPAID ad unit, the <AdParameters> element is currently the only way to pass information from the VAST response into the VPAID object; no other mechanism is provided.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Duration" minOccurs="1" maxOccurs="1" type="xs:time"> + <xs:annotation> + <xs:documentation>Duration in standard time format, hh:mm:ss</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="MediaFiles" minOccurs="1" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="ClosedCaptionFiles" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="ClosedCaptionFile" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>A CDATA-wrapped URI to a file providing Closed Caption info for the media file.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Identifies the MIME type of the file provided.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="language" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Language of the Closed Caption File using ISO 631-1 codes. An optional locale suffix can also be provided.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="MediaFile" minOccurs="1" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>URI location of linear file. Content must be wrapped in CDATA tag.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Optional identifier</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="delivery" use="required"> + <xs:annotation> + <xs:documentation>Either "progressive" for progressive download protocols (such as HTTP) or "streaming" for streaming protocols.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="streaming" /> + <xs:enumeration value="progressive" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="type" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation>MIME type. Popular MIME types include, but are not limited to "video/x-ms-wmv" for Windows Media, and "video/x-flv" for Flash Video. Image ads or interactive ads can be included in the MediaFiles section with appropriate Mime types. Flash support was deprecated in January 2017 and is now being removed.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="width" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of video, or 0 for audio ads</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="height" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of video, or 0 for audio ads</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="codec" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>The codec used to produce the media file as specified in RFC 4281.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="bitrate" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Bitrate of encoded video in Kbps. If bitrate is supplied, minBitrate and maxBitrate should not be supplied.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="minBitrate" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Minimum bitrate of an adaptive stream in Kbps. If minBitrate is supplied, maxBitrate must be supplied and bitrate should not be supplied.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maxBitrate" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Maximum bitrate of an adaptive stream in Kbps. If maxBitrate is supplied, minBitrate must be supplied and bitrate should not be supplied.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="scalable" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>Whether it is acceptable to scale the image.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maintainAspectRatio" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>Whether the ad must have its aspect ratio maintained when scales</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="fileSize" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Optional field that helps eliminate the need to calculate the size based on bitrate and duration. Units - Bytes</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="mediaType" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Type of media file (2D / 3D / 360 / etc). Default value = 2D</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="apiFramework" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>[@Deprecated in 4.1 in preparation for VPAID being phased out]. identifies the API needed to execute an interactive media file, but current support is for backward compatibility. Please use the <InteractiveCreativeFile> element to include files that require an API for execution.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="Mezzanine" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>URI location to raw, high-quality media file for high-resolution environments or to transcode video or audio files at quality levels specific to the needs of certain environments. Content must be wrapped in CDATA tag.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Optional identifier</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="delivery" use="required"> + <xs:annotation> + <xs:documentation>Either "progressive" for progressive download protocols (such as HTTP) or "streaming" for streaming protocols.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="streaming" /> + <xs:enumeration value="progressive" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="type" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation>MIME type. Popular MIME types include, but are not limited to "video/x-ms-wmv" for Windows Media, and "video/x-flv" for Flash Video. Image ads or interactive ads can be included in the MediaFiles section with appropriate Mime types. Flash support was deprecated in January 2017 and is now being removed.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="width" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of video, or 0 for audio ads</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="height" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of video, or 0 for audio ads</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="codec" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>The codec used to produce the media file as specified in RFC 4281.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="fileSize" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Optional field that helps eliminate the need to calculate the size based on bitrate and duration. Units - Bytes</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="mediaType" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Type of media file (2D / 3D / 360 / etc). Default value = 2D</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="InteractiveCreativeFile" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>For any media file that uses APIs for advanced creative functionality, the InteractivityCreativeFile element is used to identify the file and framework needed to execute advanced functions for the ad.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Identifies the MIME type of the file provided.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="apiFramework" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>identifies the API needed to execute the Creative file if applicable</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="variableDuration" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>Useful for interactive use cases. Identifies whether the ad always drops when the duration is reached, or if it can potentially extend the duration by pausing the underlying video or delaying the adStopped call after adVideoComplete. If it set to true the extension of the duration should be user-initiated (typically by engaging with an interactive element to view additional content).</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="VideoClicks" minOccurs="0" maxOccurs="1" type="vast:VideoClicks_type" /> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="NonLinearAd_Base_type"> + <xs:annotation> + <xs:documentation>An ad that is overlain on top of video content during playback</xs:documentation> + </xs:annotation> + <xs:all> + <xs:element name="NonLinearClickTracking" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation>URLs to ping when user clicks on the the non-linear ad unit. This can occur zero to many times (unbounded). The XSD syntax can't represent that.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Identifier provided to ad server for click reports</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:all> + </xs:complexType> + <xs:complexType name="NonLinearAd_Inline_type"> + <xs:annotation> + <xs:documentation>An ad that is overlain on top of video content during playback</xs:documentation> + </xs:annotation> + <xs:complexContent> + <xs:extension base="vast:CreativeResource_type"> + <xs:sequence> + <xs:element name="AdParameters" minOccurs="0" maxOccurs="1" type="vast:AdParameters_type"> + <xs:annotation> + <xs:documentation>Custom content used to pass information to ad unit</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="NonLinearClickThrough" minOccurs="0" maxOccurs="1" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>URI to advertiser page opened on viewer clicks through.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="NonLinearClickTracking" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>URLs to ping when user clicks on the the non-linear ad unit.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Identifier provided to ad server for click reports</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Optional identifier</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="width" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of companion</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="height" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of companion</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="expandedWidth" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel dimensions of expanding nonlinear ad when in expanded state</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="expandedHeight" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel dimensions of expanding nonlinear ad when in expanded state</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="scalable" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>Whether it is acceptable to scale the image.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="maintainAspectRatio" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation>Whether the ad must have its aspect ratio maintained when scales</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="minSuggestedDuration" type="xs:time" use="optional"> + <xs:annotation> + <xs:documentation>Suggested duration to display non-linear ad, typically for animation to complete. Expressed in standard time format hh:mm:ss</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="apiFramework" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>The apiFramework defines the method to use for communication with the nonlinear element</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="CompanionAd_type"> + <xs:complexContent> + <xs:extension base="vast:CreativeResource_type"> + <xs:sequence> + <xs:element name="AdParameters" minOccurs="0" maxOccurs="1" type="vast:AdParameters_type"> + <xs:annotation> + <xs:documentation>Data to be passed into the companion ads. The apiFramework defines the method to use for communication.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="AltText" minOccurs="0" maxOccurs="1" type="xs:string"> + <xs:annotation> + <xs:documentation>Alt text to be displayed when companion is rendered in HTML environment.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="CompanionClickThrough" minOccurs="0" maxOccurs="1" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>URL to open as destination page when user clicks on the the companion banner ad.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="CompanionClickTracking" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>A URI to a tracking resource file used to track a companion clickthrough.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="id" type="xs:string" use="required"> + <xs:annotation> + <xs:documentation>An id provided by the ad server to track the click in reports.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="CreativeExtensions" minOccurs="0" maxOccurs="1" type="vast:CreativeExtensions_type" /> + <xs:element name="TrackingEvents" minOccurs="0" maxOccurs="1" type="vast:TrackingEvents_type"> + <xs:annotation> + <xs:documentation>The creativeView should always be requested when present. For Companions creativeView is the only supported event.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Optional identifier</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="width" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of companion slot</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="height" type="xs:integer" use="required"> + <xs:annotation> + <xs:documentation>Pixel dimensions of companion slot</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="assetWidth" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel dimensions of the companion asset</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="assetHeight" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel dimensions of the companion asset</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="expandedWidth" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel dimensions of expanding companion ad when in expanded state</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="expandedHeight" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>Pixel dimensions of expanding companion ad when in expanded state</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="apiFramework" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>The apiFramework defines the method to use for communication with the companion</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="adSlotId" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Used to match companion creative to publisher placement areas on the page.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="pxratio" type="xs:decimal"> + <xs:annotation> + <xs:documentation>The pixel ratio for which the icon creative is intended. The pixel ratio is the ratio of physical pixels on the device to the device-independent pixels. An ad intended for display on a device with a pixel ratio that is twice that of a standard 1:1 pixel ratio would use the value "2" Default value is "1"</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="renderingMode" use="optional"> + <xs:annotation> + <xs:documentation>Used to indicate when and where to use this companion ad. If this field is empty or not given, default will be used.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="default" /> + <xs:enumeration value="end-card" /> + <xs:enumeration value="concurrent" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="CompanionAds_Collection_type"> + <xs:sequence> + <xs:element name="Companion" minOccurs="0" maxOccurs="unbounded" type="vast:CompanionAd_type"> + <xs:annotation> + <xs:documentation>General subclass type for Companion Ad elements. This can occur zero to many times.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="required" use="optional"> + <xs:annotation> + <xs:documentation>How the player should treat a companion ad when multiple are supplied</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="all" /> + <xs:enumeration value="any" /> + <xs:enumeration value="none" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:complexType> + <!-- ============= Creative Element for under a Wrapper or Inline Element ========= --> + <xs:complexType name="Creative_Base_type"> + <xs:attribute name="sequence" type="xs:integer" use="optional"> + <xs:annotation> + <xs:documentation>The preferred order in which multiple Creatives should be displayed</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="apiFramework" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Identifies an API needed to execute the creative</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>A string used to identify the ad server that provides the creative.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="adId" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>To be deprecated in future version of VAST. Ad-ID for the creative (formerly ISCI)</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + <xs:complexType name="Creative_Wrapper_type"> + <xs:complexContent> + <xs:extension base="vast:Creative_Base_type"> + <xs:sequence> + <xs:element name="CompanionAds" minOccurs="0" maxOccurs="1" type="vast:CompanionAds_Collection_type" /> + <xs:element name="Linear" minOccurs="0" maxOccurs="1" type="vast:Linear_Wrapper_type" /> + <xs:element name="NonLinearAds" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="TrackingEvents" minOccurs="0" maxOccurs="1" type="vast:TrackingEvents_type" /> + <xs:element name="NonLinear" minOccurs="0" maxOccurs="unbounded" type="vast:NonLinearAd_Base_type"> + <xs:annotation> + <xs:documentation>Any number of companions in any desired pixel dimensions.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <xs:complexType name="Creative_Inline_type"> + <xs:complexContent> + <xs:extension base="vast:Creative_Base_type"> + <xs:sequence> + <xs:element name="CompanionAds" minOccurs="0" maxOccurs="1" type="vast:CompanionAds_Collection_type" /> + <xs:element name="CreativeExtensions" minOccurs="0" maxOccurs="1" type="vast:CreativeExtensions_type" /> + <xs:element name="Linear" minOccurs="0" maxOccurs="1" type="vast:Linear_Inline_type" /> + <xs:element name="NonLinearAds" minOccurs="0" maxOccurs="1"> + <xs:complexType> + <xs:sequence> + <xs:element name="TrackingEvents" minOccurs="0" maxOccurs="1" type="vast:TrackingEvents_type" /> + <xs:element name="NonLinear" minOccurs="0" maxOccurs="unbounded" type="vast:NonLinearAd_Inline_type"> + <xs:annotation> + <xs:documentation>Any number of companions in any desired pixel dimensions.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="UniversalAdId" minOccurs="1" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>The UniversalAdId is used to provide a unique creative identifier for the purposes of tracking ad creative. This is used for AD-ID(r) for ads served in the United States. Default value is "unknown"</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="idRegistry" type="xs:string" use="required" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <!-- ============ Base Definition of Shared Inline or Wrapper ad structure ============ --> + <xs:complexType name="AdDefinitionBase_type"> + <xs:annotation> + <xs:documentation>Base type structure used by Inline or Wrapper ad content element types</xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="AdSystem" minOccurs="1" maxOccurs="1"> + <xs:annotation> + <xs:documentation>Indicates source ad server</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="version" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>Internal version used by ad system</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="Error" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>URL to request if ad does not play due to error</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Extensions" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation>XML node for custom extensions, as defined by the ad server. When used, a custom element should be nested under <Extensions> to help separa + te custom XML elements from VAST elements.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:element name="Extension" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>One instance of <Extension> should be used for each custom extension. The type attribute identifies the MIME type of any code provided in the extension.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip" /> + </xs:sequence> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>The MIME type of any code that might be included in the extension.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="Impression" minOccurs="1" maxOccurs="unbounded" type="vast:Impression_type"> + <xs:annotation> + <xs:documentation>A URI that directs the media player to a tracking resource file that the media player must + use to notify the ad server when the impression occurs. If there is no reason to include + an Impression element, the placeholder "about:blank" should be used instead of a + tracking URL</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Pricing" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation>The price of the ad that can be used in real time bidding systems.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:decimal"> + <xs:attribute name="model" use="required"> + <xs:annotation> + <xs:documentation>The pricing model used.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:token"> + <xs:enumeration value="CPC" /> + <xs:enumeration value="CPM" /> + <xs:enumeration value="CPE" /> + <xs:enumeration value="CPV" /> + <xs:enumeration value="cpc" /> + <xs:enumeration value="cpm" /> + <xs:enumeration value="cpe" /> + <xs:enumeration value="cpv" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="currency" use="required"> + <xs:annotation> + <xs:documentation>Three letter ISO-4217 currency symbol that identifies the currency of the value provied. Ex: USD, GBP, etc.</xs:documentation> + </xs:annotation> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="[a-zA-Z]{3}" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="ViewableImpression" minOccurs="0" maxOccurs="1" type="vast:ViewableImpression_type" /> + </xs:sequence> + </xs:complexType> + <xs:complexType name="ViewableImpression_type" > + <xs:annotation> + <xs:documentation>The ViewableImpression element allows for tracking URIs to report viewability</xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="Viewable" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>A URI that directs the video player to a tracking resource file that the video player should request at the time that criteria is met for a viewable impression. This can occur zero to many times.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="NotViewable" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>A URI that directs the video player to a tracking resource file that the video player should request if the ad is executed but never meets criteria for a viewable impression.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="ViewUndetermined" minOccurs="0" maxOccurs="unbounded" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>A URI that directs the video player to a tracking resource file that the video player should request if the player cannot determine whether criteria is met for a viewable impression. This can occur zero to many times.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="id" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>An ad server id for the impression. Impression resources of the same id should be requested at the same time or as close in time as possible to help prevent discrepancies.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + <!-- ============ Verification type used under Inline elements =============== --> + <xs:complexType name="Verification_type"> + <xs:annotation> + <xs:documentation>Verification elements are nested under AdVerifications. The Verification element is used to contain the executable and bootstrapping required to run the measurement code for a single verification vendor. Multiple Verification elements may be used in cases where more than one verification vendor needs to collect data or when different API frameworks are used. At lease one JavaScriptResource or ExecutableResource should be provided. At most one of these resources should selected for execution, as best matches the technology available in the current environment. If the player is willing and able to run one of these resources, it should execute them BEFORE creative playback begins. Otherwise, if no resource can be executed, any appropriate tracking events listed under the <Verification> element must be fired.</xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="ExecutableResource" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="apiFramework" type="xs:string" use="optional" /> + <xs:attribute name="type" type="xs:string" use="optional" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="JavaScriptResource" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="apiFramework" type="xs:string" use="optional" /> + <xs:attribute name="browserOptional" type="xs:boolean" use="optional" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="TrackingEvents" minOccurs="0" maxOccurs="1" type="vast:TrackingEvents_Verification_type" /> + <xs:element name="VerificationParameters" minOccurs="0" maxOccurs="1" type="xs:string"> + <xs:annotation> + <xs:documentation>CDATA-wrapped metadata string for the verification executable.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="vendor" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>An identifier for the verification vendor. The recommended format is [domain]-[useCase], to avoid name collisions. For example, "company.com-omid".</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + <!-- ========== Definition of the AdVerifications element type ========== --> + <xs:complexType name="AdVerifications_type" > + <xs:annotation> + <xs:documentation>The AdVerification element is used to initiate a controlled container where code can be executed for collecting data to verify ad playback details.</xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="Verification" minOccurs="0" maxOccurs="unbounded" type="vast:Verification_type" /> + </xs:sequence> + </xs:complexType> + <!-- ========== Definition of the Wrapper element type ========== --> + <xs:complexType name="Wrapper_type"> + <xs:complexContent> + <xs:extension base="vast:AdDefinitionBase_type"> + <xs:sequence> + <xs:element name="AdVerifications" minOccurs="0" maxOccurs="1" type="vast:AdVerifications_type" /> + <xs:element name="BlockedAdCategories" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>A string that provides a category code or label that identifies the ad content.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="authority" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>A URL for the organizational authority that produced the list being used to identify ad content. Optional unless the publisher requires ad categories. The authority attribute is required if categories are provided.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="Creatives" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation>A container for one or more Creative elements used to provide creative files for ad.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:element name="Creative" minOccurs="1" maxOccurs="unbounded" type="vast:Creative_Wrapper_type" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="VASTAdTagURI" minOccurs="1" maxOccurs="1" type="xs:anyURI"> + <xs:annotation> + <xs:documentation>A URI to another VAST response that may be another VAST Wrapper or a VAST InLine ad.</xs:documentation> + </xs:annotation> + </xs:element> + </xs:sequence> + <xs:attribute name="followAdditionalWrappers" type="xs:boolean"> + <xs:annotation> + <xs:documentation>a Boolean value that identifies whether subsequent wrappers after a requested VAST response is allowed.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="allowMultipleAds" type="xs:boolean"> + <xs:annotation> + <xs:documentation>a Boolean value that identifies whether multiple ads are allowed in the requested VAST response.</xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="fallbackOnNoAd" type="xs:boolean"> + <xs:annotation> + <xs:documentation>a Boolean value that provides instruction for using an available Ad when the requested VAST response returns no ads.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:complexContent> + </xs:complexType> + <!-- ========== Definition of the Inline element type ========== --> + <xs:complexType name="Inline_type"> + <xs:complexContent> + <xs:extension base="vast:AdDefinitionBase_type"> + <xs:sequence> + <xs:element name="AdServingId" minOccurs="1" maxOccurs="1" type="xs:string"> + <xs:annotation> + <xs:documentation>Any ad server that returns a VAST containing an <InLine> ad must generate a pseudo-unique identifier that is appropriate for all involved parties to track the lifecycle of that ad. This should be inserted into the <AdServingId> element, and also be included on all outgoing tracking pixels. The value should be different for each Inline in a VAST. Usage of a GUID is recommended.</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="AdTitle" minOccurs="1" maxOccurs="1" type="xs:string"> + <xs:annotation> + <xs:documentation>Common name of ad</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="AdVerifications" minOccurs="0" maxOccurs="1" type="vast:AdVerifications_type" /> + <xs:element name="Advertiser" minOccurs="0" maxOccurs="1" type="xs:string"> + <xs:annotation> + <xs:documentation>Name of advertiser as defined by the ad serving party</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Category" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation>A string that provides a category code or label that identifies the ad content.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="authority" type="xs:anyURI" use="required"> + <xs:annotation> + <xs:documentation>A URI for the organizational authority that produced the list being used to identify ad content.</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + <xs:element name="Creatives" minOccurs="1" maxOccurs="1"> + <xs:annotation> + <xs:documentation>A container for one or more Creative elements used to provide creative files for ad.</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:sequence> + <xs:element name="Creative" minOccurs="1" maxOccurs="unbounded" type="vast:Creative_Inline_type" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="Description" minOccurs="0" maxOccurs="1" type="xs:string"> + <xs:annotation> + <xs:documentation>Longer description of ad</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Expires" minOccurs="0" maxOccurs="1" type="xs:integer"> + <xs:annotation> + <xs:documentation>An integer value that defines the expiry period (in seconds).</xs:documentation> + </xs:annotation> + </xs:element> + <xs:element name="Survey" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation>URL of request to survey vendor</xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:simpleContent> + <xs:extension base="xs:anyURI"> + <xs:attribute name="type" type="xs:string" use="optional"> + <xs:annotation> + <xs:documentation>MIME type of the resource being served</xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:extension> + </xs:simpleContent> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:extension> + </xs:complexContent> + </xs:complexType> +</xs:schema> diff --git a/client/fluid-player/test/static/vast_ad_buffet.xml b/client/fluid-player/test/static/vast_ad_buffet.xml new file mode 100644 index 0000000..b0f60b2 --- /dev/null +++ b/client/fluid-player/test/static/vast_ad_buffet.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="5566276"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="78544088"> + <Linear skipoffset="00:00:03"> + <Duration>00:00:22.342</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>samplesite.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Ad Buffet Ad #1</MobileText> + <PCText>Ad Buffet Ad #1</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Ad Buffet Ad #2</MobileText> + <PCText>Ad Buffet Ad #2</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Ad Buffet Ad #3</MobileText> + <PCText>Ad Buffet Ad #3</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_ad_buffet_with_error.xml b/client/fluid-player/test/static/vast_ad_buffet_with_error.xml new file mode 100644 index 0000000..f74cb96 --- /dev/null +++ b/client/fluid-player/test/static/vast_ad_buffet_with_error.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="5566276"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Ad Buffet Ad #1</MobileText> + <PCText>Ad Buffet Ad #1</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Ad Buffet Ad #2</MobileText> + <PCText>Ad Buffet Ad #2</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Ad Buffet Ad #3</MobileText> + <PCText>Ad Buffet Ad #3</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_ad_pod.xml b/client/fluid-player/test/static/vast_ad_pod.xml new file mode 100644 index 0000000..9523db6 --- /dev/null +++ b/client/fluid-player/test/static/vast_ad_pod.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="5566276" sequence="2"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="78544088"> + <Linear skipoffset="00:00:03"> + <Duration>00:00:22.342</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>samplesite.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>AD #2 in an Ad Pod</MobileText> + <PCText>AD #2 in an Ad Pod</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678" sequence="1"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear skipoffset="00:00:03"> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>AD #1 in an Ad Pod</MobileText> + <PCText>AD #1 in an Ad Pod</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="20005" sequence="3"> + <InLine> + <AdSystem version="4.0">iabtechlab</AdSystem> + <AdTitle> + NonLinear Image + </AdTitle> + <Description> + <![CDATA[VAST 3.0 sample tag for Non Linear ad (i.e Overlay ad). Change the StaticResources to have a tag with your own content. Change NonLinear tag's parameters accordingly to view desired results. ]]> + </Description> + <Pricing model="cpm" currency="USD"> + <![CDATA[ 25.00 ]]> + </Pricing> + <Error> + <![CDATA[ + http://example.com/error + ]]> + </Error> + <Impression id="Impression-ID"> + <![CDATA[ + http://example.com/track/impression + ]]> + </Impression> + + <Creatives> + <Creative id="5480" sequence="1"> + <NonLinearAds> + <NonLinear width="480" height="150" minSuggestedDuration="00:00:05" scalable="true" maintainAspectRatio="true"> + <StaticResource creativeType="image/png"> + <![CDATA[ https://placekitten.com/480/150 ]]> + </StaticResource> + + <NonLinearClickTracking> + <![CDATA[http://example.com/trackingurl/clickTracking]]> + </NonLinearClickTracking> + <NonLinearClickThrough> + <![CDATA[http://iabtechlab.com]]> + </NonLinearClickThrough> + + </NonLinear> + </NonLinearAds> + </Creative> + </Creatives> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear skipoffset="00:00:02"> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Stand Alone Ad - Should Not be Played (BUG!)</MobileText> + <PCText>Stand Alone Ad - Should Not be Played (BUG!)</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_cta.xml b/client/fluid-player/test/static/vast_cta.xml new file mode 100644 index 0000000..d7283fb --- /dev/null +++ b/client/fluid-player/test/static/vast_cta.xml @@ -0,0 +1,76 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1" adType="video"> + <InLine> + <AdSystem version="1">Test</AdSystem> + <AdTitle>Vast CTA test</AdTitle> + <Creatives> + <Creative sequence="1" id="1"> + <Linear skipoffset="00:00:05"> + <Duration>00:00:00</Duration> + <MediaFiles> + <MediaFile id="1" delivery="progressive" type="video/mp4" bitrate="500" width="400" height="300" minBitrate="360" maxBitrate="1080" scalable="1" maintainAspectRatio="1" codec="0"> + <![CDATA[ https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4 ]]> + </MediaFile> + </MediaFiles> + <VideoClicks> + <ClickThrough id="1"> + <![CDATA[ http://www.example.com ]]> + </ClickThrough> + <ClickTracking> + <![CDATA[ http://www.example.com/click-tracking ]]> + </ClickTracking> + </VideoClicks> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + <Tracking event="skip">http://www.example.com/skip</Tracking> + <Tracking event="progress" offset="00:00:05">http://www.example.com/progress5</Tracking> + <Tracking event="progress" offset="00:00:10">http://www.example.com/progress10</Tracking> + <Tracking event="progress" offset="00:00:15">http://www.example.com/progress15</Tracking> + <Tracking event="progress" offset="00:00:20">http://www.example.com/progress20</Tracking> + <Tracking event="progress" offset="00:00:25">http://www.example.com/progress25</Tracking> + </TrackingEvents> + <Icons> + <Icon> + <IconClicks> <!-- Will fall back to IconClickTrough if no TitleCTA extension found --> + <IconClickThrough>https://www.example.com/IconClickThrough</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + <UniversalAdId idRegistry="unknown">unknown</UniversalAdId> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <!-- The MobileText is corresponding to our "CTA text" --> + <MobileText>CTA Text Mobile</MobileText> + + <!-- The PCText is identical to MobileText --> + <PCText>CTA Text Desktop</PCText> + + <!-- The link corresponds to our "Display Url" --> + <Link><![CDATA[https://www.example.com/advertisment?ref=1234567890!"#$%&/()=]]></Link> + + <!-- The Tracking is the same as our to ClickTracking --> + <Tracking><![CDATA[https://www.example.com/tracking.html]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + <Impression id=""> + <![CDATA[ http://www.example.com/impression ]]> + </Impression> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_cta_no_friendly_url.xml b/client/fluid-player/test/static/vast_cta_no_friendly_url.xml new file mode 100644 index 0000000..0157b02 --- /dev/null +++ b/client/fluid-player/test/static/vast_cta_no_friendly_url.xml @@ -0,0 +1,73 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1" adType="video"> + <InLine> + <AdSystem version="1">Test</AdSystem> + <AdTitle>Vast CTA test</AdTitle> + <Creatives> + <Creative sequence="1" id="1"> + <Linear skipoffset="00:00:05"> + <Duration>00:00:00</Duration> + <MediaFiles> + <MediaFile id="1" delivery="progressive" type="video/mp4" bitrate="500" width="400" height="300" minBitrate="360" maxBitrate="1080" scalable="1" maintainAspectRatio="1" codec="0"> + <![CDATA[ https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4 ]]> + </MediaFile> + </MediaFiles> + <VideoClicks> + <ClickThrough id="1"> + <![CDATA[ http://www.example.com ]]> + </ClickThrough> + </VideoClicks> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + <Tracking event="skip">http://www.example.com/skip</Tracking> + <Tracking event="progress" offset="00:00:05">http://www.example.com/progress5</Tracking> + <Tracking event="progress" offset="00:00:10">http://www.example.com/progress10</Tracking> + <Tracking event="progress" offset="00:00:15">http://www.example.com/progress15</Tracking> + <Tracking event="progress" offset="00:00:20">http://www.example.com/progress20</Tracking> + <Tracking event="progress" offset="00:00:25">http://www.example.com/progress25</Tracking> + </TrackingEvents> + <Icons> + <Icon> + <IconClicks> <!-- Will fall back to IconClickTrough if no TitleCTA extension found --> + <IconClickThrough>https://www.example.com/IconClickThrough</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + <UniversalAdId idRegistry="unknown">unknown</UniversalAdId> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <!-- The MobileText is corresponding to our "CTA text" --> + <MobileText>CTA Text Mobile</MobileText> + + <!-- The PCText is identical to MobileText --> + <PCText>CTA Text Desktop</PCText> + + <!-- The link corresponds to our "Display Url" --> + <Link><![CDATA[ ]]></Link> + + <!-- The Tracking is the same as our to ClickTracking --> + <Tracking><![CDATA[https://www.example.com/tracking.html]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + <Impression id=""> + <![CDATA[ http://www.example.com/impression ]]> + </Impression> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_hls.xml b/client/fluid-player/test/static/vast_hls.xml new file mode 100644 index 0000000..5f716c3 --- /dev/null +++ b/client/fluid-player/test/static/vast_hls.xml @@ -0,0 +1,52 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1" adType="video"> + <InLine> + <AdSystem version="1">stripcash.com</AdSystem> + <AdTitle>HLS test</AdTitle> + <Creatives> + <Creative sequence="1" id="1"> + <Linear skipoffset="00:00:05"> + <Duration>00:00:00</Duration> + <MediaFiles> + <MediaFile id="1" delivery="streaming" type="application/vnd.apple.mpegurl" width="480" height="640"> + <![CDATA[ https://cdn.fluidplayer.com/videos/HLS/index.m3u8 ]]> + </MediaFile> + </MediaFiles> + <VideoClicks> + <ClickThrough id="1"> + <![CDATA[ http://www.example.com ]]> + </ClickThrough> + <ClickTracking> + <![CDATA[ http://www.example.com/click-tracking ]]> + </ClickTracking> + </VideoClicks> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + <Tracking event="skip">http://www.example.com/skip</Tracking> + <Tracking event="progress" offset="00:00:05">http://www.example.com/progress5</Tracking> + <Tracking event="progress" offset="00:00:10">http://www.example.com/progress10</Tracking> + <Tracking event="progress" offset="00:00:15">http://www.example.com/progress15</Tracking> + <Tracking event="progress" offset="00:00:20">http://www.example.com/progress20</Tracking> + <Tracking event="progress" offset="00:00:25">http://www.example.com/progress25</Tracking> + </TrackingEvents> + </Linear> + <UniversalAdId idRegistry="unknown">unknown</UniversalAdId> + </Creative> + </Creatives> + <Impression id=""> + <![CDATA[ http://www.example.com/impression ]]> + </Impression> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_linear.xml b/client/fluid-player/test/static/vast_linear.xml new file mode 100644 index 0000000..ded0132 --- /dev/null +++ b/client/fluid-player/test/static/vast_linear.xml @@ -0,0 +1,72 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1" adType="video"> + <InLine> + <AdSystem version="1">Test</AdSystem> + <AdTitle>Vast Linear</AdTitle> + <Creatives> + <Creative sequence="1" id="1"> + <Linear skipoffset="00:00:05"> + <Duration>00:00:00</Duration> + <MediaFiles> + <MediaFile id="1" delivery="progressive" type="video/mp4" bitrate="500" width="400" height="300" minBitrate="360" maxBitrate="1080" scalable="1" maintainAspectRatio="1" codec="0"> + <![CDATA[ https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4 ]]> + </MediaFile> + </MediaFiles> + <VideoClicks> + <ClickThrough id="1"> + <![CDATA[ http://www.example.com ]]> + </ClickThrough> + <ClickTracking> + <![CDATA[ http://www.example.com/click-tracking ]]> + </ClickTracking> + </VideoClicks> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + <Tracking event="skip">http://www.example.com/skip</Tracking> + <Tracking event="progress" offset="00:00:05">http://www.example.com/progress5</Tracking> + <Tracking event="progress" offset="00:00:10">http://www.example.com/progress10</Tracking> + <Tracking event="progress" offset="00:00:15">http://www.example.com/progress15</Tracking> + <Tracking event="progress" offset="00:00:20">http://www.example.com/progress20</Tracking> + <Tracking event="progress" offset="00:00:25">http://www.example.com/progress25</Tracking> + </TrackingEvents> + <Icons> + <Icon> + <IconClicks> <!-- Will fall back to IconClickTrough if no TitleCTA extension found --> + <IconClickThrough>https://www.example.com/IconClickThrough</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + <UniversalAdId idRegistry="unknown">unknown</UniversalAdId> + </Creative> + </Creatives> + <Extensions> + </Extensions> + <Impression id=""> + <![CDATA[ http://www.example.com/impression ]]> + </Impression> + <ViewableImpression id="1543"> + <Viewable> + <![CDATA[ http://www.example.com/view-impression ]]> + </Viewable> + <NotViewable> + <![CDATA[ https://search.iabtechlab.com/error?errcode=102&imprid=s5-ea2f7f298e28c0c98374491aec3dfeb1&ts=1243 ]]> + </NotViewable> + <ViewUndetermined> + <![CDATA[ https://search.iabtechlab.com/error?errcode=102&imprid=s5-ea2f7f298e28c0c98374491aec3dfeb1&ts=1243 ]]> + </ViewUndetermined> + </ViewableImpression> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_linear_e2e.xml b/client/fluid-player/test/static/vast_linear_e2e.xml new file mode 100644 index 0000000..38717c1 --- /dev/null +++ b/client/fluid-player/test/static/vast_linear_e2e.xml @@ -0,0 +1,72 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1" adType="video"> + <InLine> + <AdSystem version="1">Test</AdSystem> + <AdTitle>Vast Linear</AdTitle> + <Creatives> + <Creative sequence="1" id="1"> + <Linear skipoffset="00:00:02"> + <Duration>00:00:00</Duration> + <MediaFiles> + <MediaFile id="1" delivery="progressive" type="video/mp4" bitrate="500" width="400" height="300" minBitrate="360" maxBitrate="1080" scalable="1" maintainAspectRatio="1" codec="0"> + <![CDATA[ http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4 ]]> + </MediaFile> + </MediaFiles> + <VideoClicks> + <ClickThrough id="1"> + <![CDATA[ http://www.example.com ]]> + </ClickThrough> + <ClickTracking> + <![CDATA[ http://www.example.com/click-tracking ]]> + </ClickTracking> + </VideoClicks> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + <Tracking event="skip">http://www.example.com/skip</Tracking> + <Tracking event="progress" offset="00:00:05">http://www.example.com/progress5</Tracking> + <Tracking event="progress" offset="00:00:10">http://www.example.com/progress10</Tracking> + <Tracking event="progress" offset="00:00:15">http://www.example.com/progress15</Tracking> + <Tracking event="progress" offset="00:00:20">http://www.example.com/progress20</Tracking> + <Tracking event="progress" offset="00:00:25">http://www.example.com/progress25</Tracking> + </TrackingEvents> + <Icons> + <Icon> + <IconClicks> <!-- Will fall back to IconClickTrough if no TitleCTA extension found --> + <IconClickThrough>https://www.example.com/IconClickThrough</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + <UniversalAdId idRegistry="unknown">unknown</UniversalAdId> + </Creative> + </Creatives> + <Extensions> + </Extensions> + <Impression id=""> + <![CDATA[ http://www.example.com/impression ]]> + </Impression> + <ViewableImpression id="1543"> + <Viewable> + <![CDATA[ http://www.example.com/view-impression ]]> + </Viewable> + <NotViewable> + <![CDATA[ https://search.iabtechlab.com/error?errcode=102&imprid=s5-ea2f7f298e28c0c98374491aec3dfeb1&ts=1243 ]]> + </NotViewable> + <ViewUndetermined> + <![CDATA[ https://search.iabtechlab.com/error?errcode=102&imprid=s5-ea2f7f298e28c0c98374491aec3dfeb1&ts=1243 ]]> + </ViewUndetermined> + </ViewableImpression> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_no_ad.xml b/client/fluid-player/test/static/vast_no_ad.xml new file mode 100644 index 0000000..5c08e88 --- /dev/null +++ b/client/fluid-player/test/static/vast_no_ad.xml @@ -0,0 +1,3 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + +</VAST> diff --git a/client/fluid-player/test/static/vast_nonlinear.xml b/client/fluid-player/test/static/vast_nonlinear.xml new file mode 100644 index 0000000..a8a74c6 --- /dev/null +++ b/client/fluid-player/test/static/vast_nonlinear.xml @@ -0,0 +1,46 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="20005"> + <InLine> + <AdSystem version="4.0">iabtechlab</AdSystem> + <AdTitle> + NonLinear Image + </AdTitle> + <Description> + <![CDATA[VAST 3.0 sample tag for Non Linear ad (i.e Overlay ad). Change the StaticResources to have a tag with your own content. Change NonLinear tag's parameters accordingly to view desired results. ]]> + </Description> + <Pricing model="cpm" currency="USD"> + <![CDATA[ 25.00 ]]> + </Pricing> + <Error> + <![CDATA[ + http://example.com/error + ]]> + </Error> + <Impression id="Impression-ID"> + <![CDATA[ + http://example.com/track/impression + ]]> + </Impression> + + <Creatives> + <Creative id="5480" sequence="1"> + <NonLinearAds> + <NonLinear width="480" height="90" minSuggestedDuration="00:00:10" scalable="true" maintainAspectRatio="true"> + <StaticResource creativeType="image/png"> + <![CDATA[ https://mms.businesswire.com/media/20150623005446/en/473787/21/iab_tech_lab.jpg ]]> + </StaticResource> + + <NonLinearClickTracking> + <![CDATA[http://example.com/trackingurl/clickTracking]]> + </NonLinearClickTracking> + <NonLinearClickThrough> + <![CDATA[http://iabtechlab.com]]> + </NonLinearClickThrough> + + </NonLinear> + </NonLinearAds> + </Creative> + </Creatives> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper.xml b/client/fluid-player/test/static/vast_wrapper.xml new file mode 100644 index 0000000..33163ce --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550"> + <Wrapper> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_linear.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + </Wrapper> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_cyclical.xml b/client/fluid-player/test/static/vast_wrapper_cyclical.xml new file mode 100644 index 0000000..802e0dd --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_cyclical.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550"> + <Wrapper> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_wrapper_cyclical.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + </Wrapper> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_false.xml b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_false.xml new file mode 100644 index 0000000..b38b891 --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_false.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550" sequence="1"> + <Wrapper fallbackOnNoAd="0" allowMultipleAds="0"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_no_ad.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension type="waterfall" fallback_index="0"> + <Extension/> + </Extension> + </Extensions> + </Wrapper> + </Ad> + <Ad id="5566276" sequence="2"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="78544088"> + <Linear> + <Duration>00:00:22.342</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>samplesite.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>This is ad #2 in an Ad Pod</MobileText> + <PCText>This is ad #2 in an Ad Pod</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Play Game Now</MobileText> + <PCText>Play Game Now</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Try for Free</MobileText> + <PCText>Try for Free</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_false_http_error.xml b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_false_http_error.xml new file mode 100644 index 0000000..9fc1dc2 --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_false_http_error.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550" sequence="1"> + <Wrapper fallbackOnNoAd="0" allowMultipleAds="0"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/404.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension type="waterfall" fallback_index="0"> + <Extension/> + </Extension> + </Extensions> + </Wrapper> + </Ad> + <Ad id="5566276"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="78544088"> + <Linear> + <Duration>00:00:22.342</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>samplesite.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>This is a stand alone Ad, from the Ad Buffet</MobileText> + <PCText>This is a stand alone Ad, from the Ad Buffet</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678" sequence="2"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>This is the second ad in a pod</MobileText> + <PCText>This is the second ad in a pod</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Try for Free</MobileText> + <PCText>Try for Free</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_true.xml b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_true.xml new file mode 100644 index 0000000..207f39e --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_true.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550" sequence="1"> + <Wrapper fallbackOnNoAd="1" allowMultipleAds="0"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_no_ad.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension type="waterfall" fallback_index="0"> + <Extension/> + </Extension> + </Extensions> + </Wrapper> + </Ad> + <Ad id="5566276"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="78544088"> + <Linear> + <Duration>00:00:22.342</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>samplesite.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>This is a stand alone Ad, from the Ad Buffet</MobileText> + <PCText>This is a stand alone Ad, from the Ad Buffet</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678" sequence="2"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Play Game Now</MobileText> + <PCText>Play Game Now</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Try for Free</MobileText> + <PCText>Try for Free</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_true_http_error.xml b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_true_http_error.xml new file mode 100644 index 0000000..0425518 --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_fallbackOnNoAd_true_http_error.xml @@ -0,0 +1,151 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550" sequence="1"> + <Wrapper fallbackOnNoAd="1" allowMultipleAds="0"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/404.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension type="waterfall" fallback_index="0"> + <Extension/> + </Extension> + </Extensions> + </Wrapper> + </Ad> + <Ad id="5566276"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="78544088"> + <Linear> + <Duration>00:00:22.342</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>samplesite.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>This is a stand alone Ad, from the Ad Buffet</MobileText> + <PCText>This is a stand alone Ad, from the Ad Buffet</PCText> + <DisplayUrl><![CDATA[sample.com]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="4840678" sequence="2"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79253280"> + <Linear> + <Duration>00:00:30.0</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>hczog.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Play Game Now</MobileText> + <PCText>Play Game Now</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[/cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> + <Ad id="5460430"> + <InLine> + <AdSystem>ExoClick</AdSystem> + <AdTitle/> + <Impression id="exotr"><![CDATA[/impression]]></Impression> + <Error><![CDATA[/error]]></Error> + <Creatives> + <Creative sequence="1" id="79167900"> + <Linear> + <Duration>00:00:24.799</Duration> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/progress_10_seconds]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickThrough><![CDATA[/clicktrough]]></ClickThrough> + </VideoClicks> + <MediaFiles> + <MediaFile delivery="progressive" type="video/mp4"><![CDATA[https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4]]></MediaFile> + </MediaFiles> + <Icons> + <Icon> + <IconClicks> + <IconClickThrough>www.zodertracker.com</IconClickThrough> + </IconClicks> + </Icon> + </Icons> + </Linear> + </Creative> + </Creatives> + <Extensions> + <Extension> + <TitleCTA> + <MobileText>Try for Free</MobileText> + <PCText>Try for Free</PCText> + <DisplayUrl><![CDATA[]]></DisplayUrl> + <Tracking><![CDATA[cta_tracking]]></Tracking> + </TitleCTA> + </Extension> + </Extensions> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_followAdditionalWrappers_false.xml b/client/fluid-player/test/static/vast_wrapper_followAdditionalWrappers_false.xml new file mode 100644 index 0000000..c18dfa7 --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_followAdditionalWrappers_false.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872551"> + <Wrapper followAdditionalWrappers="false"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_wrapper.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + </Wrapper> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_followAdditionalWrappers_true.xml b/client/fluid-player/test/static/vast_wrapper_followAdditionalWrappers_true.xml new file mode 100644 index 0000000..8a35b19 --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_followAdditionalWrappers_true.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872551"> + <Wrapper followAdditionalWrappers="true"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_wrapper.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + </Wrapper> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vast_wrapper_to_ad_pod.xml b/client/fluid-player/test/static/vast_wrapper_to_ad_pod.xml new file mode 100644 index 0000000..aaf8906 --- /dev/null +++ b/client/fluid-player/test/static/vast_wrapper_to_ad_pod.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="4872550"> + <Wrapper allowMultipleAds="1" followAdditionalWrappers="0"> + <AdSystem>ExoClick</AdSystem> + <VASTAdTagURI><![CDATA[/static/vast_ad_pod.xml]]></VASTAdTagURI> + <Impression id="exotr"><![CDATA[/wrapper_impression]]></Impression> + <Error><![CDATA[/wrapper_error]]></Error> + <Creatives> + <Creative sequence="1" id="70160400"> + <Linear> + <TrackingEvents> + <Tracking id="prog_1" event="progress" offset="00:00:10.000"><![CDATA[/wrapper_progress]]></Tracking> + </TrackingEvents> + <VideoClicks> + <ClickTracking><![CDATA[/wrapper_click]]></ClickTracking> + </VideoClicks> + </Linear> + </Creative> + </Creatives> + </Wrapper> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/video-thumbnail.jpg b/client/fluid-player/test/static/video-thumbnail.jpg new file mode 100644 index 0000000..31532c3 Binary files /dev/null and b/client/fluid-player/test/static/video-thumbnail.jpg differ diff --git a/client/fluid-player/test/static/vpaid_linear.xml b/client/fluid-player/test/static/vpaid_linear.xml new file mode 100644 index 0000000..fadf221 --- /dev/null +++ b/client/fluid-player/test/static/vpaid_linear.xml @@ -0,0 +1,67 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1234567"> + <InLine> + <AdSystem>GDFP</AdSystem> + <AdTitle>Linear VPAID Example</AdTitle> + <Description>Vpaid Linear Video Ad</Description> + <Error>http://www.example.com/error</Error> + <Impression>http://www.example.com/impression</Impression> + <ViewableImpression> + <Viewable> + <![CDATA[https://example.com/viewable-impression]]> + </Viewable> + <NotViewable> + <![CDATA[https://example.com/not-viewable-impression]]> + </NotViewable> + <ViewUndetermined> + <![CDATA[https://example.com/view-undetermined]]> + </ViewUndetermined> + </ViewableImpression> + <Creatives> + <Creative sequence="1"> + <Linear skipoffset="00:00:03"> + <Duration>00:00:10</Duration> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="unmute">http://www.example.com/unmute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + </TrackingEvents> + <AdParameters> + <![CDATA[ +{"videos":[ {"url":"https://cdn.jsdelivr.net/npm/big-buck-bunny-1080p@0.0.6/video.mp4","mimetype":"video/mp4"}]} +]]> + </AdParameters> + <VideoClicks> + <ClickThrough id="123">http://google.com</ClickThrough> + <ClickTracking id="123">http://www.example.com/click</ClickTracking> + </VideoClicks> + <MediaFiles> + + <!-- + <MediaFile apiFramework="VPAID" type="application/javascript"> + https://secure-ds.serving-sys.com/BurstingCachedScripts/VPAID/HTML5_1_40_0_0/VPAIDAPI.js + </MediaFile> + --> + + <MediaFile apiFramework="VPAID" type="application/javascript"> + https://googleads.github.io/googleads-ima-html5/vpaid/linear/VpaidVideoAd.js + </MediaFile> + + </MediaFiles> + </Linear> + </Creative> + <Creative sequence="1"/> + </Creatives> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/test/static/vpaid_nonlinear.xml b/client/fluid-player/test/static/vpaid_nonlinear.xml new file mode 100644 index 0000000..dce1811 --- /dev/null +++ b/client/fluid-player/test/static/vpaid_nonlinear.xml @@ -0,0 +1,42 @@ +<VAST xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vast4.xsd" version="4.2"> + <Ad id="1234567"> + <InLine> + <AdSystem></AdSystem> + <AdTitle>NonLinear VPAID JS</AdTitle> + <Description>Vpaid NonLinear Ad</Description> + <Error>http://www.example.com/error</Error> + <Impression>http://www.example.com/impression</Impression> + <Creatives> + <Creative sequence="1"> + <NonLinearAds> + <TrackingEvents> + <Tracking event="start">http://www.example.com/start</Tracking> + <Tracking event="firstQuartile">http://www.example.com/firstQuartile</Tracking> + <Tracking event="midpoint">http://www.example.com/midpoint</Tracking> + <Tracking event="thirdQuartile">http://www.example.com/thirdQuartile</Tracking> + <Tracking event="complete">http://www.example.com/complete</Tracking> + <Tracking event="mute">http://www.example.com/mute</Tracking> + <Tracking event="rewind">http://www.example.com/rewind</Tracking> + <Tracking event="pause">http://www.example.com/pause</Tracking> + <Tracking event="resume">http://www.example.com/resume</Tracking> + <Tracking event="fullscreen">http://www.example.com/fullscreen</Tracking> + <Tracking event="creativeView">http://www.example.com/creativeView</Tracking> + <Tracking event="acceptInvitation">http://www.example.com/acceptInvitation</Tracking> + </TrackingEvents> + <NonLinear apiFramework="VPAID" width="480" height="90" id="overlay-1"> + <AdParameters> + <![CDATA[{"overlays":["https://googleads.github.io/googleads-ima-html5/media/NonLinearVpaid.png"], "videos":[ {"url":"https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4","mimetype":"video/mp4"}]} ]]> + </AdParameters> + <StaticResource creativeType="application/javascript"> + <![CDATA[https://googleads.github.io/googleads-ima-html5/vpaid/nonlinear/VpaidNonLinear.js]]> + </StaticResource> + <NonLinearClickThrough id="GDFP"> + <![CDATA[ http://google.com ]]> + </NonLinearClickThrough> + </NonLinear> + </NonLinearAds> + </Creative> + </Creatives> + </InLine> + </Ad> +</VAST> diff --git a/client/fluid-player/vendor/webvtt.js b/client/fluid-player/vendor/webvtt.js new file mode 100644 index 0000000..26160ac --- /dev/null +++ b/client/fluid-player/vendor/webvtt.js @@ -0,0 +1,702 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +// Not intended to be fast, but if you can make it faster, please help out! + +var WebVTTParser = function() { + this.parse = function(input, mode) { + //XXX need global search and replace for \0 + var NEWLINE = /\r\n|\r|\n/, + startTime = Date.now(), + linePos = 0, + lines = input.split(NEWLINE), + alreadyCollected = false, + cues = [], + errors = [] + function err(message, col) { + errors.push({message:message, line:linePos+1, col:col}) + } + + var line = lines[linePos], + lineLength = line.length, + signature = "WEBVTT", + bom = 0, + signature_length = signature.length + + /* Byte order mark */ + if (line[0] === "\ufeff") { + bom = 1 + signature_length += 1 + } + /* SIGNATURE */ + if ( + lineLength < signature_length || + line.indexOf(signature) !== 0+bom || + lineLength > signature_length && + line[signature_length] !== " " && + line[signature_length] !== "\t" + ) { + err("No valid signature. (File needs to start with \"WEBVTT\".)") + } + + linePos++ + + /* HEADER */ + while(lines[linePos] != "" && lines[linePos] != undefined) { + err("No blank line after the signature.") + if(lines[linePos].indexOf("-->") != -1) { + alreadyCollected = true + break + } + linePos++ + } + + /* CUE LOOP */ + while(lines[linePos] != undefined) { + var cue + while(!alreadyCollected && lines[linePos] == "") { + linePos++ + } + if(!alreadyCollected && lines[linePos] == undefined) + break + + /* CUE CREATION */ + cue = { + id:"", + startTime:0, + endTime:0, + pauseOnExit:false, + direction:"horizontal", + snapToLines:true, + linePosition:"auto", + textPosition:50, + size:100, + alignment:"middle", + text:"", + tree:null + } + + var parseTimings = true + + if(lines[linePos].indexOf("-->") == -1) { + cue.id = lines[linePos] + + /* COMMENTS + Not part of the specification's parser as these would just be ignored. However, + we want them to be conforming and not get "Cue identifier cannot be standalone". + */ + if(/^NOTE($|[ \t])/.test(cue.id)) { // .startsWith fails in Chrome + linePos++ + while(lines[linePos] != "" && lines[linePos] != undefined) { + if(lines[linePos].indexOf("-->") != -1) + err("Cannot have timestamp in a comment.") + linePos++ + } + continue + } + + linePos++ + + if(lines[linePos] == "" || lines[linePos] == undefined) { + err("Cue identifier cannot be standalone.") + continue + } + + if(lines[linePos].indexOf("-->") == -1) { + parseTimings = false + err("Cue identifier needs to be followed by timestamp.") + } + } + + /* TIMINGS */ + alreadyCollected = false + var timings = new WebVTTCueTimingsAndSettingsParser(lines[linePos], err) + var previousCueStart = 0 + if(cues.length > 0) { + previousCueStart = cues[cues.length-1].startTime + } + if(parseTimings && !timings.parse(cue, previousCueStart)) { + /* BAD CUE */ + + cue = null + linePos++ + + /* BAD CUE LOOP */ + while(lines[linePos] != "" && lines[linePos] != undefined) { + if(lines[linePos].indexOf("-->") != -1) { + alreadyCollected = true + break + } + linePos++ + } + continue + } + linePos++ + + /* CUE TEXT LOOP */ + while(lines[linePos] != "" && lines[linePos] != undefined) { + if(lines[linePos].indexOf("-->") != -1) { + err("Blank line missing before cue.") + alreadyCollected = true + break + } + if(cue.text != "") + cue.text += "\n" + cue.text += lines[linePos] + linePos++ + } + + /* CUE TEXT PROCESSING */ + var cuetextparser = new WebVTTCueTextParser(cue.text, err, mode) + cue.tree = cuetextparser.parse(cue.startTime, cue.endTime) + cues.push(cue) + } + cues.sort(function(a, b) { + if (a.startTime < b.startTime) + return -1 + if (a.startTime > b.startTime) + return 1 + if (a.endTime > b.endTime) + return -1 + if (a.endTime < b.endTime) + return 1 + return 0 + }) + /* END */ + return {cues:cues, errors:errors, time:Date.now()-startTime} + } +} + +var WebVTTCueTimingsAndSettingsParser = function(line, errorHandler) { + var SPACE = /[\u0020\t\f]/, + NOSPACE = /[^\u0020\t\f]/, + line = line, + pos = 0, + err = function(message) { + errorHandler(message, pos+1) + }, + spaceBeforeSetting = true + function skip(pattern) { + while( + line[pos] != undefined && + pattern.test(line[pos]) + ) { + pos++ + } + } + function collect(pattern) { + var str = "" + while( + line[pos] != undefined && + pattern.test(line[pos]) + ) { + str += line[pos] + pos++ + } + return str + } + /* http://dev.w3.org/html5/webvtt/#collect-a-webvtt-timestamp */ + function timestamp() { + var units = "minutes", + val1, + val2, + val3, + val4 + // 3 + if(line[pos] == undefined) { + err("No timestamp found.") + return + } + // 4 + if(!/\d/.test(line[pos])) { + err("Timestamp must start with a character in the range 0-9.") + return + } + // 5-7 + val1 = collect(/\d/) + if(val1.length > 2 || parseInt(val1, 10) > 59) { + units = "hours" + } + // 8 + if(line[pos] != ":") { + err("No time unit separator found.") + return + } + pos++ + // 9-11 + val2 = collect(/\d/) + if(val2.length != 2) { + err("Must be exactly two digits.") + return + } + // 12 + if(units == "hours" || line[pos] == ":") { + if(line[pos] != ":") { + err("No seconds found or minutes is greater than 59.") + return + } + pos++ + val3 = collect(/\d/) + if(val3.length != 2) { + err("Must be exactly two digits.") + return + } + } else { + val3 = val2 + val2 = val1 + val1 = "0" + } + // 13 + if(line[pos] != ".") { + err("No decimal separator (\".\") found.") + return + } + pos++ + // 14-16 + val4 = collect(/\d/) + if(val4.length != 3) { + err("Milliseconds must be given in three digits.") + return + } + // 17 + if(parseInt(val2, 10) > 59) { + err("You cannot have more than 59 minutes.") + return + } + if(parseInt(val3, 10) > 59) { + err("You cannot have more than 59 seconds.") + return + } + return parseInt(val1, 10) * 60 * 60 + parseInt(val2, 10) * 60 + parseInt(val3, 10) + parseInt(val4, 10) / 1000 + } + + /* http://dev.w3.org/html5/webvtt/#parse-the-webvtt-settings */ + function parseSettings(input, cue) { + var settings = input.split(SPACE), + seen = [] + for(var i=0; i < settings.length; i++) { + if(settings[i] == "") + continue + + var index = settings[i].indexOf(':'), + setting = settings[i].slice(0, index) + value = settings[i].slice(index + 1) + + if(seen.indexOf(setting) != -1) { + err("Duplicate setting.") + } + seen.push(setting) + + if(value == "") { + err("No value for setting defined.") + return + } + + if(setting == "vertical") { // writing direction + if(value != "rl" && value != "lr") { + err("Writing direction can only be set to 'rl' or 'rl'.") + continue + } + cue.direction = value + } else if(setting == "line") { // line position + if(!/\d/.test(value)) { + err("Line position takes a number or percentage.") + continue + } + if(value.indexOf("-", 1) != -1) { + err("Line position can only have '-' at the start.") + continue + } + if(value.indexOf("%") != -1 && value.indexOf("%") != value.length-1) { + err("Line position can only have '%' at the end.") + continue + } + if(value[0] == "-" && value[value.length-1] == "%") { + err("Line position cannot be a negative percentage.") + continue + } + if(value[value.length-1] == "%") { + if(parseInt(value, 10) > 100) { + err("Line position cannot be >100%.") + continue + } + cue.snapToLines = false + } + cue.linePosition = parseInt(value, 10) + } else if(setting == "position") { // text position + if(value[value.length-1] != "%") { + err("Text position must be a percentage.") + continue + } + if(parseInt(value, 10) > 100) { + err("Size cannot be >100%.") + continue + } + cue.textPosition = parseInt(value, 10) + } else if(setting == "size") { // size + if(value[value.length-1] != "%") { + err("Size must be a percentage.") + continue + } + if(parseInt(value, 10) > 100) { + err("Size cannot be >100%.") + continue + } + cue.size = parseInt(value, 10) + } else if(setting == "align") { // alignment + var alignValues = ["start", "middle", "end", "left", "right"] + if(alignValues.indexOf(value) == -1) { + err("Alignment can only be set to one of " + alignValues.join(", ") + ".") + continue + } + cue.alignment = value + } else { + err("Invalid setting.") + } + } + } + + this.parse = function(cue, previousCueStart) { + skip(SPACE) + cue.startTime = timestamp() + if(cue.startTime == undefined) { + return + } + if(cue.startTime < previousCueStart) { + err("Start timestamp is not greater than or equal to start timestamp of previous cue.") + } + if(NOSPACE.test(line[pos])) { + err("Timestamp not separated from '-->' by whitespace.") + } + skip(SPACE) + // 6-8 + if(line[pos] != "-") { + err("No valid timestamp separator found.") + return + } + pos++ + if(line[pos] != "-") { + err("No valid timestamp separator found.") + return + } + pos++ + if(line[pos] != ">") { + err("No valid timestamp separator found.") + return + } + pos++ + if(NOSPACE.test(line[pos])) { + err("'-->' not separated from timestamp by whitespace.") + } + skip(SPACE) + cue.endTime = timestamp() + if(cue.endTime == undefined) { + return + } + if(cue.endTime <= cue.startTime) { + err("End timestamp is not greater than start timestamp.") + } + + if(NOSPACE.test(line[pos])) { + spaceBeforeSetting = false + } + skip(SPACE) + parseSettings(line.substring(pos), cue) + return true + } + this.parseTimestamp = function() { + var ts = timestamp() + if(line[pos] != undefined) { + err("Timestamp must not have trailing characters.") + return + } + return ts + } +} + +var WebVTTCueTextParser = function(line, errorHandler, mode) { + var line = line, + pos = 0, + err = function(message) { + if(mode == "metadata") + return + errorHandler(message, pos+1) + } + + this.parse = function(cueStart, cueEnd) { + var result = {children:[]}, + current = result, + timestamps = [] + + function attach(token) { + current.children.push({type:"object", name:token[1], classes:token[2], children:[], parent:current}) + current = current.children[current.children.length-1] + } + function inScope(name) { + var node = current + while(node) { + if(node.name == name) + return true + node = node.parent + } + return + } + + while(line[pos] != undefined) { + var token = nextToken() + if(token[0] == "text") { + current.children.push({type:"text", value:token[1], parent:current}) + } else if(token[0] == "start tag") { + if(mode == "chapters") + err("Start tags not allowed in chapter title text.") + var name = token[1] + if(name != "v" && name != "lang" && token[3] != "") { + err("Only <v> and <lang> can have an annotation.") + } + if( + name == "c" || + name == "i" || + name == "b" || + name == "u" || + name == "ruby" + ) { + attach(token) + } else if(name == "rt" && current.name == "ruby") { + attach(token) + } else if(name == "v") { + if(inScope("v")) { + err("<v> cannot be nested inside itself.") + } + attach(token) + current.value = token[3] // annotation + if(!token[3]) { + err("<v> requires an annotation.") + } + } else if(name == "lang") { + attach(token) + current.value = token[3] // language + } else { + err("Incorrect start tag.") + } + } else if(token[0] == "end tag") { + if(mode == "chapters") + err("End tags not allowed in chapter title text.") + // XXX check <ruby> content + if(token[1] == current.name) { + current = current.parent + } else if(token[1] == "ruby" && current.name == "rt") { + current = current.parent.parent + } else { + err("Incorrect end tag.") + } + } else if(token[0] == "timestamp") { + if(mode == "chapters") + err("Timestamp not allowed in chapter title text.") + var timings = new WebVTTCueTimingsAndSettingsParser(token[1], err), + timestamp = timings.parseTimestamp() + if(timestamp != undefined) { + if(timestamp <= cueStart || timestamp >= cueEnd) { + err("Timestamp must be between start timestamp and end timestamp.") + } + if(timestamps.length > 0 && timestamps[timestamps.length-1] >= timestamp) { + err("Timestamp must be greater than any previous timestamp.") + } + current.children.push({type:"timestamp", value:timestamp, parent:current}) + timestamps.push(timestamp) + } + } + } + while(current.parent) { + if(current.name != "v") { + err("Required end tag missing.") + } + current = current.parent + } + return result + } + + function nextToken() { + var state = "data", + result = "", + buffer = "", + classes = [] + while(line[pos-1] != undefined || pos == 0) { + var c = line[pos] + if(state == "data") { + if(c == "&") { + buffer = c + state = "escape" + } else if(c == "<" && result == "") { + state = "tag" + } else if(c == "<" || c == undefined) { + return ["text", result] + } else { + result += c + } + } else if(state == "escape") { + if(c == "&") { + err("Incorrect escape.") + result += buffer + buffer = c + } else if(/[abglmnsprt]/.test(c)) { + buffer += c + } else if(c == ";") { + if(buffer == "&") { + result += "&" + } else if(buffer == "<") { + result += "<" + } else if(buffer == ">") { + result += ">" + } else if(buffer == "&lrm") { + result += "\u200e" + } else if(buffer == "&rlm") { + result += "\u200f" + } else if(buffer == " ") { + result += "\u00A0" + } else { + err("Incorrect escape.") + result += buffer + ";" + } + state = "data" + } else if(c == "<" || c == undefined) { + err("Incorrect escape.") + result += buffer + return ["text", result] + } else { + err("Incorrect escape.") + result += buffer + c + state = "data" + } + } else if(state == "tag") { + if(c == "\t" || c == "\n" || c == "\f" || c == " ") { + state = "start tag annotation" + } else if(c == ".") { + state = "start tag class" + } else if(c == "/") { + state = "end tag" + } else if(/\d/.test(c)) { + result = c + state = "timestamp tag" + } else if(c == ">" || c == undefined) { + if(c == ">") { + pos++ + } + return ["start tag", "", [], ""] + } else { + result = c + state = "start tag" + } + } else if(state == "start tag") { + if(c == "\t" || c == "\f" || c == " ") { + state = "start tag annotation" + } else if(c == "\n") { + buffer = c + state = "start tag annotation" + } else if(c == ".") { + state = "start tag class" + } else if(c == ">" || c == undefined) { + if(c == ">") { + pos++ + } + return ["start tag", result, [], ""] + } else { + result += c + } + } else if(state == "start tag class") { + if(c == "\t" || c == "\f" || c == " ") { + classes.push(buffer) + buffer = "" + state = "start tag annotation" + } else if(c == "\n") { + classes.push(buffer) + buffer = c + state = "start tag annotation" + } else if(c == ".") { + classes.push(buffer) + buffer = "" + } else if(c == ">" || c == undefined) { + if(c == ">") { + pos++ + } + classes.push(buffer) + return ["start tag", result, classes, ""] + } else { + buffer += c + } + } else if(state == "start tag annotation") { + if(c == ">" || c == undefined) { + if(c == ">") { + pos++ + } + buffer = buffer.split(/[\u0020\t\f\r\n]+/).filter(function(item) { if(item) return true }).join(" ") + return ["start tag", result, classes, buffer] + } else { + buffer +=c + } + } else if(state == "end tag") { + if(c == ">" || c == undefined) { + if(c == ">") { + pos++ + } + return ["end tag", result] + } else { + result += c + } + } else if(state == "timestamp tag") { + if(c == ">" || c == undefined) { + if(c == ">") { + pos++ + } + return ["timestamp", result] + } else { + result += c + } + } else { + err("Never happens.") // The joke is it might. + } + // 8 + pos++ + } + } +} + +var WebVTTSerializer = function() { + function serializeTree(tree) { + var result = "" + for (var i = 0; i < tree.length; i++) { + var node = tree[i] + if(node.type == "text") { + result += node.value + } else if(node.type == "object") { + result += "<" + node.name + if(node.classes) { + for(var y = 0; y < node.classes.length; y++) { + result += "." + node.classes[y] + } + } + if(node.value) { + result += " " + node.value + } + result += ">" + if(node.children) + result += serializeTree(node.children) + result += "</" + node.name + ">" + } else { + result += "<" + node.value + ">" + } + } + return result + } + function serializeCue(cue) { + return cue.startTime + " " + cue.endTime + "\n" + serializeTree(cue.tree.children) + "\n\n" + } + this.serialize = function(cues) { + var result = "" + for(var i=0;i<cues.length;i++) { + result += serializeCue(cues[i]) + } + return result + } +} + +export default WebVTTParser; diff --git a/client/fluid-player/webpack.config.js b/client/fluid-player/webpack.config.js new file mode 100644 index 0000000..8265036 --- /dev/null +++ b/client/fluid-player/webpack.config.js @@ -0,0 +1,185 @@ +const path = require('path'); +const fs = require('fs'); +const webpack = require('webpack'); +const semver = require('semver'); +const cheerio = require('cheerio'); +const HtmlWebpackPlugin = require('html-webpack-plugin') +const CopyPlugin = require('copy-webpack-plugin'); + +// Loading the current package.json - will be used to determine version etc. +const packageJSON = require(path.resolve(__dirname, 'package.json')); + +// Validate package version is valid semver +if (!semver.valid(packageJSON.version)) { + throw 'Invalid package version - ' + packageJSON.version; +} + +// Distribution options configure how build paths are going to be configured. +const getDistOptions = (mode) => { + const fullVersion = packageJSON.version; + const majorVersion = semver.major(packageJSON.version); + const cdnRoot = packageJSON.com_fluidplayer.cdn; + + switch (mode) { + case 'development': + return { + path: path.resolve(__dirname, 'dist'), + publicPath: '/' + }; + case 'current': + return { + path: path.resolve(__dirname, 'dist-cdn/v' + majorVersion + '/current/'), + publicPath: cdnRoot + '/v' + majorVersion + '/current/' + }; + case 'versioned': + return { + path: path.resolve(__dirname, 'dist-cdn/' + fullVersion + '/'), + publicPath: cdnRoot + '/' + fullVersion + '/' + }; + default: + throw 'Unknown distribution type provided in --dist!'; + } +} + +// Webpack configuration +module.exports = (env, argv) => { + const wpMode = typeof argv.mode !== 'undefined' ? argv.mode : 'development'; + const wpDebug = wpMode === 'development' && typeof env.debug !== 'undefined' && !!env.debug; + const wpDist = typeof env.dist !== 'undefined' ? env.dist : 'development'; + const wpDistOptions = getDistOptions(wpDist); + + if ('development' !== wpDist && (wpMode !== 'production' || wpDebug)) { + throw 'Building a production distribution in development mode or with debug enabled is not allowed!' + } + + const plugins = [ + // Define common variables for use in Fluid Player + new webpack.DefinePlugin({ + FP_BUILD_VERSION: JSON.stringify(packageJSON.version), + FP_HOMEPAGE: JSON.stringify(packageJSON.homepage), + FP_ENV: JSON.stringify(wpMode), + FP_DEBUG: JSON.stringify(wpDebug), + FP_WITH_CSS: false + }) + ]; + + // Development mode builds and development server specifics + if ('development' === wpMode) { + // Locate all E2E cases + const caseFiles = []; + fs.readdirSync(path.resolve(__dirname, 'test/html/')).forEach(file => { + if (file === 'special-cases' || file === 'e2e') { + return; + } + + const absPath = path.resolve(__dirname, 'test/html/', file); + const caseHtml = cheerio.load(fs.readFileSync(absPath)); + const publicName = file.replace('.tpl', ''); + + plugins.push(new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'test/html/', file), + inject: false, + filename: publicName, + scriptLoading: "blocking", + })); + + caseFiles.push({ + file: publicName, + name: caseHtml('title').text() + }); + }); + + fs.readdirSync(path.resolve(__dirname, 'test/html/special-cases')).forEach(file => { + const publicName = file.replace('.tpl', ''); + + plugins.push(new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'test/html/special-cases', file), + inject: false, + filename: publicName, + scriptLoading: "blocking", + })); + }); + + fs.readdirSync(path.resolve(__dirname, 'test/html/e2e')).forEach(file => { + const publicName = file.replace('.tpl', ''); + + plugins.push(new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'test/html/e2e', file), + inject: false, + filename: publicName, + scriptLoading: "blocking", + })); + }); + + // Emit all cases as separate HTML pages + plugins.push(new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'test/index.html'), + filename: 'index.html', + inject: false, + templateParameters: { + cases: caseFiles + } + })); + + // Copy static assets for E2E + plugins.push(new CopyPlugin( + { + patterns: [ + { from: path.resolve(__dirname, 'test/static/'), to: path.resolve(wpDistOptions.path, 'static') } + ] + } + )); + } + + return { + devServer: { + static: wpDistOptions.path, + // index: 'index.html', + // allowedHosts: "all", // To use with remote hosting (ie: ngrok) + }, + devtool: wpMode === 'development' ? 'source-map' : false, + cache: wpMode !== 'development' ? { + type: "filesystem", + buildDependencies: { + // This makes all dependencies of this file - build dependencies + config: [__filename], + // By default webpack and loaders are build dependencies + } + } : undefined, + plugins, + entry: { + fluidplayer: './src/browser.js' + }, + optimization: { + minimize: wpMode !== 'development' + }, + output: { + filename: '[name].min.js', + chunkFilename: '[name].[chunkhash].min.js', + path: wpDistOptions.path, + publicPath: wpDistOptions.publicPath + }, + module: { + rules: [ + { + test: /\.m?js$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'] + } + } + }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.svg/, + type: 'asset' + }, + ], + } + }; +} diff --git a/client/fluid-player/yarn.lock b/client/fluid-player/yarn.lock new file mode 100644 index 0000000..0ce4a39 --- /dev/null +++ b/client/fluid-player/yarn.lock @@ -0,0 +1,3899 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": + version "7.20.14" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.14.tgz" + integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== + +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.12.0", "@babel/core@^7.13.0", "@babel/core@^7.20.12", "@babel/core@^7.4.0-0": + version "7.20.12" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/generator@^7.20.7": + version "7.20.14" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz" + integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== + dependencies: + "@babel/types" "^7.20.7" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" + +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5", "@babel/helper-create-class-features-plugin@^7.20.7": + version "7.20.12" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.12.tgz" + integrity sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": + version "7.20.5" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz" + integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.2.1" + +"@babel/helper-define-polyfill-provider@^0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz" + integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.20.7": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.20.7.tgz" + integrity sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw== + dependencies: + "@babel/types" "^7.20.7" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11": + version "7.20.11" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz" + integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.10" + "@babel/types" "^7.20.7" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.20.2" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz" + integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0": + version "7.20.0" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz" + integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg== + dependencies: + "@babel/types" "^7.20.0" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-wrap-function@^7.18.9": + version "7.20.5" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz" + integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q== + dependencies: + "@babel/helper-function-name" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.5" + "@babel/types" "^7.20.5" + +"@babel/helpers@^7.20.7": + version "7.20.13" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.13.tgz" + integrity sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.13" + "@babel/types" "^7.20.7" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.20.13", "@babel/parser@^7.20.7": + version "7.20.15" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.20.15.tgz" + integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz" + integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-proposal-optional-chaining" "^7.20.7" + +"@babel/plugin-proposal-async-generator-functions@^7.20.1": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz" + integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.20.7.tgz" + integrity sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz" + integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.20.2": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.20.7" + +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.20.7.tgz" + integrity sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.20.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz" + integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.20.0": + version "7.20.0" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz" + integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz" + integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz" + integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" + +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-block-scoping@^7.20.2": + version "7.20.15" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.15.tgz" + integrity sha512-Vv4DMZ6MiNOhu/LdaZsT/bsLRxgL94d269Mv4R/9sp6+Mp++X/JqypZYypJXLlM4mlL352/Egzbzr98iABH1CA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-classes@^7.20.2": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz" + integrity sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz" + integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/template" "^7.20.7" + +"@babel/plugin-transform-destructuring@^7.20.2": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz" + integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-amd@^7.19.6": + version "7.20.11" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz" + integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== + dependencies: + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-modules-commonjs@^7.19.6": + version "7.20.11" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz" + integrity sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw== + dependencies: + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-simple-access" "^7.20.2" + +"@babel/plugin-transform-modules-systemjs@^7.19.6": + version "7.20.11" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz" + integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-identifier" "^7.19.1" + +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": + version "7.20.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz" + integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + +"@babel/plugin-transform-parameters@^7.20.1", "@babel/plugin-transform-parameters@^7.20.7": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz" + integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.20.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz" + integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + regenerator-transform "^0.15.1" + +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.19.0": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz" + integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-env@^7.20.2": + version "7.20.2" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz" + integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== + dependencies: + "@babel/compat-data" "^7.20.1" + "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.20.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.20.2" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.20.2" + "@babel/plugin-transform-classes" "^7.20.2" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.20.2" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.19.6" + "@babel/plugin-transform-modules-commonjs" "^7.19.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.6" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.20.2" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" + semver "^6.3.0" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/runtime@^7.8.4": + version "7.17.9" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.18.10", "@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7": + version "7.20.13" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz" + integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.13" + "@babel/types" "^7.20.7" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.4.4": + version "7.20.7" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz" + integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@playwright/test@^1.49.0": + version "1.49.0" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz" + integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw== + dependencies: + playwright "1.49.0" + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.4" + resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz" + integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.21.0" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.0.tgz" + integrity sha512-35EhHNOXgxnUgh4XCJsGhE7zdlDhYDN/aMG6UbkByCFFNgQ7b3U+uVoqBpicFydR8JEfgdjCF7SJ7MiJfzuiTA== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.31": + version "4.17.33" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz" + integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.16" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.16.tgz" + integrity sha512-LkKpqRZ7zqXJuvoELakaFYuETHjZkSol8EV6cNnyishutDBCCdv6+dsKPbKkCcIk57qRphOLY5sEgClw1bO3gA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.31" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-proxy@^1.17.8": + version "1.17.9" + resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz" + integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== + dependencies: + "@types/node" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/mime@*": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz" + integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== + +"@types/node@*", "@types/node@^22.9.1": + version "22.10.1" + resolved "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz" + integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== + dependencies: + undici-types "~6.20.0" + +"@types/qs@*": + version "6.9.7" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.0" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz" + integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== + dependencies: + "@types/mime" "*" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.1": + version "8.5.4" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz" + integrity sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg== + dependencies: + "@types/node" "*" + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz" + integrity sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w== + +"@webpack-cli/info@^2.0.1": + version "2.0.1" + resolved "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz" + integrity sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA== + +"@webpack-cli/serve@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz" + integrity sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn@^8, acorn@^8.5.0, acorn@^8.7.1: + version "8.8.2" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.12.5, ajv@^6.9.1: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.8.0, ajv@^8.8.2: + version "8.12.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +array-flatten@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +babel-loader@^9.1.2: + version "9.1.2" + resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz" + integrity sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA== + dependencies: + find-cache-dir "^3.3.2" + schema-utils "^4.0.0" + +babel-plugin-polyfill-corejs2@^0.3.3: + version "0.3.3" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz" + integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.3" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" + +babel-plugin-polyfill-regenerator@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz" + integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +bcp-47-match@^1.0.0, bcp-47-match@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz" + integrity sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w== + +bcp-47-normalize@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz" + integrity sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A== + dependencies: + bcp-47 "^1.0.0" + bcp-47-match "^1.0.0" + +bcp-47@^1.0.0: + version "1.0.8" + resolved "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz" + integrity sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag== + dependencies: + is-alphabetical "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +body-parser@1.20.1: + version "1.20.1" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.1.0" + resolved "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz" + integrity sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q== + dependencies: + array-flatten "^2.1.2" + dns-equal "^1.0.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4, "browserslist@>= 4.21.0": + version "4.21.5" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + dependencies: + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +caniuse-lite@^1.0.30001449: + version "1.0.30001450" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz" + integrity sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +cheerio-select@^1.5.0: + version "1.6.0" + resolved "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz" + integrity sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g== + dependencies: + css-select "^4.3.0" + css-what "^6.0.1" + domelementtype "^2.2.0" + domhandler "^4.3.1" + domutils "^2.8.0" + +cheerio@^1.0.0-rc.3: + version "1.0.0-rc.10" + resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + +chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +clean-css@^5.2.2: + version "5.3.2" + resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz" + integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww== + dependencies: + source-map "~0.6.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +codem-isoboxer@0.3.6: + version "0.3.6" + resolved "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.6.tgz" + integrity sha512-LuO8/7LW6XuR5ERn1yavXAfodGRhuY2yP60JTZIw5yNYMCE5lUVbk3NFUCJxjnphQH+Xemp5hOGb1LgUXm00Xw== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +colorette@^2.0.10, colorette@^2.0.14: + version "2.0.19" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + +commander@^10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + +core-js-compat@^3.25.1: + version "3.27.2" + resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.27.2.tgz" + integrity sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg== + dependencies: + browserslist "^4.21.4" + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-loader@^6.7.3: + version "6.7.3" + resolved "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz" + integrity sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.19" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.8" + +css-select@^4.1.3, css-select@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +dashjs@^4.5.2: + version "4.5.2" + resolved "https://registry.npmjs.org/dashjs/-/dashjs-4.5.2.tgz" + integrity sha512-WXPk0lPDSaHjiSVoVRh2jQPiMmB1alKUH8hV2CVmaI0vPUeT1wIY7madVE38SthfOmwS9IJViv1RrxrxdGjElg== + dependencies: + bcp-47-match "^1.0.3" + bcp-47-normalize "^1.1.1" + codem-isoboxer "0.3.6" + es6-promise "^4.2.8" + fast-deep-equal "2.0.1" + html-entities "^1.2.1" + imsc "^1.0.2" + localforage "^1.7.1" + ua-parser-js "^1.0.2" + +debug@^4.1.0, debug@^4.1.1: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz" + integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== + +dns-packet@^5.2.2: + version "5.4.0" + resolved "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz" + integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@^1.0.1, dom-serializer@^1.3.2: + version "1.4.1" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.284: + version "1.4.285" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.285.tgz" + integrity sha512-47o4PPgxfU1KMNejz+Dgaodf7YTcg48uOfV1oM6cs3adrl2+7R+dHkt3Jpxqo0LRCbGJEzTKMUt0RdvByb/leg== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +enhanced-resolve@^5.10.0: + version "5.12.0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz" + integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +envinfo@^7.7.3: + version "7.8.1" + resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es6-promise@^4.2.8: + version "4.2.8" + resolved "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.3: + version "4.18.2" + resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.1" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-deep-equal@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w== + +fast-glob@^3.2.11: + version "3.2.12" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +fastq@^1.6.0: + version "1.15.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +follow-redirects@^1.0.0: + version "1.15.0" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz" + integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-monkey@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global@^4.3.1: + version "4.4.0" + resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globby@^13.1.1: + version "13.1.3" + resolved "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz" + integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hls.js@^1.5.13: + version "1.5.15" + resolved "https://registry.npmjs.org/hls.js/-/hls.js-1.5.15.tgz" + integrity sha512-6cD7xN6bycBHaXz2WyPIaHn/iXFizE5au2yvY5q9aC4wfihxAr16C9fUy4nxh2a3wOw0fEgLRa9dN6wsYjlpNg== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^1.2.1: + version "1.4.0" + resolved "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz" + integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== + +html-entities@^2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz" + integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-webpack-plugin@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz" + integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz" + integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-parser-js@>=0.5.1: + version "0.5.6" + resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.6.tgz" + integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imsc@^1.0.2: + version "1.1.3" + resolved "https://registry.npmjs.org/imsc/-/imsc-1.1.3.tgz" + integrity sha512-IY0hMkVTNoqoYwKEp5UvNNKp/A5jeJUOrIO7judgOyhHT+xC6PA4VBOMAOhdtAYbMRHx9DTgI8p6Z6jhYQPFDA== + dependencies: + sax "1.2.1" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@2, inherits@2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + +ipaddr.js@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz" + integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.8.1: + version "2.9.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.2.2: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +lie@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz" + integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== + dependencies: + immediate "~3.0.5" + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +localforage@^1.7.1: + version "1.10.0" + resolved "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== + dependencies: + lie "3.1.1" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.4.3: + version "3.4.13" + resolved "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz" + integrity sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg== + dependencies: + fs-monkey "^1.0.3" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +"mime-db@>= 1.43.0 < 2", mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz" + integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== + dependencies: + dom-walk "^0.1.0" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.8: + version "2.0.9" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.9.tgz" + integrity sha512-2xfmOrRkGogbTK9R6Leda0DGiXeY3p2NJpy4+gNCffdUvV6mdEJnaDEic1i3Ec2djAo8jWYoJMR5PB0MSMpxUA== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + +object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.9: + version "8.4.0" + resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +panolens@^0.12.1: + version "0.12.1" + resolved "https://registry.npmjs.org/panolens/-/panolens-0.12.1.tgz" + integrity sha512-2hpjm+rRnDdaLD5Bak49K0Y9/X6vOr1OcyJx5piSA6sCOs1tsgchMgKIwpSGCMpBMHWZ10E/Cz4BIwyXYebt5g== + dependencies: + three "^0.105.2" + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +playwright-core@1.49.0: + version "1.49.0" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz" + integrity sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA== + +playwright@1.49.0: + version "1.49.0" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz" + integrity sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A== + dependencies: + playwright-core "1.49.0" + optionalDependencies: + fsevents "2.3.2" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + version "6.0.10" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.1.0, postcss@^8.4.19: + version "8.4.21" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz" + integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regenerator-transform@^0.15.1: + version "0.15.1" + resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^5.2.1: + version "5.2.2" + resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz" + integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsgen "^0.7.1" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsgen@^0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz" + integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA== + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.14.2, resolve@^1.20.0: + version "1.22.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== + dependencies: + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@^5.1.0, safe-buffer@>=5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz" + integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA== + +schema-utils@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz" + integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== + dependencies: + node-forge "^1" + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^6.1.1: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^6.1.2: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.2, semver@^7.3.8: + version "7.3.8" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +string_decoder@^1.1.1, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +style-loader@^3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz" + integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.1.3: + version "5.3.6" + resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz" + integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.14" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.14.1" + +terser@^5.10.0, terser@^5.14.1: + version "5.16.3" + resolved "https://registry.npmjs.org/terser/-/terser-5.16.3.tgz" + integrity sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +three@^0.105.2: + version "0.105.2" + resolved "https://registry.npmjs.org/three/-/three-0.105.2.tgz" + integrity sha512-L3Al37k4g3hVbgFFS251UVtIc25chhyN0/RvXzR0C+uIBToV6EKDG+MZzEXm9L2miGUVMK27W46/VkP6WUZXMg== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tslib@^2.0.3, tslib@^2.2.0: + version "2.4.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +ua-parser-js@^1.0.2: + version "1.0.33" + resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz" + integrity sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + +unpipe@~1.0.0, unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.10: + version "1.0.10" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utila@~0.4: + version "0.4.0" + resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +videojs-vtt.js@^0.15.4: + version "0.15.4" + resolved "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.4.tgz" + integrity sha512-r6IhM325fcLb1D6pgsMkTQT1PpFdUdYZa1iqk7wJEu+QlibBwATPfPc9Bg8Jiym0GE5yP1AG2rMLu+QMVWkYtA== + dependencies: + global "^4.3.1" + +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +webpack-cli@^5.1.1, webpack-cli@5.x.x: + version "5.1.1" + resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz" + integrity sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^2.1.0" + "@webpack-cli/info" "^2.0.1" + "@webpack-cli/serve" "^2.0.4" + colorette "^2.0.14" + commander "^10.0.1" + cross-spawn "^7.0.3" + envinfo "^7.7.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^5.7.3" + +webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.11.1: + version "4.11.1" + resolved "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz" + integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.1" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" + +webpack-merge@^5.7.3: + version "5.8.0" + resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz" + integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +"webpack@^4.0.0 || ^5.0.0", "webpack@^4.37.0 || ^5.0.0", webpack@^5.0.0, webpack@^5.1.0, webpack@^5.20.0, webpack@^5.75.0, webpack@>=5, webpack@5.x.x: + version "5.75.0" + resolved "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz" + integrity sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.7.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + +websocket-driver@^0.7.4, websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.4.2: + version "8.12.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz" + integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..b7528e0 --- /dev/null +++ b/client/index.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html prefix="og: https://ogp.me/ns#"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title><%- title %> + <%- analytics %> + + + +
+ + diff --git a/client/jsconfig.json b/client/jsconfig.json deleted file mode 100644 index 5c02774..0000000 --- a/client/jsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "module": "commonJS", - "target": "es2015", - "moduleResolution": "node" - }, - "exclude": ["node_modules", "dist", "dev", "src"] -} diff --git a/client/package-lock.json b/client/package-lock.json index 6492016..0209635 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,3127 +1,114 @@ { "name": "kemono-2-client", - "version": "0.2.1", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kemono-2-client", - "version": "0.2.1", + "version": "1.4.0", + "hasInstallScript": true, "license": "ISC", "dependencies": { - "@babel/runtime": "^7.22.10", - "@uppy/core": "^3.4.0", - "@uppy/dashboard": "^3.5.1", - "@uppy/form": "^3.0.2", - "@uppy/tus": "^3.1.3", - "diff": "^5.1.0", - "fluid-player": "^3.22.0", - "micromodal": "^0.4.10", + "@babel/runtime": "^7.26.7", + "@dr.pogodin/react-helmet": "^3.0.1", + "@uppy/core": "^4.4.2", + "@uppy/dashboard": "^4.3.1", + "@uppy/form": "^4.1.1", + "@uppy/tus": "^4.2.2", + "clsx": "^2.1.1", + "diff": "^7.0.0", + "fluid-player": "file:./fluid-player", + "micromodal": "^0.6.1", "purecss": "^3.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.5.0", "sha256-wasm": "^2.2.2", - "whatwg-fetch": "^3.6.17" + "swagger-ui-react": "^5.20.7" }, "devDependencies": { - "@babel/core": "^7.22.10", - "@babel/plugin-transform-runtime": "^7.22.10", - "@babel/preset-env": "^7.22.10", - "babel-loader": "^8.3.0", + "@babel/core": "^7.26.8", + "@babel/plugin-transform-runtime": "^7.26.8", + "@babel/preset-env": "^7.26.8", + "@babel/preset-react": "^7.26.3", + "@babel/preset-typescript": "^7.26.0", + "@hyperjump/json-schema": "^1.11.0", + "@modyfi/vite-plugin-yaml": "^1.1.0", + "@types/micromodal": "^0.3.5", + "@types/node": "^22.14.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/sha256-wasm": "^2.2.3", + "@types/swagger-ui-react": "^5.18.0", + "@types/webpack-bundle-analyzer": "^4.7.0", + "@vitejs/plugin-legacy": "^6.0.1", + "@vitejs/plugin-react": "^4.3.4", + "ajv": "^8.17.1", + "babel-loader": "^9.2.1", "buffer": "^6.0.3", - "copy-webpack-plugin": "^8.1.1", - "css-loader": "^5.2.7", - "dotenv": "^8.6.0", - "fs-extra": "^10.1.0", - "html-webpack-plugin": "^5.5.3", - "mini-css-extract-plugin": "^1.6.2", - "postcss": "^8.4.28", - "postcss-loader": "^7.3.3", - "postcss-preset-env": "^9.1.1", - "rimraf": "^3.0.2", - "sass": "^1.66.0", - "sass-loader": "^11.1.1", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "fs-extra": "^11.3.0", + "html-webpack-plugin": "^5.6.3", + "mini-css-extract-plugin": "^2.9.2", + "postcss": "^8.5.1", + "postcss-loader": "^8.1.1", + "postcss-preset-env": "^10.1.3", + "rimraf": "^6.0.1", + "sass": "^1.84.0", + "sass-loader": "^16.0.4 ", "stream-browserify": "^3.0.0", - "style-loader": "^2.0.0", - "webpack": "^5.88.2", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "style-loader": "^4.0.0", + "terser": "^5.39.0", + "ts-loader": "^9.5.2", + "typescript": "^5.7.3", + "vite": "^6.1.0", + "vite-css-modules": "^1.8.4", + "vite-plugin-html": "^3.2.2", + "vite-plugin-static-copy": "^2.2.0", + "webpack": "^5.97.1", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0", "webpack-manifest-plugin": "^5.0.0", - "webpack-merge": "^5.9.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.22.10", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", - "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz", - "integrity": "sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-compilation-targets": "^7.22.10", - "@babel/helper-module-transforms": "^7.22.9", - "@babel/helpers": "^7.22.10", - "@babel/parser": "^7.22.10", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.10", - "@babel/types": "^7.22.10", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.10", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz", - "integrity": "sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", - "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.9", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz", - "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", - "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", - "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", - "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", - "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.22.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", - "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz", - "integrity": "sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz", - "integrity": "sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.10", - "@babel/types": "^7.22.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz", - "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz", - "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", - "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz", - "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz", - "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", - "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.10.tgz", - "integrity": "sha512-RchI7HePu1eu0CYNKHHHQdfenZcM4nz8rew5B1VWqeRKdcwW5aQ5HeG9eTUbWiAS1UrmHVLmoxTWHt3iLD/NhA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.5", - "babel-plugin-polyfill-corejs3": "^0.8.3", - "babel-plugin-polyfill-regenerator": "^0.5.2", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", - "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz", - "integrity": "sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-compilation-targets": "^7.22.10", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.10", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.10", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.6", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.10", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.10", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.10", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.10", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "@babel/types": "^7.22.10", - "babel-plugin-polyfill-corejs2": "^0.4.5", - "babel-plugin-polyfill-corejs3": "^0.8.3", - "babel-plugin-polyfill-regenerator": "^0.5.2", - "core-js-compat": "^3.31.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", - "dev": true - }, - "node_modules/@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.4.tgz", - "integrity": "sha512-zXMGsJetbLoXe+gjEES07MEGjL0Uy3hMxmnGtVBrRpVKr5KV9OgCB09zr/vLrsEtoVQTgJFewxaU8IYSAE4tjg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-3.0.0.tgz", - "integrity": "sha512-rBODd1rY01QcenD34QxbQxLc1g+Uh7z1X/uzTHNQzJUnFCT9/EZYI7KWq+j0YfWMXJsRJ8lVkqBcB0R/qLr+yg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.3.tgz", - "integrity": "sha512-7mJZ8gGRtSQfQKBQFi5N0Z+jzNC0q8bIkwojP1W0w+APzEqHu5wJoGVsvKxVnVklu9F8tW1PikbBRseYnAdv+g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.2.3.tgz", - "integrity": "sha512-YaEnCoPTdhE4lPQFH3dU4IEk8S+yCnxS88wMv45JzlnMfZp57hpqA6qf2gX8uv7IJTJ/43u6pTQmhy7hCjlz7g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^3.0.0", - "@csstools/css-calc": "^1.1.3" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.1.tgz", - "integrity": "sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^2.2.0" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.2.0.tgz", - "integrity": "sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", - "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.0.tgz", - "integrity": "sha512-dVPVVqQG0FixjM9CG/+8eHTsCAxRKqmNh6H69IpruolPlnEF1611f2AoLK8TijTSAsqBSclKd4WHs1KUb/LdJw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.0.0", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-3.0.1.tgz", - "integrity": "sha512-+vrvCQeUifpMeyd42VQ3JPWGQ8cO19+TnGbtfq1SDSgZzRapCQO4aK9h/jhMOKPnxGzbA57oS0aHgP/12N9gSQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/postcss-progressive-custom-properties": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.1.tgz", - "integrity": "sha512-Z5cXkLiccKIVcUTe+fAfjUD7ZUv0j8rq3dSoBpM6I49dcw+50318eYrwUZa3nyb4xNx7ntNNUPmesAc87kPE2Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/postcss-progressive-custom-properties": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.0.tgz", - "integrity": "sha512-FPndJ/7oGlML7/4EhLi902wGOukO0Nn37PjwOQGc0BhhjQPy3np3By4d3M8s9Cfmp9EHEKgUHRN2DQ5HLT/hTw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.1.3", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.0.tgz", - "integrity": "sha512-ntkGj+1uDa/u6lpjPxnkPcjJn7ChO/Kcy08YxctOZI7vwtrdYvFhmE476dq8bj1yna306+jQ9gzXIG/SWfOaRg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.1.tgz", - "integrity": "sha512-IHeFIcksjI8xKX7PWLzAyigM3UvJdZ4btejeNa7y/wXxqD5dyPPZuY55y8HGTrS6ETVTRqfIznoCPtTzIX7ygQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/postcss-progressive-custom-properties": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.1.tgz", - "integrity": "sha512-FYe2K8EOYlL1BUm2HTXVBo6bWAj0xl4khOk6EFhQHy/C5p3rlr8OcetzQuwMeNQ3v25nB06QTgqUHoOUwoEqhA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.0.tgz", - "integrity": "sha512-FH3+zfOfsgtX332IIkRDxiYLmgwyNk49tfltpC6dsZaO4RV2zWY6x9VMIC5cjvmjlDO7DIThpzqaqw2icT8RbQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.0.tgz", - "integrity": "sha512-0I6siRcDymG3RrkNTSvHDMxTQ6mDyYE8awkcaHNgtYacd43msl+4ZWDfQ1yZQ/viczVWjqJkLmPiRHSgxn5nZA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.0.0", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.0.tgz", - "integrity": "sha512-Wki4vxsF6icRvRz8eF9bPpAvwaAt0RHwhVOyzfoFg52XiIMjb6jcbHkGxwpJXP4DVrnFEwpwmrz5aTRqOW82kg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-2.0.0.tgz", - "integrity": "sha512-lCQ1aX8c5+WI4t5EoYf3alTzJNNocMqTb+u1J9CINdDhFh1fjovqK+0aHalUHsNstZmzFPNzIkU4Mb3eM9U8SA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.1.tgz", - "integrity": "sha512-R5s19SscS7CHoxvdYNMu2Y3WDwG4JjdhsejqjunDB1GqfzhtHSvL7b5XxCkUWqm2KRl35hI6kJ4HEaCDd/3BXg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-tokenizer": "^2.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.7.tgz", - "integrity": "sha512-5LGLdu8cJgRPmvkjUNqOPKIKeHbyQmoGKooB5Rh0mp5mLaNI9bl+IjFZ2keY0cztZYsriJsGf6Lu8R5XetuwoQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.1.3", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.2.tgz", - "integrity": "sha512-kQJR6NvTRidsaRjCdHGjra2+fLoFiDQOm5B2aZrhmXqng/hweXjruboKzB326rxQO2L0m0T+gCKbZgyuncyhLg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/media-query-list-parser": "^2.1.4" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.0.tgz", - "integrity": "sha512-HsB66aDWAouOwD/GcfDTS0a7wCuVWaTpXcjl5VKP0XvFxDiU+r0T8FG7xgb6ovZNZ+qzvGIwRM+CLHhDgXrYgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-3.0.0.tgz", - "integrity": "sha512-6Nw55PRXEKEVqn3bzA8gRRPYxr5tf5PssvcE5DRA/nAxKgKtgNZMCHCSd1uxTCWeyLnkf6h5tYRSB0P1Vh/K/A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.1.tgz", - "integrity": "sha512-3TIz+dCPlQPzz4yAEYXchUpfuU2gRYK4u1J+1xatNX85Isg4V+IbLyppblWLV4Vb6npFF8qsHN17rNuxOIy/6w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/postcss-progressive-custom-properties": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.0.0.tgz", - "integrity": "sha512-2/D3CCL9DN2xhuUTP8OKvKnaqJ1j4yZUxuGLsCUOQ16wnDAuMLKLkflOmZF5tsPh/02VPeXRmqIN+U595WAulw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.1.tgz", - "integrity": "sha512-9B8br/7q0bjD1fV3yE22izjc7Oy5hDbDgwdFEz207cdJHYC9yQneJzP3H+/w3RgC7uyfEVhyyhkGRx5YAfJtmg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/postcss-progressive-custom-properties": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.0.tgz", - "integrity": "sha512-GFNVsD97OuEcfHmcT0/DAZWAvTM/FFBDQndIOLawNc1Wq8YqpZwBdHa063Lq+Irk7azygTT+Iinyg3Lt76p7rg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.1.tgz", - "integrity": "sha512-y1sykToXorFE+5cjtp//xAMWEAEple0kcZn2QhzEFIZDDNvGOCp5JvvmmPGsC3eDlj6yQp70l9uXZNLnimEYfA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.1.3", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.0.tgz", - "integrity": "sha512-BAa1MIMJmEZlJ+UkPrkyoz3DC7kLlIl2oDya5yXgvUrelpwxddgz8iMp69qBStdXwuMyfPx46oZcSNx8Z0T2eA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^3.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.1.tgz", - "integrity": "sha512-hW+JPv0MPQfWC1KARgvJI6bisEUFAZWSvUNq/khGCupYV/h6Z9R2ZFz0Xc633LXBst0ezbXpy7NpnPurSx5Klw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^1.1.3", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-3.0.0.tgz", - "integrity": "sha512-P0JD1WHh3avVyKKRKjd0dZIjCEeaBer8t1BbwGMUDtSZaLhXlLNBqZ8KkqHzYWXOJgHleXAny2/sx8LYl6qhEA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz", - "integrity": "sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.0.13" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@transloadit/prettier-bytes": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.0.9.tgz", - "integrity": "sha512-pCvdmea/F3Tn4hAtHqNXmjcixSaroJJ+L3STXlYJdir1g1m2mRQpWbN8a4SvgQtaw2930Ckhdx8qXdXBFMKbAA==" - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.10.tgz", - "integrity": "sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==", - "dev": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true - }, - "node_modules/@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==", - "dev": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.11", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", - "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.5.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", - "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", - "dev": true, - "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz", - "integrity": "sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.5.5", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz", - "integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@uppy/companion-client": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-3.3.0.tgz", - "integrity": "sha512-ogU0QieutbM0A6/yxK91w1Ge4KTC+eAGQMk6JKZu58b435dLScRTCsWGFSSIvt1U8RDY7YDCyl51zawh+A+5CQ==", - "dependencies": { - "@uppy/utils": "^5.4.3", - "namespace-emitter": "^2.0.1" - } - }, - "node_modules/@uppy/core": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@uppy/core/-/core-3.4.0.tgz", - "integrity": "sha512-95NNyXZfuNfB6sgna41fNNPRuTqjrHdlVzkXaJlZzghAckIxNz2CoeMYA1rtgn9o8ykKa2Zdz4kk2MEq8Qy4fw==", - "dependencies": { - "@transloadit/prettier-bytes": "0.0.9", - "@uppy/store-default": "^3.0.3", - "@uppy/utils": "^5.4.3", - "lodash": "^4.17.21", - "mime-match": "^1.0.2", - "namespace-emitter": "^2.0.1", - "nanoid": "^4.0.0", - "preact": "^10.5.13" - } - }, - "node_modules/@uppy/dashboard": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-3.5.1.tgz", - "integrity": "sha512-Fb3FFg4n3QuoLsFb2Cp2wnlEXJ9bYq/uy4d68USqrkAAHiFiT+/y07Lvrf2BN4H5UFCzWEMhgaBrwo792DwxjQ==", - "dependencies": { - "@transloadit/prettier-bytes": "0.0.7", - "@uppy/informer": "^3.0.3", - "@uppy/provider-views": "^3.5.0", - "@uppy/status-bar": "^3.2.4", - "@uppy/thumbnail-generator": "^3.0.4", - "@uppy/utils": "^5.4.3", - "classnames": "^2.2.6", - "is-shallow-equal": "^1.0.1", - "lodash": "^4.17.21", - "memoize-one": "^6.0.0", - "nanoid": "^4.0.0", - "preact": "^10.5.13" - }, - "peerDependencies": { - "@uppy/core": "^3.4.0" - } - }, - "node_modules/@uppy/dashboard/node_modules/@transloadit/prettier-bytes": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", - "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==" - }, - "node_modules/@uppy/form": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@uppy/form/-/form-3.0.2.tgz", - "integrity": "sha512-o1wQy23Yh8q8oh+ZHxwx6RAJFWcoRL9p42l6W1X+9y9MyduXYyHPIRvib6QOp9MHJiqITDpAQQyoHPHSkdYi8Q==", - "dependencies": { - "@uppy/utils": "^5.3.0", - "get-form-data": "^3.0.0" - }, - "peerDependencies": { - "@uppy/core": "^3.2.0" - } - }, - "node_modules/@uppy/informer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@uppy/informer/-/informer-3.0.3.tgz", - "integrity": "sha512-jMMlZ0bCJ2ruJJ0LMl7pJrM/b0e9vjVEHvYYdQghnRSRDSMONcTJXEqNZ0Lu4x7OZR1SGvqqchFk7n3vAsuERw==", - "dependencies": { - "@uppy/utils": "^5.4.3", - "preact": "^10.5.13" - }, - "peerDependencies": { - "@uppy/core": "^3.4.0" - } - }, - "node_modules/@uppy/provider-views": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@uppy/provider-views/-/provider-views-3.5.0.tgz", - "integrity": "sha512-xSp5xQ6NsPLS2XJdsdBQCLgQELEd0BvVM2R34/XFyGTSqeA4NJKHfM6kSKwjW/jkj26CyFN5nth6CGeNaaKQ+w==", - "dependencies": { - "@uppy/utils": "^5.4.3", - "classnames": "^2.2.6", - "nanoid": "^4.0.0", - "p-queue": "^7.3.4", - "preact": "^10.5.13" - }, - "peerDependencies": { - "@uppy/core": "^3.4.0" - } - }, - "node_modules/@uppy/status-bar": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@uppy/status-bar/-/status-bar-3.2.4.tgz", - "integrity": "sha512-WuK0LRmz7H7iBDV0VO+iUNoXmhbyeCEAWzslX0nqhkGuMchIQprVwd80ZegACySajqcpV1RDNxdhmgtCbRn8wA==", - "dependencies": { - "@transloadit/prettier-bytes": "0.0.9", - "@uppy/utils": "^5.4.3", - "classnames": "^2.2.6", - "preact": "^10.5.13" - }, - "peerDependencies": { - "@uppy/core": "^3.4.0" - } - }, - "node_modules/@uppy/store-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-3.0.3.tgz", - "integrity": "sha512-/zlvQNj4HjkthI+7dNdj/8mOlTg1Zb1gJ/ZsOxof0g3xXD+OAwm7asRnOwpfj2dos+lExdW/zMn8XsRGsuvb6Q==" - }, - "node_modules/@uppy/thumbnail-generator": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@uppy/thumbnail-generator/-/thumbnail-generator-3.0.4.tgz", - "integrity": "sha512-f7E+4F6UWunX3jnV3wfL+k5zQaukKmD1z2qYbmRg5OuE9CxDJrNdAVk14KDAi79seejPJa6VVfCgGjTlIGLaRA==", - "dependencies": { - "@uppy/utils": "^5.4.3", - "exifr": "^7.0.0" - }, - "peerDependencies": { - "@uppy/core": "^3.4.0" - } - }, - "node_modules/@uppy/tus": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-3.1.3.tgz", - "integrity": "sha512-AY4tXHfeM+btnG9uKWc2ZiPhnB29xEFTudVbVmC/vEN6oBeKuJVF9NF7z9s34cRxptvvrZsv8pnRkvPJkTdfyQ==", - "dependencies": { - "@uppy/companion-client": "^3.3.0", - "@uppy/utils": "^5.4.3", - "tus-js-client": "^3.0.0" - }, - "peerDependencies": { - "@uppy/core": "^3.4.0" - } - }, - "node_modules/@uppy/utils": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-5.4.3.tgz", - "integrity": "sha512-ewQTWQ5Wu1/ocz/lLCkhoXQwHLRktFK4CxrOsZmeCLK9LxjD1GOwSFjOuL199WDQKXiCle6SVlAJGQ3SDlXVkg==", - "dependencies": { - "lodash": "^4.17.21", - "preact": "^10.5.13" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { + "webpack-merge": "^6.0.1", + "yaml": "^2.7.0" + } + }, + "fluid-player": { + "version": "3.46.0", + "license": "MIT", + "dependencies": { + "dashjs": "^4.5.2", + "es6-promise": "^4.2.8", + "hls.js": "^1.5.13", + "panolens": "^0.12.1", + "videojs-vtt.js": "^0.15.4" + }, + "devDependencies": { + "@babel/core": "^7.20.12", + "@babel/preset-env": "^7.20.2", + "@playwright/test": "^1.49.0", + "@types/node": "^22.9.1", + "babel-loader": "^9.1.2", + "cheerio": "^1.0.0-rc.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.7.3", + "html-webpack-plugin": "^5.5.0", + "semver": "^7.3.2", + "style-loader": "^3.3.1", + "webpack": "^5.75.0", + "webpack-cli": "^5.1.1", + "webpack-dev-server": "^4.11.1" + } + }, + "fluid-player/node_modules/@webpack-cli/configtest": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.15.0" }, @@ -3130,11 +117,12 @@ "webpack-cli": "5.x.x" } }, - "node_modules/@webpack-cli/info": { + "fluid-player/node_modules/@webpack-cli/info": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.15.0" }, @@ -3143,11 +131,12 @@ "webpack-cli": "5.x.x" } }, - "node_modules/@webpack-cli/serve": { + "fluid-player/node_modules/@webpack-cli/serve": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.15.0" }, @@ -3161,3923 +150,19 @@ } } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ajv/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "fluid-player/node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", "dev": true, + "license": "MIT", "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.15", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", - "integrity": "sha512-KCuPB8ZCIqFdA4HwKXsvz7j6gvSDNhDP7WnUjBleRkKjPdvCmHFuQ77ocavI8FT6NdvlBnE2UFr2H4Mycn8Vew==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.21.10", - "caniuse-lite": "^1.0.30001520", - "fraction.js": "^4.2.0", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" - }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", - "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", - "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.31.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", - "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/bcp-47": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", - "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bcp-47-match": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", - "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/bcp-47-normalize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", - "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", - "dependencies": { - "bcp-47": "^1.0.0", - "bcp-47-match": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/bonjour-service": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", - "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/bonjour-service/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001521", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001521.tgz", - "integrity": "sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" - }, - "node_modules/clean-css": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz", - "integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/codem-isoboxer": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.9.tgz", - "integrity": "sha512-4XOTqEzBWrGOZaMd+sTED2hLpzfBbiQCf1W6OBGkIHqk1D8uwy8WFLazVbdQwfDpQ+vf39lqTGPa9IhWW0roTA==" - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/combine-errors": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", - "integrity": "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==", - "dependencies": { - "custom-error-instance": "2.1.1", - "lodash.uniqby": "4.5.0" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "node_modules/copy-webpack-plugin": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-8.1.1.tgz", - "integrity": "sha512-rYM2uzRxrLRpcyPqGceRBDpxxUV8vcDqIKxAUKfcnFpcrPxT5+XvhTxv7XLjo5AvEJFPdAE3zCogG2JVahqgSQ==", - "dev": true, - "dependencies": { - "fast-glob": "^3.2.5", - "glob-parent": "^5.1.1", - "globby": "^11.0.3", - "normalize-path": "^3.0.0", - "p-limit": "^3.1.0", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/core-js-compat": { - "version": "3.32.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.1.tgz", - "integrity": "sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==", - "dev": true, - "dependencies": { - "browserslist": "^4.21.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", - "dev": true, - "dependencies": { - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-blank-pseudo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.0.tgz", - "integrity": "sha512-VbfLlOWO7sBHBTn6pwDQzc07Z0SDydgDBfNfCE0nvrehdBNv9RKsuupIRa/qal0+fBZhAALyQDPMKz5lnvcchw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-has-pseudo": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.0.tgz", - "integrity": "sha512-X+r+JBuoO37FBOWVNhVJhxtSBUFHgHbrcc0CjFT28JEdOw1qaDwABv/uunyodUuSy2hMPe9j/HjssxSlvUmKjg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.0.0", - "postcss-selector-parser": "^6.0.13", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-loader": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", - "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", - "dev": true, - "dependencies": { - "icss-utils": "^5.1.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.15", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^3.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.27.0 || ^5.0.0" - } - }, - "node_modules/css-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/css-prefers-color-scheme": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.0.tgz", - "integrity": "sha512-03QGAk/FXIRseDdLb7XAiu6gidQ0Nd8945xuM7VFVPpc6goJsG9uIO8xQjTxwbPdPIIV4o4AJoOJyt8gwDl67g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssdb": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.7.0.tgz", - "integrity": "sha512-1hN+I3r4VqSNQ+OmMXxYexnumbOONkSil0TWMebVXHtzYW4tRRPovUNHPHj2d4nrgOuYJ8Vs3XwvywsuwwXNNA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ] - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/custom-error-instance": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", - "integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==" - }, - "node_modules/dashjs": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-4.7.1.tgz", - "integrity": "sha512-RPUqJGjR4lXrApHfNOd9G6885q8GpQ4rWecYBMdJjXCtnM8sNg9bhqic3Jl0bTgR0Xzl7Jd86qRc1YZbq1wjPw==", - "dependencies": { - "bcp-47-match": "^1.0.3", - "bcp-47-normalize": "^1.1.1", - "codem-isoboxer": "0.3.9", - "es6-promise": "^4.2.8", - "fast-deep-equal": "2.0.1", - "html-entities": "^1.2.1", - "imsc": "^1.1.3", - "localforage": "^1.7.1", - "path-browserify": "^1.0.1", - "ua-parser-js": "^1.0.2" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", - "dev": true - }, - "node_modules/dns-packet": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.0.tgz", - "integrity": "sha512-rza3UH1LwdHh9qyPXp8lkwpjSNk/AMD3dPytUoRoqnypDUhY0xvbdmVhWOfxO68frEfV9BU8V12Ez7ZsHGZpCQ==", - "dev": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.496", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.496.tgz", - "integrity": "sha512-qeXC3Zbykq44RCrBa4kr8v/dWzYJA8rAwpyh9Qd+NKWoJfjG5vvJqy9XOJ9H4P/lqulZBCgUWAYi+FeK5AuJ8g==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", - "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", - "dev": true - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exifr": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz", - "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==" - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fluid-player": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/fluid-player/-/fluid-player-3.22.0.tgz", - "integrity": "sha512-SyR7hBjB1+sHAApE4Emo5xcIXVz72c68BhLwaGb8EjwEJ9NnWOtMHZGDZeOg4sg2DN5OWo95XPzUhNSHxOmtLw==", - "dependencies": { - "dashjs": "^4.5.2", - "es6-promise": "^4.2.8", - "hls.js": "^1.3.2", - "panolens": "^0.12.1", - "videojs-vtt.js": "^0.15.4" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", - "dev": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-form-data/-/get-form-data-3.0.0.tgz", - "integrity": "sha512-1d53Kn08wlPuLu31/boF1tW2WRYKw3xAWae3mqcjqpDjoqVBtXolbQnudbbEFyFWL7+2SLGRAFdotxNY06V7MA==" - }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hls.js": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.10.tgz", - "integrity": "sha512-wAVSj4Fm2MqOHy5+BlYnlKxXvJlv5IuZHjlzHu18QmjRzSDFQiUDWdHs5+NsFMQrgKEBwuWDcyvaMC9dUzJ5Uw==" - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==" - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz", - "integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==", - "dev": true, - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "node_modules/immutable": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", - "integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imsc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.3.tgz", - "integrity": "sha512-IY0hMkVTNoqoYwKEp5UvNNKp/A5jeJUOrIO7judgOyhHT+xC6PA4VBOMAOhdtAYbMRHx9DTgI8p6Z6jhYQPFDA==", - "dependencies": { - "sax": "1.2.1" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-alphabetical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", - "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", - "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", - "dependencies": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-decimal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", - "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-shallow-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shallow-equal/-/is-shallow-equal-1.0.1.tgz", - "integrity": "sha512-lq5RvK+85Hs5J3p4oA4256M1FEffzmI533ikeDHvJd42nouRRx5wBzt36JuviiGe5dIPyHON/d0/Up+PBo6XkQ==" - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.3.tgz", - "integrity": "sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==", - "dev": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-base64": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.5.tgz", - "integrity": "sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/launch-editor": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", - "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dependencies": { - "lie": "3.1.1" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash._baseiteratee": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", - "integrity": "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==", - "dependencies": { - "lodash._stringtopath": "~4.8.0" - } - }, - "node_modules/lodash._basetostring": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", - "integrity": "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==" - }, - "node_modules/lodash._baseuniq": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", - "integrity": "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==", - "dependencies": { - "lodash._createset": "~4.0.0", - "lodash._root": "~3.0.0" - } - }, - "node_modules/lodash._createset": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", - "integrity": "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==" - }, - "node_modules/lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==" - }, - "node_modules/lodash._stringtopath": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", - "integrity": "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==", - "dependencies": { - "lodash._basetostring": "~4.12.0" - } - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" - }, - "node_modules/lodash.uniqby": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", - "integrity": "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==", - "dependencies": { - "lodash._baseiteratee": "~4.7.0", - "lodash._baseuniq": "~4.6.0" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromodal": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.4.10.tgz", - "integrity": "sha512-BUrEnzMPFBwK8nOE4xUDYHLrlGlLULQVjpja99tpJQPSUEWgw3kTLp1n1qv0HmKU29AiHE7Y7sMLiRziDK4ghQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", - "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", - "dependencies": { - "wildcard": "^1.1.0" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz", - "integrity": "sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0", - "webpack-sources": "^1.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.4.0 || ^5.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/namespace-emitter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", - "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==" - }, - "node_modules/nanoassert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", - "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==" - }, - "node_modules/nanoid": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.2.tgz", - "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^14 || ^16 || >=18" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.3.4.tgz", - "integrity": "sha512-esox8CWt0j9EZECFvkFl2WNPat8LN4t7WWeXq73D9ha0V96qPRufApZi4ZhPwXAln1uVVal429HVVKPa2X0yQg==", - "dependencies": { - "eventemitter3": "^4.0.7", - "p-timeout": "^5.0.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/p-timeout": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", - "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/panolens": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/panolens/-/panolens-0.12.1.tgz", - "integrity": "sha512-2hpjm+rRnDdaLD5Bak49K0Y9/X6vOr1OcyJx5piSA6sCOs1tsgchMgKIwpSGCMpBMHWZ10E/Cz4BIwyXYebt5g==", - "dependencies": { - "three": "^0.105.2" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz", - "integrity": "sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.0.tgz", - "integrity": "sha512-kaWTgnhRKFtfMF8H0+NQBFxgr5CGg05WGe07Mc1ld6XHwwRWlqSbHOW0zwf+BtkBQpsdVUu7+gl9dtdvhWMedw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.2.tgz", - "integrity": "sha512-SfPjgr//VQ/DOCf80STIAsdAs7sbIbxATvVmd+Ec7JvR8onz9pjawhq3BJM3Pie40EE3TyB0P6hft16D33Nlyg==", - "dev": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.0.tgz", - "integrity": "sha512-RmUFL+foS05AKglkEoqfx+KFdKRVmqUAxlHNz4jLqIi7046drIPyerdl4B6j/RA2BSP8FI8gJcHmLRrwJOMnHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-media": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.0.tgz", - "integrity": "sha512-NxDn7C6GJ7X8TsWOa8MbCdq9rLERRLcPfQSp856k1jzMreL8X9M6iWk35JjPRIb9IfRnVohmxAylDRx7n4Rv4g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.3", - "@csstools/css-parser-algorithms": "^2.3.0", - "@csstools/css-tokenizer": "^2.1.1", - "@csstools/media-query-list-parser": "^2.1.2" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-properties": { - "version": "13.3.0", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.0.tgz", - "integrity": "sha512-q4VgtIKSy5+KcUvQ0WxTjDy9DZjQ5VCXAZ9+tT9+aPMbA0z6s2t1nMw0QHszru1ib5ElkXl9JUpYYU37VVUs7g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.4", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.4.tgz", - "integrity": "sha512-TU2xyUUBTlpiLnwyE2ZYMUIYB41MKMkBZ8X8ntkqRDQ8sdBLhFFsPgNcOliBd5+/zcK51C9hRnSE7hKUJMxQSw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.3", - "@csstools/css-parser-algorithms": "^2.3.0", - "@csstools/css-tokenizer": "^2.1.1", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.0.tgz", - "integrity": "sha512-Oy5BBi0dWPwij/IA+yDYj+/OBMQ9EPqAzTHeSNUYrUWdll/PRJmcbiUj0MNcsBi681I1gcSTLvMERPaXzdbvJg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.0.tgz", - "integrity": "sha512-wR8npIkrIVUTicUpCWSSo1f/g7gAEIH70FMqCugY4m4j6TX4E0T2Q5rhfO0gqv00biBZdLyb+HkW8x6as+iJNQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^3.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-9.0.0.tgz", - "integrity": "sha512-zA4TbVaIaT8npZBEROhZmlc+GBKE8AELPHXE7i4TmIUEQhw/P/mSJfY9t6tBzpQ1rABeGtEOHYrW4SboQeONMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-8.0.0.tgz", - "integrity": "sha512-E7+J9nuQzZaA37D/MUZMX1K817RZGDab8qw6pFwzAkDd/QtlWJ9/WTKmzewNiuxzeq6WWY7ATiRePVoDKp+DnA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-5.0.0.tgz", - "integrity": "sha512-YjsEEL6890P7MCv6fch6Am1yq0EhQCJMXyT4LBohiu87+4/WqR7y5W3RIv53WdA901hhytgRvjlrAhibhW4qsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-image-set-function": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.0.tgz", - "integrity": "sha512-bg58QnJexFpPBU4IGPAugAPKV0FuFtX5rHYNSKVaV91TpHN7iwyEzz1bkIPCiSU5+BUN00e+3fV5KFrwIgRocw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-initial": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-lab-function": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.1.tgz", - "integrity": "sha512-/Xl6JitDh7jWkcOLxrHcAlEaqkxyaG3g4iDMy5RyhNaiQPJ9Egf2+Mxp1W2qnH5jB2bj59f3RbdKmC6qx1IcXA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^1.2.2", - "@csstools/css-parser-algorithms": "^2.3.1", - "@csstools/css-tokenizer": "^2.2.0", - "@csstools/postcss-progressive-custom-properties": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", - "dev": true, - "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" }, "engines": { "node": ">= 14.15.0" @@ -7087,780 +172,52 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" + "webpack": "^5.1.0" } }, - "node_modules/postcss-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/postcss-logical": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-7.0.0.tgz", - "integrity": "sha512-zYf3vHkoW82f5UZTEXChTJvH49Yl9X37axTZsJGxrCG2kOUwtaAoz9E7tqYg0lsIoJLybaL8fk/2mOi81zVIUw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nesting": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.1.tgz", - "integrity": "sha512-6LCqCWP9pqwXw/njMvNK0hGY44Fxc4B2EsGbn6xDcxbNRzP8GYoxT7yabVVMLrX3quqOJ9hg2jYMsnkedOf8pA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^3.0.0", - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz", - "integrity": "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==", - "dev": true, - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.2" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.0.tgz", - "integrity": "sha512-2rlxDyeSics/hC2FuMdPnWiP9WUPZ5x7FTuArXLFVpaSQ2woPSfZS4RD59HuEokbZhs/wPUQJ1E3MT6zVv94MQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true, - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-9.0.0.tgz", - "integrity": "sha512-qLEPD9VPH5opDVemwmRaujODF9nExn24VOC3ghgVLEvfYN7VZLwJHes0q/C9YR5hI2UC3VgBE8Wkdp1TxCXhtg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-preset-env": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.1.1.tgz", - "integrity": "sha512-rMPEqyTLm8JLbvaHnDAdQg6SN4Z/NDOsm+CRefg4HmSOiNpTcBXaw4RAaQbfTNe8BB75l4NpoQ6sMdrutdEpdQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-cascade-layers": "^4.0.0", - "@csstools/postcss-color-function": "^3.0.1", - "@csstools/postcss-color-mix-function": "^2.0.1", - "@csstools/postcss-exponential-functions": "^1.0.0", - "@csstools/postcss-font-format-keywords": "^3.0.0", - "@csstools/postcss-gradients-interpolation-method": "^4.0.1", - "@csstools/postcss-hwb-function": "^3.0.1", - "@csstools/postcss-ic-unit": "^3.0.0", - "@csstools/postcss-is-pseudo-class": "^4.0.0", - "@csstools/postcss-logical-float-and-clear": "^2.0.0", - "@csstools/postcss-logical-resize": "^2.0.0", - "@csstools/postcss-logical-viewport-units": "^2.0.1", - "@csstools/postcss-media-minmax": "^1.0.7", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.2", - "@csstools/postcss-nested-calc": "^3.0.0", - "@csstools/postcss-normalize-display-values": "^3.0.0", - "@csstools/postcss-oklab-function": "^3.0.1", - "@csstools/postcss-progressive-custom-properties": "^3.0.0", - "@csstools/postcss-relative-color-syntax": "^2.0.1", - "@csstools/postcss-scope-pseudo-class": "^3.0.0", - "@csstools/postcss-stepped-value-functions": "^3.0.1", - "@csstools/postcss-text-decoration-shorthand": "^3.0.0", - "@csstools/postcss-trigonometric-functions": "^3.0.1", - "@csstools/postcss-unset-value": "^3.0.0", - "autoprefixer": "^10.4.14", - "browserslist": "^4.21.10", - "css-blank-pseudo": "^6.0.0", - "css-has-pseudo": "^6.0.0", - "css-prefers-color-scheme": "^9.0.0", - "cssdb": "^7.7.0", - "postcss-attribute-case-insensitive": "^6.0.2", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^6.0.0", - "postcss-color-hex-alpha": "^9.0.2", - "postcss-color-rebeccapurple": "^9.0.0", - "postcss-custom-media": "^10.0.0", - "postcss-custom-properties": "^13.3.0", - "postcss-custom-selectors": "^7.1.4", - "postcss-dir-pseudo-class": "^8.0.0", - "postcss-double-position-gradients": "^5.0.0", - "postcss-focus-visible": "^9.0.0", - "postcss-focus-within": "^8.0.0", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^5.0.0", - "postcss-image-set-function": "^6.0.0", - "postcss-initial": "^4.0.1", - "postcss-lab-function": "^6.0.1", - "postcss-logical": "^7.0.0", - "postcss-nesting": "^12.0.1", - "postcss-opacity-percentage": "^2.0.0", - "postcss-overflow-shorthand": "^5.0.0", - "postcss-page-break": "^3.0.4", - "postcss-place": "^9.0.0", - "postcss-pseudo-class-any-link": "^9.0.0", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^7.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.0.tgz", - "integrity": "sha512-QNCYIL98VKFKY6HGDEJpF6+K/sg9bxcUYnOmNHJxZS5wsFDFaVoPeG68WAuhsqwbIBSo/b9fjEnTwY2mTSD+uA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^6.0.13" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true, - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.1.tgz", - "integrity": "sha512-1zT5C27b/zeJhchN7fP0kBr16Cc61mu7Si9uWWLoA3Px/D9tIJPKchJCkUH3tPO5D0pCFmGeApAv8XpXBQJ8SQ==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.10" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true - }, - "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/preact": { - "version": "10.17.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.17.1.tgz", - "integrity": "sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/purecss": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/purecss/-/purecss-3.0.0.tgz", - "integrity": "sha512-IdYbGwbmuA7Hy9ACIO1q7ks4xGLcJSVHxJT2BXIz2c4Ve1aSrNU5bAzA1ILT4Gmdy5K59ruWoRPf9WvJZU5fbA==" - }, - "node_modules/qs": { + "fluid-player/node_modules/css-loader": { "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=0.6" + "node": ">= 12.13.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "webpack": { + "optional": true } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dev": true, - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { + "fluid-player/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -7871,987 +228,29 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "fluid-player/node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sass": { - "version": "1.66.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.66.1.tgz", - "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, + "license": "MIT", "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/sass-loader": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-11.1.1.tgz", - "integrity": "sha512-fOCp/zLmj1V1WHDZbUbPgrZhA7HKXHEqkslzB+05U5K9SbSbcmH91C7QLW31AsXikxUMaxXRhhcqWZAxUMLDyA==", - "dev": true, - "dependencies": { - "klona": "^2.0.4", - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 10.13.0" + "node": ">= 12.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", - "sass": "^1.3.0", "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - } } }, - "node_modules/sax": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", - "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", - "dev": true, - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha256-wasm": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/sha256-wasm/-/sha256-wasm-2.2.2.tgz", - "integrity": "sha512-qKSGARvao+JQlFiA+sjJZhJ/61gmW/3aNLblB2rsgIxDlDxsJPHo8a1seXj12oKtuHVgJSJJ7QEGBUYQN741lQ==", - "dependencies": { - "b4a": "^1.0.1", - "nanoassert": "^2.0.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/style-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", - "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/style-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/three": { - "version": "0.105.2", - "resolved": "https://registry.npmjs.org/three/-/three-0.105.2.tgz", - "integrity": "sha512-L3Al37k4g3hVbgFFS251UVtIc25chhyN0/RvXzR0C+uIBToV6EKDG+MZzEXm9L2miGUVMK27W46/VkP6WUZXMg==" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/tus-js-client": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-3.1.1.tgz", - "integrity": "sha512-SZzWP62jEFLmROSRZx+uoGLKqsYWMGK/m+PiNehPVWbCm7/S9zRIMaDxiaOcKdMnFno4luaqP5E+Y1iXXPjP0A==", - "dependencies": { - "buffer-from": "^1.1.2", - "combine-errors": "^3.0.3", - "is-stream": "^2.0.0", - "js-base64": "^3.7.2", - "lodash.throttle": "^4.1.1", - "proper-lockfile": "^4.1.2", - "url-parse": "^1.5.7" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ua-parser-js": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", - "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/videojs-vtt.js": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", - "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", - "dependencies": { - "global": "^4.3.1" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { + "fluid-player/node_modules/webpack-cli": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -8892,102 +291,12 @@ } } }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", - "dev": true, - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "fluid-player/node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, + "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -9017,7 +326,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -9042,27 +351,5864 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "fluid-player/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dev": true, + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", + "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", + "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", + "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", + "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", + "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", + "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", + "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.0.tgz", + "integrity": "sha512-UWjX6t+v+0ckwZ50Y5ShZLnlk95pP5MyW/pon9tiYzl3+18pkTHTFNTKr7rQbfRXPkowt2QAn30o1b6oswszew==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz", + "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.2.tgz", + "integrity": "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.8.tgz", + "integrity": "sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", + "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.8.tgz", + "integrity": "sha512-9dUvP2qpZI6PlGQ/sob+95B3u5u7nkYt9yhZFCC7G9HBRHBxj+QxS/wUlwaMGYW0waf+NIierI8aoDTssEdRYw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.8.tgz", + "integrity": "sha512-yuZpgWUzqZWQhEqfvtJufhl28DgO9sBwSbXbf/59gejNuvZcoUTRGQZhzhwF4ccqb53YAGB+u92z9+eSKoB4YA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", + "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.7.tgz", + "integrity": "sha512-XTb6Mw0v2qXtQYRW9d9duAjDnoTbBpsngD7sRNLmYDjvwU2ebpIHplyxgOeo6jp/Kr52gkLi5VaK5RDCqzMzZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.2", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.8.tgz", + "integrity": "sha512-/K8u9ZyGMGPjmwCSIjgaOLKfic2RIGdFHHes84XW5LnmrvdhOTVxo255NppHi3ROEvoHPW7MplMJgjZK5Q+TxA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.8.tgz", + "integrity": "sha512-CoHQ/0UXrvxLovu0ZeW6c3/20hjJ/QRg6lyXm3dZLY/JgvRU6bdbQZF/Du30A4TvowfcgvIHQmP1bNXUxgDrAw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.8.tgz", + "integrity": "sha512-LpFKjX6hblpeqyych1cKmk+3FJZ19QmaJtqincySoMkbkG/w2tfbnO5oE6mlnCTXcGUJ0rCEuRHvTqKK0nHYUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", + "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", + "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", + "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz", + "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.7.tgz", + "integrity": "sha512-LB6tIP7iBZb5CYv8iRenfBZmbaG3DWNEziOnPjGoQX5P94FBPvvTBy68b/d9NnS5PELKwFmmOYsAEIgEhDPCHA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.2", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz", + "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.8.tgz", + "integrity": "sha512-+5aPsNWgxohXoYNS1f+Ys0x3Qnfehgygv3qrPyv+Y25G0yX54/WlVB+IXprqBLOXHM1gsVF+QQSjlArhygna0Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", + "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.3.tgz", + "integrity": "sha512-dbNeEEPHxAwfQJ3duRL5IPpuD77QAHtRl4bAHRs0vOVhVbHrsL7mHnwe0irYjbs9kYwhAHZBQTLBgmvufPuRkA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.2", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.8.tgz", + "integrity": "sha512-eGE31oLnJDoUysDdjS9MLxNZdtqqSxjDXMdISpLh80QMaYrKs7VINpid34tWQ+iU23Wg5x76qAzf1Q/SLLbZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.2.tgz", + "integrity": "sha512-4EcAvXTUPh7n6UoZZkCzgtCf/wPzMlTNuddcKg7HG8ozfQkUcHsJ2faQKeLmjyKdYPyOUn4YA7yDPf8K/jfIxw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.2", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.7.tgz", + "integrity": "sha512-rdrRCKRnWtj5FyRin0u/gLla7CIvZRw/zMGI1fVJP0Sg/m1WGicjPVHRANL++3HQtsiXKAbPrcPr+VkyGck0IA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.2", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.2.tgz", + "integrity": "sha512-8XvCRrFNseBSAGxeaVTaNijAu+FzUvjwFXtcrynmazGb/9WUdsPCpBX+mHEHShVRq47Gy4peYAoxYs8ltUnmzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.7.tgz", + "integrity": "sha512-qTrZgLju3AV7Djhzuh2Bq/wjFqbcypnk0FhHjxW8DWJQcZLS1HecIus4X2/RLch1ukX7b+YYCdqbEnpIQO5ccg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.2", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/selector-resolve-nested": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", + "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@dr.pogodin/react-helmet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@dr.pogodin/react-helmet/-/react-helmet-3.0.1.tgz", + "integrity": "sha512-nU6pq6ES8pZYfAwMg/MBEwo+krjmVbyKKVrVHxZzZrv0bNuWPqJN3BlaR8S2s7pnwuAuiDJUofm3JdiOL7wIaA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.26.9" + }, + "peerDependencies": { + "react": "19" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hyperjump/browser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hyperjump/browser/-/browser-1.3.0.tgz", + "integrity": "sha512-bf2ZTqpjfvcEq3DAZSg1h0FuliNUddR6nDPuaPb9qNoPPBQQzD1ldtuXX0QggXKQZl0OgsI3eovGCR3Dl5kToA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@hyperjump/json-pointer": "^1.1.0", + "@hyperjump/uri": "^1.2.0", + "content-type": "^1.0.5", + "just-curry-it": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/json-pointer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-1.1.0.tgz", + "integrity": "sha512-tFCKxMKDKK3VEdtUA3EBOS9GmSOS4mbrTjh9v3RnK10BphDMOb6+bxTh++/ae1AyfHyWb6R54O/iaoAtPMZPCg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/json-schema": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-1.12.1.tgz", + "integrity": "sha512-kbXbFsU7m4xPYk2ku0lgnBD8d+P2g4yS8imn8CF16Zf09gbAfeJpmiodbAXOT0e8j00AiW76McgBQdndEr0QbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hyperjump/json-pointer": "^1.1.0", + "@hyperjump/pact": "^1.2.0", + "@hyperjump/uri": "^1.2.0", + "content-type": "^1.0.4", + "json-stringify-deterministic": "^1.0.12", + "just-curry-it": "^5.3.0", + "uuid": "^9.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + }, + "peerDependencies": { + "@hyperjump/browser": "^1.1.0" + } + }, + "node_modules/@hyperjump/pact": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-1.4.0.tgz", + "integrity": "sha512-01Q7VY6BcAkp9W31Fv+ciiZycxZHGlR2N6ba9BifgyclHYHdbaZgITo0U6QMhYRlem4k8pf8J31/tApxvqAz8A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/uri": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hyperjump/uri/-/uri-1.3.1.tgz", + "integrity": "sha512-2ecKymxf6prQMgrNpAvlx4RhsuM5+PFT6oh6uUTZdv5qmBv0RZvxv8LJ7oR30ZxGhdPdZAl4We/1NFc0nqHeAw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.5.0.tgz", + "integrity": "sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@modyfi/vite-plugin-yaml": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@modyfi/vite-plugin-yaml/-/vite-plugin-yaml-1.1.1.tgz", + "integrity": "sha512-rEbfFNlMGLKpAYs2RsfLAhxCHFa6M4QKHHk0A4EYcCJAUwFtFO6qiEdLjUGUTtnRUxAC7GxxCa+ZbeUILSDvqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "5.1.0", + "js-yaml": "4.1.0", + "tosource": "2.0.0-alpha.3" + }, + "peerDependencies": { + "vite": ">=3.2.7" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@playwright/test": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.51.1.tgz", + "integrity": "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.51.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", + "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", + "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", + "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", + "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", + "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", + "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", + "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", + "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", + "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", + "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", + "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", + "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", + "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", + "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", + "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", + "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", + "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", + "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", + "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", + "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@swagger-api/apidom-ast": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.30.tgz", + "integrity": "sha512-5Wj3zdt0dxS9ERVk4qSuqDIsMQ8dP2vop8b494OpJ/O2W261yCV39Z+vN+PqeJ2NiKDRMlJ+QoQ1uVfKwEo8Kg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "node_modules/@swagger-api/apidom-core": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.30.tgz", + "integrity": "sha512-pDnUhXIKKUvmeezQfwKLL05rkOH1L7ueiy5ja5ob9y2w4r+HXDID7qHtDGeRxKZoIt4E3Sd1K37OjcE9fNcknQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.0.2", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-error": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.30.tgz", + "integrity": "sha512-hVDx0kUF1DTyaEXwmsF3wpJClEfnH0pxjEubqtvHpjjeTMgZzmKc5azbYtvgBX3uUpGHyQZyG/O9g94/wIhhMA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "node_modules/@swagger-api/apidom-json-pointer": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.30.tgz", + "integrity": "sha512-G+BDNXU/ARJCbJiFq1A6dh6pNDDp1J0jPfKeIHjsD8aZoRdpJC0F3F7onm8TjQm2cnvAi4B7vPOKzjWrYN1VWw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-ns-api-design-systems": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.30.tgz", + "integrity": "sha512-YsFtttsq39qVU2J9lMD3i+aeuiMD8EjeageszDEePYgb4/k2PZX9YJqb9urwxydBM7BFG7H/r9K/dVUMHFV5hw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-arazzo-1": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.0.0-beta.30.tgz", + "integrity": "sha512-HpszcpuDlSOXWruHzasR64L8640VHVDuy8xXJrhx1iBu+gDHriOM8gbh8jQgWST91H0smtPeTG9WV1/h6frhRw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.30.tgz", + "integrity": "sha512-/DvnCZY2cVz8E79Nc5mXD8J0++D8QT/c1PKPMMGEGVwGWB6XLh8jZM0HERb6yAiLUC0qzv4Jau/iQH1gs/ZtiQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.0.0-beta.30.tgz", + "integrity": "sha512-HZL76SJaUDmL1GuFcev23UX1vVuxSHIED3vvKso+k3KWNfVWZJrr7GX1ELJx84fWW8g3b5S5+nyz5q1ApT084A==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.0-beta.30.tgz", + "integrity": "sha512-D2adAcu/ISoBe0zRbcX0HyaDvWoMhmaL8iPR4pvjLY7soB2tCR4uLEzAkqPa2zaOKBRA2ziF74aNKrKbM5sX8w==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-2019-09": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.30.tgz", + "integrity": "sha512-u5YMIw/g74Z59wPBFS2A2LaheC+EEqRcbpUQOApTvb6zjW+xWxbCuKV1ypzIaVDDPIry8e3mpwjjXLj1mvad5w==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-beta.30", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.30.tgz", + "integrity": "sha512-/Mp11+tBKTN6XnpOiQo/cKnqmvfJhdCniHCK6Bg8wpCI3dMi+nSSpIYgWEPVQfNsLtf/PaYegrtYY56W4UzNRw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.30.tgz", + "integrity": "sha512-6sZ0LLYnEz9KXtt9xTRSc0EORBl5Fj3LUbfabUjqLQZGldsJWU+3TTQ4XtzFFHlan7z2WYyALKP7iP+b60XbPg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.30.tgz", + "integrity": "sha512-nloJUjf6AtKRnBuWmaFkVk7lR7aht9cudXkR/W0ui+feLSJ5rnYy6nyLyGFLZqLnb2cSV8L6bB6tGPJnvc5KzA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.30.tgz", + "integrity": "sha512-7bz6kCgjStTKGGI4wBP2ho574lyfjH5EDPPuXhkwmAG2mOn9MZezlQhsbdo3B+vbi/58mqQb2XCoB4aeP1F+GQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-1": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.30.tgz", + "integrity": "sha512-pq2jxSp0I6xnGzyAiEXWYMuurp8H7TlOQ6Ijr/XX54gNmaIK+yQ3HXc7S6FZx+B2kQx03Tb8Y8O7L7J7YnmFiA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-beta.30", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-json-pointer": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-beta.30.tgz", + "integrity": "sha512-ER5kQtxOXG8W1cQC7xH8EYYUOAMaqVrECIZShoa6yOLoI0/a40xFF5Lansn2P9szR1hT/2neM8KLcjaxCFjXSQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-beta.30.tgz", + "integrity": "sha512-Xghcidv1TJVwrb/jFHQZA5YHPm+LxNPpFjOJYrijugXK72D3a5fqc/2PZzkGXeYefE4lGM+YB83c08N6NDCa4w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.0.0-beta.30.tgz", + "integrity": "sha512-SZajkrTJ7c1I9CI3gnsdHZCQFSIyQ2H/lkWDjA/drZkRcfbR1CTbR2q0BGGlV5Y+nFHBxjRNpPbYbZrqh0WV4w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.0.0-beta.30.tgz", + "integrity": "sha512-T+N1ix+V5IpOWMFcamQRI50830JayD1gifnRm+mVeWJKMzp+xm08bnO8NiR9LQ2SKJZ6FWYM38oG2tAt0Lwxcg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-beta.30.tgz", + "integrity": "sha512-KjyF966T9HVvSsk+RWaOcNDxXBqOWr/09SAw1OdBBfGHqs+xF3KOV7/2RB88Adw3+ZZ3E5oXDvVVhobq8wVvyA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-beta.30.tgz", + "integrity": "sha512-+6zlRD0nP7T5Yiu9hHgP3b7d016WYRXqfr9TW/yqPFInM/tI74ROPJnMQ1G3s0HyW6lB0KX7cG0O0TqcMmnSqg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-beta.30.tgz", + "integrity": "sha512-cciT19OOXafwBnXe9KFVwUGEVu4Zrvb4k12TYNlNqzVg1xA9pBc3Ywq5EgHIhiiQOLY3fILr0fr6B36N6irN2Q==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-beta.30", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.22.1", + "tree-sitter-json": "=0.24.8", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json/node_modules/tree-sitter": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.1.tgz", + "integrity": "sha512-gRO+jk2ljxZlIn20QRskIvpLCMtzuLl5T0BY6L9uvPYD17uUrxlxWkvYCiVqED2q2q7CVtY52Uex4WcYo2FEXw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-beta.30.tgz", + "integrity": "sha512-Q5b9XVTId/FiGSmGKSOxyKJZYdvWcZOqogpLkF0Q8PtPVCgp2LFl73XuJOgjxO1nkE+n/ap+93svgaaxQRaVow==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-beta.30.tgz", + "integrity": "sha512-VsDpKXmRl6sXpgR6o582yyDJqfFfliYVrVWve0DCOTkpvOeOYqPPLA45oMMvunJkqVsBL4Fpy9/ZqAQvdlur7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-beta.30.tgz", + "integrity": "sha512-Q2NQ1/IF500mFuZZDC3tTw75UOTgSknqRyBywsA159BRnqnWxwk/2//Ifh8Vwq/mMyW2zSChigCvnqI+/IvQxA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-beta.30.tgz", + "integrity": "sha512-6Zj1UtbQIwnsVJi2xn+Zl9yn9U014XzkX6QKrpAXIUGNCcjwWIbuOKd3u2T481OOP0BuVf3JpWhRqxumtosV3w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-beta.30.tgz", + "integrity": "sha512-YaGDkZaV9ZRtbIGorsyyqL2x323gLMqqgLrPpAjaBbBFiAJRwF/gwRHMY4iJ85H2YeUxUq0jqtSc3jH3wsQJGg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-beta.30.tgz", + "integrity": "sha512-rBa7daaUrDVAIwJZm+S4lwc5pqNt6avNTGxEB69dNZ3QDJmCC+HUnudUtsG3VqMfP46JITKUPvtzRLGjX8CgRg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.30", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-beta.30.tgz", + "integrity": "sha512-NRmQehyw4gbDzeBAl0zjyPqj4e/jNYgqnRLcOsxTKpWODud8RHBqEvju/M6iET6ru0o+A9265efFzqR9hiE0LA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.0-beta.30", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@swagger-api/apidom-error": "^1.0.0-beta.30", + "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.0", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.22.1", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/@tree-sitter-grammars/tree-sitter-yaml": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.7.0.tgz", + "integrity": "sha512-GOMIK3IaDvECD0eZEhAsLl03RMtM1E8StxuGMn6PpMKFg7jyQ+jSzxJZ4Jmc/tYitah9/AECt8o4tlRQ5yEZQg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.22.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/tree-sitter": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.1.tgz", + "integrity": "sha512-gRO+jk2ljxZlIn20QRskIvpLCMtzuLl5T0BY6L9uvPYD17uUrxlxWkvYCiVqED2q2q7CVtY52Uex4WcYo2FEXw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.1", + "node-gyp-build": "^4.8.2" + } + }, + "node_modules/@swagger-api/apidom-reference": { + "version": "1.0.0-beta.30", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.30.tgz", + "integrity": "sha512-l1MpLMlmaX+y2hra5EadfR37sAMzmEz1wZomVcnw7vJEFlLQo3WwOdFvpQemPCZ9IJHUs+5zhZ++w7z60uKpSw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.0-beta.30", + "@types/ramda": "~0.30.0", + "axios": "^1.8.2", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "optionalDependencies": { + "@swagger-api/apidom-error": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3 <1.0.0-rc.0" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@swaggerexpert/cookie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/cookie/-/cookie-2.0.2.tgz", + "integrity": "sha512-DPI8YJ0Vznk4CT+ekn3rcFNq1uQwvUHZhH6WvTSPD0YKBIlMS9ur2RYKghXuxxOiqOam/i4lHJH4xTIiTgs3Mg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.3.5.tgz", + "integrity": "sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA==", + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/micromodal": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@types/micromodal/-/micromodal-0.3.5.tgz", + "integrity": "sha512-xDref7Vyx0nhfJWpeEkVrSb5l1GuHIyxfePxuHSTP3eW587Qe3hzKcBy0V+1Wjuyh21UhJH46eP43czH2ZRpGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ramda": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", + "license": "MIT", + "dependencies": { + "types-ramda": "^0.30.1" + } + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.0.tgz", + "integrity": "sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sha256-wasm": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/sha256-wasm/-/sha256-wasm-2.2.3.tgz", + "integrity": "sha512-yaESPxpToMojUgEAjVsk0XAxXcVK8e2dyRfFzD9U8wKsdnV4SGrI3tg6tUrRoEW4PEmfWvKFJ2KocSUt5AETfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/swagger-ui-react": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-5.18.0.tgz", + "integrity": "sha512-c2M9adVG7t28t1pq19K9Jt20VLQf0P/fwJwnfcmsVVsdkwCWhRmbKDu+tIs0/NGwJ/7GY8lBx+iKZxuDI5gDbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/webpack-bundle-analyzer": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz", + "integrity": "sha512-c5i2ThslSNSG8W891BRvOd/RoCjI2zwph8maD22b1adtSns20j+0azDDMCK06DiVrzTgnwiDl5Ntmu1YRJw8Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@uppy/companion-client": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-4.4.1.tgz", + "integrity": "sha512-ardMacShsfzaIbqHEH48YlpzWZkBj1qhAj0Dvn3r31p9d0HA5xFUvAdLYrZ6ezKvZ0RcDbf0SB5qCrQMkjscXQ==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^6.1.1", + "namespace-emitter": "^2.0.1", + "p-retry": "^6.1.0" + }, + "peerDependencies": { + "@uppy/core": "^4.4.1" + } + }, + "node_modules/@uppy/companion-client/node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "license": "MIT" + }, + "node_modules/@uppy/companion-client/node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@uppy/core": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-4.4.4.tgz", + "integrity": "sha512-etaG6uSrShnS8x/CGI9ME/XP23jw8FTkujzKbaag4lDTtdlvcs6PxcObtH3I5MxxJXUEWwcfVGTdAUvd/tGCaA==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/store-default": "^4.2.0", + "@uppy/utils": "^6.1.3", + "lodash": "^4.17.21", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^5.0.9", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/dashboard": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@uppy/dashboard/-/dashboard-4.3.3.tgz", + "integrity": "sha512-bZzzyzWzyoCsXxLUKsjT+X6AWNlFjRU1Ui+28Hp0C7A43zm8y0pb8BbwECl8a3NJH10tKNOHwT8IiVwbiGwXPQ==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/informer": "^4.2.1", + "@uppy/provider-views": "^4.4.2", + "@uppy/status-bar": "^4.1.3", + "@uppy/thumbnail-generator": "^4.1.1", + "@uppy/utils": "^6.1.3", + "classnames": "^2.2.6", + "lodash": "^4.17.21", + "memoize-one": "^6.0.0", + "nanoid": "^5.0.9", + "preact": "^10.5.13", + "shallow-equal": "^3.0.0" + }, + "peerDependencies": { + "@uppy/core": "^4.4.4" + } + }, + "node_modules/@uppy/form": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@uppy/form/-/form-4.1.1.tgz", + "integrity": "sha512-S4GqnFOp0Q+el8iz6tTYYdcr4vw2HU5AIeZmKT+vJdYj74JnMGWCJuaAn7VN8w5Bm28bgYoK5M37cGviMB0yrw==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^6.1.1", + "get-form-data": "^3.0.0" + }, + "peerDependencies": { + "@uppy/core": "^4.4.1" + } + }, + "node_modules/@uppy/informer": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@uppy/informer/-/informer-4.2.1.tgz", + "integrity": "sha512-0en8Py47pl6RMDrgUfqFoF807W5kK5AKVJNT1SkTsLiGg5anmTIMuvmNG3k6LN4cn9P/rKyEHSdGcoBBUj9u7Q==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^6.1.1", + "preact": "^10.5.13" + }, + "peerDependencies": { + "@uppy/core": "^4.4.1" + } + }, + "node_modules/@uppy/provider-views": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@uppy/provider-views/-/provider-views-4.4.2.tgz", + "integrity": "sha512-YGrPJuydrksmMCjvo7Ty7/lDLNo/Y8zsOgWgWmVbXB0V5aRvqY49LeKY8HDlOXclKmn6dl5CeQFf7p46txRNGQ==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^6.1.2", + "classnames": "^2.2.6", + "nanoid": "^5.0.9", + "p-queue": "^8.0.0", + "preact": "^10.5.13" + }, + "peerDependencies": { + "@uppy/core": "^4.4.2" + } + }, + "node_modules/@uppy/status-bar": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@uppy/status-bar/-/status-bar-4.1.3.tgz", + "integrity": "sha512-1YlbsoA9lTNL2b7nhehDri15XslVzGLG+J7HFAsxbE2cMHnOusuLCkm03oE9c72pOU9nG2qZV6yqdWBTwdxbNA==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "^0.3.4", + "@uppy/utils": "^6.1.3", + "classnames": "^2.2.6", + "preact": "^10.5.13" + }, + "peerDependencies": { + "@uppy/core": "^4.4.4" + } + }, + "node_modules/@uppy/store-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-4.2.0.tgz", + "integrity": "sha512-PieFVa8yTvRHIqsNKfpO/yaJw5Ae/hT7uT58ryw7gvCBY5bHrNWxH5N0XFe8PFHMpLpLn8v3UXGx9ib9QkB6+Q==", + "license": "MIT" + }, + "node_modules/@uppy/thumbnail-generator": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@uppy/thumbnail-generator/-/thumbnail-generator-4.1.1.tgz", + "integrity": "sha512-65znkGNgVTbVte51IKOhgxOpHGSwYj9Qik2jF2ZBocMbhBY4gPkWFwqMrKQBfddA9KbUa4jVe1psxhAQTzYgiA==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^6.1.1", + "exifr": "^7.0.0" + }, + "peerDependencies": { + "@uppy/core": "^4.4.1" + } + }, + "node_modules/@uppy/tus": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-4.2.2.tgz", + "integrity": "sha512-fauUHqoLDtyRXwoaIyWM8ctuJ+SAXdjuM2eyoPYcGtpVaEGa+AS7IQkJkWz2RrWSdLCHL9O+fk6jKr+0PIDEpQ==", + "license": "MIT", + "dependencies": { + "@uppy/companion-client": "^4.4.1", + "@uppy/utils": "^6.1.1", + "tus-js-client": "^4.2.3" + }, + "peerDependencies": { + "@uppy/core": "^4.4.1" + } + }, + "node_modules/@uppy/utils": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-6.1.3.tgz", + "integrity": "sha512-7WuTtMf0k1g962sE76mKy8aDV/kLeDrF8Wv1oTxaXQzUpmHBAoKd3FXLrQXu7TgM0XNHHRZXAckBttbVOWkKCw==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "preact": "^10.5.13" + } + }, + "node_modules/@vitejs/plugin-legacy": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-6.0.2.tgz", + "integrity": "sha512-b/a6ARuJ1yCoIH/lSjpwPMyqo3NSCoqyxYtff7VCC6cnJfvBTzd7PthcrbomhLZnMsp/eW41b6TrbNSQvHW2lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.9", + "@babel/preset-env": "^7.26.9", + "browserslist": "^4.24.4", + "browserslist-to-esbuild": "^2.1.1", + "core-js": "^3.40.0", + "magic-string": "^0.30.17", + "regenerator-runtime": "^0.14.1", + "systemjs": "^6.15.1" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "peerDependencies": { + "terser": "^5.16.0", + "vite": "^6.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -9070,16 +6216,2893 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server/node_modules/fast-deep-equal": { + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/apg-lite": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.4.tgz", + "integrity": "sha512-B32zCN3IdHIc99Vy7V9BaYTUzLeRA8YXYY1aQD1/5I2aqIrO0coi4t6hJPqMisidlBxhyME8UexkHt31SlR6Og==", + "license": "BSD-2-Clause" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autolinker": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", + "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", + "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-1.0.3.tgz", + "integrity": "sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-1.1.1.tgz", + "integrity": "sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==", + "license": "MIT", + "dependencies": { + "bcp-47": "^1.0.0", + "bcp-47-match": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserslist-to-esbuild": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz", + "integrity": "sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "meow": "^13.0.0" + }, + "bin": { + "browserslist-to-esbuild": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "browserslist": "*" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001712", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz", + "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/codem-isoboxer": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.9.tgz", + "integrity": "sha512-4XOTqEzBWrGOZaMd+sTED2hLpzfBbiQCf1W6OBGkIHqk1D8uwy8WFLazVbdQwfDpQ+vf39lqTGPa9IhWW0roTA==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==", + "dependencies": { + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ignore": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz", + "integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.41.0.tgz", + "integrity": "sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.2.tgz", + "integrity": "sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, + "node_modules/cssdb": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.4.tgz", + "integrity": "sha512-3KSCVkjZJe/QxicVXnbyYSY26WsFc1YoMY7jep1ZKWMEVc7jEm6V2Xq2r+MX8WKQIuB7ofGbnr5iVI+aZpoSzg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==", + "license": "ISC" + }, + "node_modules/dashjs": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/dashjs/-/dashjs-4.7.4.tgz", + "integrity": "sha512-+hldo25QPP3H/NOwqUrvt4uKdMse60/Gsz9AUAnoYfhga8qHWq4nWiojUosOiigbigkDTCAn9ORcvUaKCvmfCA==", + "license": "BSD-3-Clause", + "dependencies": { + "bcp-47-match": "^1.0.3", + "bcp-47-normalize": "^1.1.1", + "codem-isoboxer": "0.3.9", + "es6-promise": "^4.2.8", + "fast-deep-equal": "2.0.1", + "html-entities": "^1.2.1", + "imsc": "^1.1.5", + "localforage": "^1.7.1", + "path-browserify": "^1.0.1", + "ua-parser-js": "^1.0.37" + } + }, + "node_modules/dashjs/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "license": "MIT" + }, + "node_modules/dashjs/node_modules/html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dompurify": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.134", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.134.tgz", + "integrity": "sha512-zSwzrLg3jNP3bwsLqWHmS5z2nIOQ5ngMnfMZOWWtXnqqQkPVyOipxK98w+1beLw1TB+EImPNcG8wVP/cLVs2Og==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exifr": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz", + "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==", + "license": "MIT" + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, - "node_modules/webpack-dev-server/node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fluid-player": { + "resolved": "fluid-player", + "link": true + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generic-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz", + "integrity": "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^3.2.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-form-data/-/get-form-data-3.0.0.tgz", + "integrity": "sha512-1d53Kn08wlPuLu31/boF1tW2WRYKw3xAWae3mqcjqpDjoqVBtXolbQnudbbEFyFWL7+2SLGRAFdotxNY06V7MA==", + "license": "MIT" + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/hls.js": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.1.tgz", + "integrity": "sha512-7GOkcqn0Y9EqU2OJZlzkwxj9Uynuln7URvr7dRjgqNJNZ5UbbjL/v1BjAvQogy57Psdd/ek1u2s6IDEFYlabrA==", + "license": "Apache-2.0" + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", "dev": true, "funding": [ { @@ -9090,24 +9113,1278 @@ "type": "patreon", "url": "https://patreon.com/mdevils" } - ] + ], + "license": "MIT" }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", + "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.1.tgz", + "integrity": "sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/import-local/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imsc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.5.tgz", + "integrity": "sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ==", + "license": "BSD-2-Clause", + "dependencies": { + "sax": "1.2.1" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz", + "integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", + "license": "BSD-3-Clause" + }, + "node_modules/js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-deterministic": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/json-stringify-deterministic/-/json-stringify-deterministic-1.0.12.tgz", + "integrity": "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/just-curry-it": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.3.0.tgz", + "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==", + "dev": true, + "license": "MIT" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", + "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==", + "license": "MIT", + "dependencies": { + "lodash._stringtopath": "~4.8.0" + } + }, + "node_modules/lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==", + "license": "MIT" + }, + "node_modules/lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==", + "license": "MIT", + "dependencies": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "node_modules/lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==", + "license": "MIT" + }, + "node_modules/lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==", + "license": "MIT" + }, + "node_modules/lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==", + "license": "MIT", + "dependencies": { + "lodash._basetostring": "~4.12.0" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==", + "license": "MIT", + "dependencies": { + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromodal": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/micromodal/-/micromodal-0.6.1.tgz", + "integrity": "sha512-rw1fOptxQe3XGDm9xil9hBC2ylPb1kKZyYv4FlK54R/7L+KG+D8SQxPL5L8OlEXAhBDHckeoULYxWSYU9rg/RA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "license": "ISC", + "dependencies": { + "wildcard": "^1.1.0" + } + }, + "node_modules/mime-match/node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", + "license": "MIT" + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" }, "engines": { "node": ">= 12.13.0" @@ -9115,75 +10392,2898 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-manifest-plugin": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz", - "integrity": "sha512-8RQfMAdc5Uw3QbCQ/CBV/AXqOR8mt03B6GJmRbhWopE8GzRfEpn+k0ZuWywxW+5QZsffhmFDY1J6ohqJo+eMuw==", - "dev": true, - "dependencies": { - "tapable": "^2.0.0", - "webpack-sources": "^2.2.0" - }, - "engines": { - "node": ">=12.22.0" }, "peerDependencies": { - "webpack": "^5.47.0" + "webpack": "^5.0.0" } }, - "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", - "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", - "dev": true, + "node_modules/minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "license": "MIT", "dependencies": { - "source-list-map": "^2.0.1", - "source-map": "^0.6.1" + "lodash": "^4.15.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=6" } }, - "node_modules/webpack-merge": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", - "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10.0.0" + "node": "*" } }, - "node_modules/webpack-merge/node_modules/wildcard": { + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mrmime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "license": "MIT", + "engines": { + "node": ">=10" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", + "license": "MIT" + }, + "node_modules/nanoassert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", + "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", + "license": "ISC" + }, + "node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-html-parser": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.4.2.tgz", + "integrity": "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.2.1", + "he": "1.2.0" + } + }, + "node_modules/node-html-parser/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/node-html-parser/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/node-html-parser/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/node-html-parser/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/node-html-parser/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-path-templating": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-2.2.1.tgz", + "integrity": "sha512-eN14VrDvl/YyGxxrkGOHkVkWEoPyhyeydOUrbvjoz8K5eIGgELASwN1eqFOJ2CTQMGCy2EntOK1KdtJ8ZMekcg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/openapi-server-url-templating": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.3.0.tgz", + "integrity": "sha512-DPlCms3KKEbjVQb0spV6Awfn6UWNheuG/+folQPzh/wUaKwuqvj8zt5gagD7qoyxtE03cIiKPgLFS3Q8Bz00uQ==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz", + "integrity": "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/panolens": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/panolens/-/panolens-0.12.1.tgz", + "integrity": "sha512-2hpjm+rRnDdaLD5Bak49K0Y9/X6vOr1OcyJx5piSA6sCOs1tsgchMgKIwpSGCMpBMHWZ10E/Cz4BIwyXYebt5g==", + "license": "MIT", + "dependencies": { + "three": "^0.105.2" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", + "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.2.0.tgz", + "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/playwright": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz", + "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.51.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.51.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz", + "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.8.tgz", + "integrity": "sha512-S/TpMKVKofNvsxfau/+bw+IA6cSfB6/kmzFj5szUofHOVnFFMB2WwK+Zu07BeMD8T0n+ZnTO5uXiMvAKe2dPkA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz", + "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz", + "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz", + "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", + "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-lab-function": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.8.tgz", + "integrity": "sha512-plV21I86Hg9q8omNz13G9fhPtLopIWH06bt/Cb5cs1XnaGU2kUtEitvVd4vtQb/VqCdNUHK5swKn3QFmMRbpDg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.0.8", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-logical": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", + "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", + "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.1.5", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.5.tgz", + "integrity": "sha512-LQybafF/K7H+6fAs4SIkgzkSCixJy0/h0gubDIAP3Ihz+IQBRwsjyvBnAZ3JUHD+A/ITaxVRPDxn//a3Qy4pDw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-cascade-layers": "^5.0.1", + "@csstools/postcss-color-function": "^4.0.8", + "@csstools/postcss-color-mix-function": "^3.0.8", + "@csstools/postcss-content-alt-text": "^2.0.4", + "@csstools/postcss-exponential-functions": "^2.0.7", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.8", + "@csstools/postcss-gradients-interpolation-method": "^5.0.8", + "@csstools/postcss-hwb-function": "^4.0.8", + "@csstools/postcss-ic-unit": "^4.0.0", + "@csstools/postcss-initial": "^2.0.1", + "@csstools/postcss-is-pseudo-class": "^5.0.1", + "@csstools/postcss-light-dark-function": "^2.0.7", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.3", + "@csstools/postcss-media-minmax": "^2.0.7", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.8", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-random-function": "^1.0.3", + "@csstools/postcss-relative-color-syntax": "^3.0.8", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.2", + "@csstools/postcss-stepped-value-functions": "^4.0.7", + "@csstools/postcss-text-decoration-shorthand": "^4.0.2", + "@csstools/postcss-trigonometric-functions": "^4.0.7", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.19", + "browserslist": "^4.24.4", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.2", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.2.3", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.8", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.5", + "postcss-custom-properties": "^14.0.4", + "postcss-custom-selectors": "^8.0.4", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.0", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.8", + "postcss-logical": "^8.1.0", + "postcss-nesting": "^13.0.1", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/preact": { + "version": "10.26.5", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.5.tgz", + "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/purecss": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/purecss/-/purecss-3.0.0.tgz", + "integrity": "sha512-IdYbGwbmuA7Hy9ACIO1q7ks4xGLcJSVHxJT2BXIz2c4Ve1aSrNU5bAzA1ILT4Gmdy5K59ruWoRPf9WvJZU5fbA==", + "license": "BSD-3-Clause" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/ramda-adjunct": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", + "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda-adjunct" + }, + "peerDependencies": { + "ramda": ">= 0.30.0" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "license": "MIT", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, + "node_modules/react-debounce-input": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", + "integrity": "sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.2" + }, + "peerDependencies": { + "immutable": ">=3.6.2" + } + }, + "node_modules/react-immutable-pure-component": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz", + "integrity": "sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==", + "license": "MIT", + "peerDependencies": { + "immutable": ">= 2 || >= 4.0.0-rc", + "react": ">= 16.6", + "react-dom": ">= 16.6" + } + }, + "node_modules/react-inspector": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", + "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz", + "integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.10", + "autolinker": "^3.11.0" + }, + "bin": { + "remarkable": "bin/remarkable.js" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/remarkable/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.39.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", + "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.39.0", + "@rollup/rollup-android-arm64": "4.39.0", + "@rollup/rollup-darwin-arm64": "4.39.0", + "@rollup/rollup-darwin-x64": "4.39.0", + "@rollup/rollup-freebsd-arm64": "4.39.0", + "@rollup/rollup-freebsd-x64": "4.39.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", + "@rollup/rollup-linux-arm-musleabihf": "4.39.0", + "@rollup/rollup-linux-arm64-gnu": "4.39.0", + "@rollup/rollup-linux-arm64-musl": "4.39.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-gnu": "4.39.0", + "@rollup/rollup-linux-riscv64-musl": "4.39.0", + "@rollup/rollup-linux-s390x-gnu": "4.39.0", + "@rollup/rollup-linux-x64-gnu": "4.39.0", + "@rollup/rollup-linux-x64-musl": "4.39.0", + "@rollup/rollup-win32-arm64-msvc": "4.39.0", + "@rollup/rollup-win32-ia32-msvc": "4.39.0", + "@rollup/rollup-win32-x64-msvc": "4.39.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.86.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz", + "integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.5", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", + "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -9193,11 +13293,2087 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/webpack/node_modules/webpack-sources": { + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/sha256-wasm": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/sha256-wasm/-/sha256-wasm-2.2.2.tgz", + "integrity": "sha512-qKSGARvao+JQlFiA+sjJZhJ/61gmW/3aNLblB2rsgIxDlDxsJPHo8a1seXj12oKtuHVgJSJJ7QEGBUYQN741lQ==", + "license": "ISC", + "dependencies": { + "b4a": "^1.0.1", + "nanoassert": "^2.0.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallow-equal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", + "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/short-unique-id": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.2.tgz", + "integrity": "sha512-MlRVyT5RYfDO2kUzBgOPlZriRzG+NIAuwSy1HBN8tahXyFi3+804GGi/mzjUsi6VxgiQuDgMnhoI2FqmSHX8Tg==", + "license": "Apache-2.0", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-client": { + "version": "3.34.4", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.34.4.tgz", + "integrity": "sha512-Qvtu8DtARAx5GwefA0eV1WRLa4Q9bhczrtNAsiBMOx3HkxAOczy1APQhrcblJdLys0xEGQ4xYizYFXfIL9BhpA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.22.15", + "@scarf/scarf": "=1.4.0", + "@swagger-api/apidom-core": ">=1.0.0-beta.13 <1.0.0-rc.0", + "@swagger-api/apidom-error": ">=1.0.0-beta.13 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.13 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.13 <1.0.0-rc.0", + "@swagger-api/apidom-reference": ">=1.0.0-beta.13 <1.0.0-rc.0", + "@swaggerexpert/cookie": "^2.0.2", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "js-yaml": "^4.1.0", + "neotraverse": "=0.6.18", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.2", + "openapi-path-templating": "^2.2.1", + "openapi-server-url-templating": "^1.3.0", + "ramda": "^0.30.1", + "ramda-adjunct": "^5.1.0" + } + }, + "node_modules/swagger-ui-react": { + "version": "5.20.7", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.20.7.tgz", + "integrity": "sha512-TmWWER0OKkwx/IC1G82AaU265oOpETZFt2KjYxvsGwRWB4cN0NYd85jVogd6tATS8YxbiUHfVpUUJp662CCljg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@scarf/scarf": "=1.4.0", + "base64-js": "^1.5.1", + "classnames": "^2.5.1", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "=3.2.4", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "=4.1.0", + "lodash": "^4.17.21", + "prop-types": "^15.8.1", + "randexp": "^0.5.3", + "randombytes": "^2.1.0", + "react-copy-to-clipboard": "5.1.0", + "react-debounce-input": "=3.3.0", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^2.2.0", + "react-inspector": "^6.0.1", + "react-redux": "^9.2.0", + "react-syntax-highlighter": "^15.6.1", + "redux": "^5.0.1", + "redux-immutable": "^4.0.0", + "remarkable": "^2.0.1", + "reselect": "^5.1.1", + "serialize-error": "^8.1.0", + "sha.js": "^2.4.11", + "swagger-client": "^3.34.4", + "url-parse": "^1.5.10", + "xml": "=1.0.1", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0 <19", + "react-dom": ">=16.8.0 <19" + } + }, + "node_modules/swagger-ui-react/node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/swagger-ui-react/node_modules/redux-immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", + "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "license": "BSD-3-Clause", + "peerDependencies": { + "immutable": "^3.8.1 || ^4.0.0-rc.1" + } + }, + "node_modules/systemjs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.15.1.tgz", + "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/three": { + "version": "0.105.2", + "resolved": "https://registry.npmjs.org/three/-/three-0.105.2.tgz", + "integrity": "sha512-L3Al37k4g3hVbgFFS251UVtIc25chhyN0/RvXzR0C+uIBToV6EKDG+MZzEXm9L2miGUVMK27W46/VkP6WUZXMg==", + "license": "MIT" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tosource": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/tosource/-/tosource-2.0.0-alpha.3.tgz", + "integrity": "sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "node_modules/tree-sitter-json": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.24.8.tgz", + "integrity": "sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.2", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-json/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/tree-sitter/node_modules/node-addon-api": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.1.tgz", + "integrity": "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/ts-loader": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", + "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/tus-js-client": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-4.3.1.tgz", + "integrity": "sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.1.2", + "combine-errors": "^3.0.3", + "is-stream": "^2.0.0", + "js-base64": "^3.7.2", + "lodash.throttle": "^4.1.1", + "proper-lockfile": "^4.1.2", + "url-parse": "^1.5.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz", + "integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", + "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/videojs-vtt.js": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz", + "integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==", + "license": "Apache-2.0", + "dependencies": { + "global": "^4.3.1" + } + }, + "node_modules/vite": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz", + "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-css-modules": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/vite-css-modules/-/vite-css-modules-1.8.4.tgz", + "integrity": "sha512-FpHXNyih8rs7TnnzYuLyPvGHWUJ98tJSEFQDjXK/undcidQr5uIPeBZOsojTBVFk7T+Bd9U/ucdJFNUojTNrRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@rollup/pluginutils": "^5.1.4", + "generic-names": "^4.0.0", + "icss-utils": "^5.1.0", + "magic-string": "^0.30.17", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.2.0", + "postcss-modules-scope": "^3.2.1", + "postcss-modules-values": "^4.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/vite-css-modules?sponsor=1" + }, + "peerDependencies": { + "lightningcss": "^1.23.0", + "postcss": "^8.4.33", + "vite": "^5.0.12 || ^6.0.0" + }, + "peerDependenciesMeta": { + "lightningcss": { + "optional": true + } + } + }, + "node_modules/vite-css-modules/node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/vite-css-modules/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite-plugin-html": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/vite-plugin-html/-/vite-plugin-html-3.2.2.tgz", + "integrity": "sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^4.2.0", + "colorette": "^2.0.16", + "connect-history-api-fallback": "^1.6.0", + "consola": "^2.15.3", + "dotenv": "^16.0.0", + "dotenv-expand": "^8.0.2", + "ejs": "^3.1.6", + "fast-glob": "^3.2.11", + "fs-extra": "^10.0.1", + "html-minifier-terser": "^6.1.0", + "node-html-parser": "^5.3.3", + "pathe": "^0.2.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vite-plugin-html/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/vite-plugin-html/node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/vite-plugin-html/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-plugin-static-copy": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.3.1.tgz", + "integrity": "sha512-EfsPcBm3ewg3UMG8RJaC0ADq6/qnUZnokXx4By4+2cAcipjT9i0Y0owIJGqmZI7d6nxk4qB1q5aXOwNuSyPdyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "fast-glob": "^3.2.11", + "fs-extra": "^11.1.0", + "p-map": "^7.0.3", + "picocolors": "^1.0.0" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.5.tgz", + "integrity": "sha512-+J/2VSHN8J47gQUAvF8KDadrfz6uFYVjxoxbKWDoXVsH2u7yLdarCnIURnrMA6uSRkgX3SdmqM5BOoQjPdSh5w==", + "license": "MIT", + "optional": true + }, + "node_modules/webpack": { + "version": "5.99.5", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz", + "integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz", + "integrity": "sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.7", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/webpack-dev-server/node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/memfs": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz", + "integrity": "sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", + "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.1.tgz", + "integrity": "sha512-xTlX7dC3hrASixA2inuWFMz6qHsNi6MT3Uiqw621sJjRTShtpMjbDYhPPZBwWUKdIYKIjSq9em6+uzWayf38aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "webpack": "^5.75.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.13.0" } @@ -9207,6 +15383,7 @@ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -9221,20 +15398,40 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=0.8.0" } }, - "node_modules/whatwg-fetch": { - "version": "3.6.17", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.17.tgz", - "integrity": "sha512-c4ghIvG6th0eudYwKZY5keb81wtFz9/WeAHAoy8+r18kcWlitUIrmGFQ2rWEl4UCKUilD3zCLHOIPheHx5ypRQ==" + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -9246,21 +15443,126 @@ } }, "node_modules/wildcard": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", - "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -9277,23 +15579,68 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "license": "MIT", + "dependencies": { + "repeat-string": "^1.5.2" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zenscroll": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", + "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==", + "license": "Unlicense" } } } diff --git a/client/package.json b/client/package.json index 14cb424..7a573c0 100644 --- a/client/package.json +++ b/client/package.json @@ -1,52 +1,101 @@ { "name": "kemono-2-client", - "version": "0.2.1", + "version": "1.4.0", "description": "frontend for kemono 2", "private": true, - "scripts": { - "dev": "webpack serve --config webpack.dev.js", - "build": "webpack --config webpack.prod.js" - }, - "keywords": [], "author": "BassOfBass", "license": "ISC", + "scripts": { + "start": "vite preview --config ./vite.prod.mjs", + "postinstall": "cd \"fluid-player\" && npm install", + "dev": "vite --config ./vite.dev.mjs", + "validate": "node scripts/validate.mjs && tsc --noEmit", + "prebuild": "cd \"fluid-player\" && npm run build", + "build": "vite build --config ./vite.prod.mjs" + }, + "overrides": { + "vite": "$vite", + "swagger-ui-react": { + "react": "$react", + "react-dom": "$react-dom" + } + }, + "imports": { + "#storage/*": "./src/browser/storage/*/index.ts", + "#hooks": "./src/browser/hooks/index.ts", + "#components/*": "./src/components/*/index.ts", + "#env/*": "./src/env/*.ts", + "#lib/*": "./src/lib/*/index.ts", + "#pages/*": "./src/pages/*.tsx", + "#entities/*": "./src/entities/*/index.ts", + "#css": "./src/css/*.scss", + "#assets/*": "./src/assets/*", + "#api/*": "./src/api/*/index.ts" + }, "dependencies": { - "@babel/runtime": "^7.22.10", - "@uppy/core": "^3.4.0", - "@uppy/dashboard": "^3.5.1", - "@uppy/form": "^3.0.2", - "@uppy/tus": "^3.1.3", - "diff": "^5.1.0", - "fluid-player": "^3.22.0", - "micromodal": "^0.4.10", + "@babel/runtime": "^7.26.7", + "@uppy/core": "^4.4.2", + "@uppy/dashboard": "^4.3.1", + "@uppy/form": "^4.1.1", + "@uppy/tus": "^4.2.2", + "clsx": "^2.1.1", + "diff": "^7.0.0", + "fluid-player": "file:./fluid-player", + "micromodal": "^0.6.1", "purecss": "^3.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "@dr.pogodin/react-helmet": "^3.0.1", + "react-router": "^7.5.0", "sha256-wasm": "^2.2.2", - "whatwg-fetch": "^3.6.17" + "swagger-ui-react": "^5.20.7" }, "devDependencies": { - "@babel/core": "^7.22.10", - "@babel/plugin-transform-runtime": "^7.22.10", - "@babel/preset-env": "^7.22.10", - "babel-loader": "^8.3.0", + "@babel/core": "^7.26.8", + "@babel/plugin-transform-runtime": "^7.26.8", + "@babel/preset-env": "^7.26.8", + "@babel/preset-react": "^7.26.3", + "@babel/preset-typescript": "^7.26.0", + "@hyperjump/json-schema": "^1.11.0", + "@modyfi/vite-plugin-yaml": "^1.1.0", + "@types/micromodal": "^0.3.5", + "@types/node": "^22.14.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@types/sha256-wasm": "^2.2.3", + "@types/swagger-ui-react": "^5.18.0", + "@types/webpack-bundle-analyzer": "^4.7.0", + "@vitejs/plugin-legacy": "^6.0.1", + "@vitejs/plugin-react": "^4.3.4", + "ajv": "^8.17.1", + "babel-loader": "^9.2.1", "buffer": "^6.0.3", - "copy-webpack-plugin": "^8.1.1", - "css-loader": "^5.2.7", - "dotenv": "^8.6.0", - "fs-extra": "^10.1.0", - "html-webpack-plugin": "^5.5.3", - "mini-css-extract-plugin": "^1.6.2", - "postcss": "^8.4.28", - "postcss-loader": "^7.3.3", - "postcss-preset-env": "^9.1.1", - "rimraf": "^3.0.2", - "sass": "^1.66.0", - "sass-loader": "^11.1.1", + "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "fs-extra": "^11.3.0", + "html-webpack-plugin": "^5.6.3", + "mini-css-extract-plugin": "^2.9.2", + "postcss": "^8.5.1", + "postcss-loader": "^8.1.1", + "postcss-preset-env": "^10.1.3", + "rimraf": "^6.0.1", + "sass": "^1.84.0", + "sass-loader": "^16.0.4 ", "stream-browserify": "^3.0.0", - "style-loader": "^2.0.0", - "webpack": "^5.88.2", - "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1", + "style-loader": "^4.0.0", + "terser": "^5.39.0", + "ts-loader": "^9.5.2", + "typescript": "^5.7.3", + "vite": "^6.1.0", + "vite-css-modules": "^1.8.4", + "vite-plugin-html": "^3.2.2", + "vite-plugin-static-copy": "^2.2.0", + "webpack": "^5.97.1", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.0", "webpack-manifest-plugin": "^5.0.0", - "webpack-merge": "^5.9.0" + "webpack-merge": "^6.0.1", + "yaml": "^2.7.0" } } diff --git a/client/scripts/validate.mjs b/client/scripts/validate.mjs new file mode 100644 index 0000000..67b4c3b --- /dev/null +++ b/client/scripts/validate.mjs @@ -0,0 +1,32 @@ +// @ts-check +import path from "node:path"; +import fs from "node:fs/promises"; +import { cwd } from "node:process"; +import { validate } from "@hyperjump/json-schema/openapi-3-1"; +import YAML from "yaml"; +import { parseConfiguration } from "../configs/parse-config.js"; + +const schemaPath = path.join(cwd(), "..", "src", "pages", "api", "schema.yaml"); + +run().catch((error) => { + console.error(error); + process.exitCode = 1; + process.exit(); +}); + +async function run() { + parseConfiguration(); + // const fileContent = await fs.readFile(schemaPath, { encoding: "utf8" }); + // const parsedSchema = YAML.parse(fileContent) + // const output = await validate( + // "https://spec.openapis.org/oas/3.1/schema-base", + // parsedSchema, + // // the library doesn't support values beyond `"FLAG"` and `"BASIC"` + // // but it's the only library in js which can validate OpenAPI 3.1 schemas + // "BASIC" + // ); + + // if (!output.valid) { + // throw new Error("Failed to validate OpenAPI Schema.") + // } +} diff --git a/client/src/api/_index.js b/client/src/api/_index.js deleted file mode 100644 index 6990896..0000000 --- a/client/src/api/_index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { kemonoAPI } from "./kemono/_index"; -export { paysitesAPI } from "./paysites/_index"; diff --git a/client/src/api/account/account.ts b/client/src/api/account/account.ts new file mode 100644 index 0000000..4282ba4 --- /dev/null +++ b/client/src/api/account/account.ts @@ -0,0 +1,19 @@ +import { apiFetch } from "#lib/api"; +import { IAccount } from "#entities/account"; + +interface IResult { + props: { + currentPage: "account"; + title: string; + account: IAccount; + notifications_count: number; + }; +} + +export async function fetchAccount() { + const path = "/account"; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/account/administrator/account.ts b/client/src/api/account/administrator/account.ts new file mode 100644 index 0000000..4e9da46 --- /dev/null +++ b/client/src/api/account/administrator/account.ts @@ -0,0 +1,11 @@ +import { apiV2Fetch } from "#lib/api"; +import { IAccount } from "#entities/account"; + +export async function apiFetchAccount(accountID: number) { + const pathSpec = `/account/administrator/account/{account_id}`; + const path = `/account/administrator/account/${accountID}`; + + const result = await apiV2Fetch(pathSpec, "GET", path); + + return result; +} diff --git a/client/src/api/account/administrator/accounts.ts b/client/src/api/account/administrator/accounts.ts new file mode 100644 index 0000000..4584aa4 --- /dev/null +++ b/client/src/api/account/administrator/accounts.ts @@ -0,0 +1,46 @@ +import { apiV2Fetch } from "#lib/api"; +import { IAccount } from "#entities/account"; + +export async function apiCountAccounts(name?: string, role?: string) { + const pathSpec = `/account/administrator/accounts`; + const path = `/account/administrator/accounts`; + const searchParams = new URLSearchParams(); + + if (name) { + searchParams.set("name", name); + } + + if (role) { + searchParams.set("role", role); + } + + const result = await apiV2Fetch(pathSpec, "GET", path, { + searchParams, + }); + + return result; +} + +export async function apiFetchAccounts( + page: number, + name?: string, + role?: string +) { + const pathSpec = `/account/administrator/accounts/{page}`; + const path = `/account/administrator/accounts/${page}`; + const searchParams = new URLSearchParams(); + + if (name) { + searchParams.set("name", name); + } + + if (role) { + searchParams.set("role", role); + } + + const result = await apiV2Fetch(pathSpec, "GET", path, { + searchParams, + }); + + return result; +} diff --git a/client/src/api/account/administrator/change-roles.ts b/client/src/api/account/administrator/change-roles.ts new file mode 100644 index 0000000..f881c12 --- /dev/null +++ b/client/src/api/account/administrator/change-roles.ts @@ -0,0 +1,22 @@ +import { apiV2Fetch } from "#lib/api"; + +interface IBody { + role: string; +} + +export async function apiChangeTargetAccountRole( + accountID: number, + role: string +) { + const pathSpec = `/account/administrator/account/{account_id}`; + const path = `/account/administrator/account/${accountID}`; + const body: IBody = { + role, + }; + + const targetAccountID = await apiV2Fetch(pathSpec, "PATCH", path, { + body, + }); + + return targetAccountID; +} diff --git a/client/src/api/account/administrator/index.ts b/client/src/api/account/administrator/index.ts new file mode 100644 index 0000000..a4c1526 --- /dev/null +++ b/client/src/api/account/administrator/index.ts @@ -0,0 +1,3 @@ +export { apiCountAccounts, apiFetchAccounts } from "./accounts"; +export { apiChangeTargetAccountRole } from "./change-roles"; +export { apiFetchAccount } from "./account"; diff --git a/client/src/api/account/auto-import-keys/get.ts b/client/src/api/account/auto-import-keys/get.ts new file mode 100644 index 0000000..fd152e4 --- /dev/null +++ b/client/src/api/account/auto-import-keys/get.ts @@ -0,0 +1,19 @@ +import { apiFetch } from "#lib/api"; +import { IAutoImportKey } from "#entities/account"; + +interface IResult { + props: { + currentPage: "account"; + title: "Your service keys"; + service_keys: IAutoImportKey[]; + }; + import_ids: { key_id: string; import_id: string }[]; +} + +export async function fetchAccountAutoImportKeys() { + const path = `/account/keys`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/account/auto-import-keys/index.ts b/client/src/api/account/auto-import-keys/index.ts new file mode 100644 index 0000000..134ec93 --- /dev/null +++ b/client/src/api/account/auto-import-keys/index.ts @@ -0,0 +1,2 @@ +export { fetchAccountAutoImportKeys } from "./get"; +export { fetchRevokeAutoImportKeys } from "./revoke"; diff --git a/client/src/api/account/auto-import-keys/revoke.ts b/client/src/api/account/auto-import-keys/revoke.ts new file mode 100644 index 0000000..1658129 --- /dev/null +++ b/client/src/api/account/auto-import-keys/revoke.ts @@ -0,0 +1,15 @@ +import { apiFetch } from "#lib/api"; + +interface IBody { + revoke: number[]; +} + +export async function fetchRevokeAutoImportKeys(keyIDs: number[]) { + const path = `/account/keys`; + const body: IBody = { + revoke: keyIDs, + }; + await apiFetch(path, { method: "POST", body }); + + return true; +} diff --git a/client/src/api/account/change-password.ts b/client/src/api/account/change-password.ts new file mode 100644 index 0000000..ed647cf --- /dev/null +++ b/client/src/api/account/change-password.ts @@ -0,0 +1,24 @@ +import { apiFetch } from "#lib/api"; + +interface IBody { + "current-password": string; + "new-password": string; + "new-password-confirmation": string; +} + +export async function fetchAccountChangePassword( + currentPassword: string, + newPassword: string, + newPasswordConfirmation: string +) { + const path = `/account/change_password`; + const body: IBody = { + "current-password": currentPassword, + "new-password": newPassword, + "new-password-confirmation": newPasswordConfirmation, + }; + + const result = await apiFetch(path, { method: "POST", body }); + + return result; +} diff --git a/client/src/api/account/dms/get.ts b/client/src/api/account/dms/get.ts new file mode 100644 index 0000000..12b3cf8 --- /dev/null +++ b/client/src/api/account/dms/get.ts @@ -0,0 +1,22 @@ +import { apiFetch } from "#lib/api"; +import { IUnapprovedDM } from "#entities/dms"; + +interface IResult { + currentPage: "import"; + account_id: number; + status: "ignored" | "pending"; + dms: IUnapprovedDM[]; +} + +export async function fetchDMsForReview(status?: "ignored" | "pending") { + const path = `/account/review_dms`; + const params = new URLSearchParams(); + + if (status) { + params.set("status", status); + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/account/dms/index.ts b/client/src/api/account/dms/index.ts new file mode 100644 index 0000000..55f9cda --- /dev/null +++ b/client/src/api/account/dms/index.ts @@ -0,0 +1,2 @@ +export { fetchDMsForReview } from "./get"; +export { fetchApproveDMs } from "./review" diff --git a/client/src/api/account/dms/review.ts b/client/src/api/account/dms/review.ts new file mode 100644 index 0000000..7fa1059 --- /dev/null +++ b/client/src/api/account/dms/review.ts @@ -0,0 +1,21 @@ +import { apiFetch } from "#lib/api"; + +interface IBody { + approved_hashes: string[]; + delete_ignored?: boolean; +} + +export async function fetchApproveDMs( + hashes: string[], + isIgnoredDeleted?: boolean +) { + const path = `/account/review_dms`; + const body: IBody = { + approved_hashes: hashes, + delete_ignored: isIgnoredDeleted, + }; + + const result = await apiFetch(path, { method: "POST", body }); + + return result; +} diff --git a/client/src/api/account/favorites/favorite-post.ts b/client/src/api/account/favorites/favorite-post.ts new file mode 100644 index 0000000..a9be04b --- /dev/null +++ b/client/src/api/account/favorites/favorite-post.ts @@ -0,0 +1,25 @@ +import { apiFetch } from "#lib/api"; + +export async function apiFavoritePost( + service: string, + profileID: string, + postID: string +) { + const path = `/favorites/post/${service}/${profileID}/${postID}`; + + await apiFetch(path, { method: "POST" }); + + return true; +} + +export async function apiUnfavoritePost( + service: string, + profileID: string, + postID: string +) { + const path = `/favorites/post/${service}/${profileID}/${postID}`; + + await apiFetch(path, { method: "DELETE" }); + + return true; +} diff --git a/client/src/api/account/favorites/favorite-profile.ts b/client/src/api/account/favorites/favorite-profile.ts new file mode 100644 index 0000000..9e6db7c --- /dev/null +++ b/client/src/api/account/favorites/favorite-profile.ts @@ -0,0 +1,17 @@ +import { apiFetch } from "#lib/api"; + +export async function apiFavoriteProfile(service: string, profileID: string) { + const path = `/favorites/creator/${service}/${profileID}`; + + await apiFetch(path, { method: "POST" }); + + return true; +} + +export async function apiUnfavoriteProfile(service: string, profileID: string) { + const path = `/favorites/creator/${service}/${profileID}`; + + await apiFetch(path, { method: "DELETE" }); + + return true; +} diff --git a/client/src/api/account/favorites/get-favourite-artists.ts b/client/src/api/account/favorites/get-favourite-artists.ts new file mode 100644 index 0000000..665297f --- /dev/null +++ b/client/src/api/account/favorites/get-favourite-artists.ts @@ -0,0 +1,15 @@ +import { apiFetch } from "#lib/api"; +import { IFavouriteArtist } from "#entities/account"; + +export async function fetchFavouriteProfiles() { + const path = `/account/favorites`; + const params = new URLSearchParams([["type", "artist"]]); + + const data = await apiFetch( + path, + { method: "GET" }, + params + ); + + return data; +} diff --git a/client/src/api/account/favorites/get-favourite-posts.ts b/client/src/api/account/favorites/get-favourite-posts.ts new file mode 100644 index 0000000..5b2b964 --- /dev/null +++ b/client/src/api/account/favorites/get-favourite-posts.ts @@ -0,0 +1,15 @@ +import { apiFetch } from "#lib/api"; +import { IFavouritePost } from "#entities/account"; + +export async function fetchFavouritePosts() { + const path = `/account/favorites`; + const params = new URLSearchParams([["type", "post"]]); + + const data = await apiFetch( + path, + { method: "GET" }, + params + ); + + return data; +} diff --git a/client/src/api/account/favorites/index.ts b/client/src/api/account/favorites/index.ts new file mode 100644 index 0000000..961927c --- /dev/null +++ b/client/src/api/account/favorites/index.ts @@ -0,0 +1,4 @@ +export { fetchFavouriteProfiles } from "./get-favourite-artists"; +export { fetchFavouritePosts } from "./get-favourite-posts"; +export { apiFavoritePost, apiUnfavoritePost } from "./favorite-post"; +export { apiFavoriteProfile, apiUnfavoriteProfile } from "./favorite-profile"; diff --git a/client/src/api/account/index.ts b/client/src/api/account/index.ts new file mode 100644 index 0000000..e87cc7a --- /dev/null +++ b/client/src/api/account/index.ts @@ -0,0 +1,4 @@ +export { fetchAccount } from "./account"; +export { fetchAccountNotifications } from "./notifications"; +export { fetchAddProfileLink } from "./profiles"; +export { fetchAccountChangePassword } from "./change-password"; diff --git a/client/src/api/account/moderator/index.ts b/client/src/api/account/moderator/index.ts new file mode 100644 index 0000000..36ddfc1 --- /dev/null +++ b/client/src/api/account/moderator/index.ts @@ -0,0 +1,5 @@ +export { + fetchProfileLinkRequests, + fetchApproveLinkRequest, + fetchRejectLinkRequest, +} from "./profile-link-requests"; diff --git a/client/src/api/account/moderator/profile-link-requests.ts b/client/src/api/account/moderator/profile-link-requests.ts new file mode 100644 index 0000000..05cbcb2 --- /dev/null +++ b/client/src/api/account/moderator/profile-link-requests.ts @@ -0,0 +1,30 @@ +import { apiFetch } from "#lib/api"; +import { IProfileLinkRequest } from "#entities/account"; + +export async function fetchProfileLinkRequests() { + const path = `/account/moderator/tasks/creator_links`; + + const linkRequests = await apiFetch(path, { + method: "GET", + }); + + return linkRequests; +} + +export async function fetchApproveLinkRequest(requestID: string) { + const path = `/account/moderator/creator_link_requests/${requestID}/approve`; + const resp = await apiFetch<{ response: "approved" }>(path, { + method: "POST", + }); + + return resp; +} + +export async function fetchRejectLinkRequest(requestID: string) { + const path = `/account/moderator/creator_link_requests/${requestID}/reject`; + const resp = await apiFetch<{ response: "rejected" }>(path, { + method: "POST", + }); + + return resp; +} diff --git a/client/src/api/account/notifications.ts b/client/src/api/account/notifications.ts new file mode 100644 index 0000000..20a8b6b --- /dev/null +++ b/client/src/api/account/notifications.ts @@ -0,0 +1,17 @@ +import { apiFetch } from "#lib/api"; +import { INotification } from "#entities/account"; + +interface IResult { + props: { + currentPage: "account"; + notifications: INotification[]; + }; +} + +export async function fetchAccountNotifications() { + const path = "/account/notifications"; + + const result = await apiFetch(path, { method: "GET" }); + + return result.props; +} diff --git a/client/src/api/account/profiles.ts b/client/src/api/account/profiles.ts new file mode 100644 index 0000000..e1390e7 --- /dev/null +++ b/client/src/api/account/profiles.ts @@ -0,0 +1,42 @@ +import { apiFetch } from "#lib/api"; +import { IArtist } from "#entities/profiles"; + +interface IResult { + message: string + props: { + id: string + service: string + artist: IArtist + share_count: number + has_links: "✔️" | "0" + display_data: { + service: string + href: string + } + } +} + +interface IBody { + service: string; + artist_id: string; + reason?: string; +} + +export async function fetchAddProfileLink( + service: string, + profileID: string, + linkService: string, + linkProfileID: string, + reason?: string +) { + const path = `/${service}/user/${profileID}/links/new`; + const body: IBody = { + service: linkService, + artist_id: linkProfileID, + reason, + }; + + const result = await apiFetch(path, { method: "POST", body }); + + return result; +} diff --git a/client/src/api/authentication/index.ts b/client/src/api/authentication/index.ts new file mode 100644 index 0000000..f03bd79 --- /dev/null +++ b/client/src/api/authentication/index.ts @@ -0,0 +1,3 @@ +export { fetchRegisterAccount } from "./register"; +export { fetchLoginAccount } from "./login"; +export { fetchLogoutAccount } from "./logout"; diff --git a/client/src/api/authentication/login.ts b/client/src/api/authentication/login.ts new file mode 100644 index 0000000..d49a89e --- /dev/null +++ b/client/src/api/authentication/login.ts @@ -0,0 +1,28 @@ +import { IAccount } from "#entities/account"; +import { apiFetch, ensureAPIError } from "#lib/api"; +import { fetchAccount } from "../account/account"; + +export async function fetchLoginAccount(username: string, password: string) { + const path = `/authentication/login`; + const body = { + username, + password, + }; + + try { + const result = await apiFetch(path, { method: "POST", body }); + + return result; + } catch (error) { + ensureAPIError(error); + + // account is already logged in + if (error.response.status !== 409) { + throw error; + } + + const result = await fetchAccount(); + + return result.props.account; + } +} diff --git a/client/src/api/authentication/logout.ts b/client/src/api/authentication/logout.ts new file mode 100644 index 0000000..5939fc4 --- /dev/null +++ b/client/src/api/authentication/logout.ts @@ -0,0 +1,9 @@ +import { apiFetch } from "#lib/api"; + +export async function fetchLogoutAccount() { + const path = `/authentication/logout`; + + const result = await apiFetch(path, { method: "POST"}); + + return result; +} diff --git a/client/src/api/authentication/register.ts b/client/src/api/authentication/register.ts new file mode 100644 index 0000000..fc4d1f2 --- /dev/null +++ b/client/src/api/authentication/register.ts @@ -0,0 +1,20 @@ +import { apiFetch } from "#lib/api"; + +export async function fetchRegisterAccount( + userName: string, + password: string, + confirmPassword: string, + favorites?: string +) { + const path = `/authentication/register`; + const body = { + username: userName, + password, + confirm_password: confirmPassword, + favorites_json: favorites, + }; + + const result = await apiFetch(path, { method: "POST", body }); + + return result; +} diff --git a/client/src/api/dms/all.ts b/client/src/api/dms/all.ts new file mode 100644 index 0000000..205f0fe --- /dev/null +++ b/client/src/api/dms/all.ts @@ -0,0 +1,29 @@ +import { apiFetch } from "#lib/api"; +import { IApprovedDM } from "#entities/dms"; + +interface IResult { + props: { + currentPage: "artists"; + count: number; + limit: number; + dms: IApprovedDM[]; + }; + base: {}; +} + +export async function fetchDMs(offset?: number, query?: string) { + const path = "/dms"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (query) { + params.set("q", query); + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/dms/has-pending.ts b/client/src/api/dms/has-pending.ts new file mode 100644 index 0000000..3e7be2b --- /dev/null +++ b/client/src/api/dms/has-pending.ts @@ -0,0 +1,8 @@ +import { apiFetch } from "#lib/api"; + +export async function fetchHasPendingDMs() { + const path = `/has_pending_dms`; + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/dms/index.ts b/client/src/api/dms/index.ts new file mode 100644 index 0000000..3fd3257 --- /dev/null +++ b/client/src/api/dms/index.ts @@ -0,0 +1,3 @@ +export { fetchDMs } from "./all"; +export { fetchProfileDMs } from "./profile"; +export { fetchHasPendingDMs } from "./has-pending"; diff --git a/client/src/api/dms/profile.ts b/client/src/api/dms/profile.ts new file mode 100644 index 0000000..b32b5d7 --- /dev/null +++ b/client/src/api/dms/profile.ts @@ -0,0 +1,27 @@ +import { apiFetch } from "#lib/api"; +import { IArtist } from "#entities/profiles"; +import { IApprovedDM } from "#entities/dms"; + +interface IResult { + props: { + id: string; + service: string; + artist: IArtist; + display_data: { + service: string; + href: string; + }; + + share_count: number; + dm_count: number; + dms: IApprovedDM[]; + has_links: "✔️" | "0"; + }; +} + +export async function fetchProfileDMs(service: string, profileID: string) { + const path = `/${service}/user/${profileID}/dms`; + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/files/archive-file.ts b/client/src/api/files/archive-file.ts new file mode 100644 index 0000000..d0832a0 --- /dev/null +++ b/client/src/api/files/archive-file.ts @@ -0,0 +1,35 @@ +import { apiFetch } from "#lib/api"; + +export interface IArchiveFile { + password?: string; + file: { + hash: string; + ext: string; + }; + file_list: string[]; +} + +export async function apiFetchArchiveFile(fileHash: string) { + const path = `/file/${fileHash}`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} + +type SetPasswordBody = Array; +type SetPasswordResponse = "ok" | { + error: string; +}; + +export async function apiSetArchiveFilePassword( + archiveHash: string, + password: string +): Promise { + const path = `/file/${archiveHash}`; + const body: SetPasswordBody = [password]; + + const result = await apiFetch(path, { body, method: "PATCH" }); + + return result; +} diff --git a/client/src/api/files/index.ts b/client/src/api/files/index.ts new file mode 100644 index 0000000..2981eac --- /dev/null +++ b/client/src/api/files/index.ts @@ -0,0 +1,2 @@ +export { apiFetchArchiveFile, apiSetArchiveFilePassword, type IArchiveFile } from "./archive-file"; +export { fetchSearchFileByHash } from "./search-by-hash"; diff --git a/client/src/api/files/search-by-hash.ts b/client/src/api/files/search-by-hash.ts new file mode 100644 index 0000000..a9adf61 --- /dev/null +++ b/client/src/api/files/search-by-hash.ts @@ -0,0 +1,56 @@ +import { apiFetch } from "#lib/api"; + +interface IResult { + id: number; + hash: string; + mtime: string; + + ctime: string; + + mime: string; + ext: string; + added: string; + + size: number; + ihash: string; + + posts: IPostResult[]; + discord_posts: IDiscordPostResult[]; +} + +interface IPostResult { + file_id: number; + id: string; + user: string; + service: string; + title: string; + substring: string; + published: string; + + file: { + name: string; + path: string; + }; + attachments: { name: string; path: string }[]; +} + +interface IDiscordPostResult { + file_id: number; + id: string; + server: string; + channel: string; + substring: string; + published: string; + + embeds: unknown[]; + mentions: unknown[]; + attachments: { name: string; path: string }[]; +} + +export async function fetchSearchFileByHash(fileHash: string) { + const path = `/search_hash/${fileHash}`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/imports/create-import.ts b/client/src/api/imports/create-import.ts new file mode 100644 index 0000000..1e03057 --- /dev/null +++ b/client/src/api/imports/create-import.ts @@ -0,0 +1,26 @@ +import { apiFetch } from "#lib/api"; + +interface IBody { + session_key: string; + service: string; + auto_import?: string; + save_session_key?: string; + save_dms?: boolean; + channel_ids?: string; + "x-bc"?: string; + auth_id?: string; + user_agent?: string; +} + +interface IResult { + import_id: string; +} + +export async function fetchCreateImport(input: IBody) { + const path = `/importer/submit`; + const body: IBody = input; + + const result = await apiFetch(path, { method: "POST", body }); + + return result; +} diff --git a/client/src/api/imports/get-import.ts b/client/src/api/imports/get-import.ts new file mode 100644 index 0000000..f3430e3 --- /dev/null +++ b/client/src/api/imports/get-import.ts @@ -0,0 +1,9 @@ +import { apiFetch } from "#lib/api"; + +export async function fetchImportLogs(importId: string): Promise> { + const path = `/importer/logs/${importId}`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/imports/index.ts b/client/src/api/imports/index.ts new file mode 100644 index 0000000..958814d --- /dev/null +++ b/client/src/api/imports/index.ts @@ -0,0 +1,2 @@ +export { fetchImportLogs } from "./get-import"; +export { fetchCreateImport } from "./create-import"; diff --git a/client/src/api/kemono/_index.js b/client/src/api/kemono/_index.js deleted file mode 100644 index a41d235..0000000 --- a/client/src/api/kemono/_index.js +++ /dev/null @@ -1,14 +0,0 @@ -import { favorites } from "./favorites"; -import { posts } from "./posts"; -import { api } from "./api"; -import { dms } from "./dms"; - -/** - * @type {KemonoAPI} - */ -export const kemonoAPI = { - favorites, - posts, - api, - dms, -}; diff --git a/client/src/api/kemono/api.js b/client/src/api/kemono/api.js deleted file mode 100644 index 7031be8..0000000 --- a/client/src/api/kemono/api.js +++ /dev/null @@ -1,100 +0,0 @@ -import { KemonoError } from "@wp/utils"; -import { kemonoFetch } from "./kemono-fetch"; -import { CREATORS_LOCATION } from "@wp/env/env-vars"; - -export const api = { - bans, - bannedArtist, - creators, - logs, -}; - -async function bans() { - try { - const response = await kemonoFetch("/api/v1/creators/bans", { method: "GET" }); - - if (!response || !response.ok) { - alert(new KemonoError(6)); - return null; - } - - /** - * @type {KemonoAPI.API.BanItem[]} - */ - const banItems = await response.json(); - - return banItems; - } catch (error) { - console.error(error); - } -} - -/** - * @param {string} id - * @param {string} service - */ -async function bannedArtist(id, service) { - const params = new URLSearchParams([["service", service]]).toString(); - - try { - const response = await kemonoFetch(`/api/v1/lookup/cache/${id}?${params}`); - - if (!response || !response.ok) { - alert(new KemonoError(7)); - return null; - } - - /** - * @type {KemonoAPI.API.BannedArtist} - */ - const artist = await response.json(); - - return artist; - } catch (error) { - console.error(error); - } -} - -async function creators() { - try { - const response = await kemonoFetch(CREATORS_LOCATION || "/api/v1/creators", { - method: "GET", - }); - - if (!response || !response.ok) { - alert(new KemonoError(8)); - return null; - } - - /** - * @type {KemonoAPI.User[]} - */ - const artists = await response.json(); - - return artists; - } catch (error) { - console.error(error); - } -} - -async function logs(importID) { - try { - const response = await kemonoFetch(`/api/v1/importer/logs/${importID}`, { - method: "GET", - }); - - if (!response || !response.ok) { - alert(new KemonoError(9)); - return null; - } - - /** - * @type {KemonoAPI.API.LogItem[]} - */ - const logs = await response.json(); - - return logs; - } catch (error) { - console.error(error); - } -} diff --git a/client/src/api/kemono/dms.js b/client/src/api/kemono/dms.js deleted file mode 100644 index 6f4dd78..0000000 --- a/client/src/api/kemono/dms.js +++ /dev/null @@ -1,22 +0,0 @@ -import { KemonoError } from "@wp/utils"; -import { kemonoFetch } from "./kemono-fetch"; - -/** - * @type {KemonoAPI.DMs} - */ -export const dms = { - retrieveHasPendingDMs, -}; - -async function retrieveHasPendingDMs() { - try { - const response = await kemonoFetch(`/api/v1/has_pending_dms`); - - if (!response || !response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); - } - return await response.json(); - } catch (error) { - console.error(error); - } -} diff --git a/client/src/api/kemono/favorites.js b/client/src/api/kemono/favorites.js deleted file mode 100644 index 6b87f84..0000000 --- a/client/src/api/kemono/favorites.js +++ /dev/null @@ -1,142 +0,0 @@ -import { KemonoError } from "@wp/utils"; -import { kemonoFetch } from "./kemono-fetch"; - -/** - * @type {KemonoAPI.Favorites} - */ -export const favorites = { - retrieveFavoriteArtists, - favoriteArtist, - unfavoriteArtist, - retrieveFavoritePosts, - favoritePost, - unfavoritePost, -}; - -async function retrieveFavoriteArtists() { - const params = new URLSearchParams([["type", "artist"]]).toString(); - - try { - const response = await kemonoFetch(`/api/v1/account/favorites?${params}`); - - if (!response || !response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); - } - /** - * @type {string} - */ - const favs = await response.text(); - return favs; - } catch (error) { - console.error(error); - } -} - -/** - * @param {string} service - * @param {string} userID - */ -async function favoriteArtist(service, userID) { - try { - const response = await kemonoFetch(`/api/v1/favorites/creator/${service}/${userID}`, { method: "POST" }); - - if (!response || !response.ok) { - alert(new KemonoError(3)); - return false; - } - - return true; - } catch (error) { - console.error(error); - } -} - -/** - * @param {string} service - * @param {string} userID - */ -async function unfavoriteArtist(service, userID) { - try { - const response = await kemonoFetch(`/api/v1/favorites/creator/${service}/${userID}`, { method: "DELETE" }); - - if (!response || !response.ok) { - alert(new KemonoError(4)); - return false; - } - - return true; - } catch (error) { - console.error(error); - } -} - -async function retrieveFavoritePosts() { - const params = new URLSearchParams([["type", "post"]]).toString(); - - try { - const response = await kemonoFetch(`/api/v1/account/favorites?${params}`); - - if (!response || !response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); - } - - /** - * @type {KemonoAPI.Post[]} - */ - const favs = await response.json(); - /** - * @type {KemonoAPI.Favorites.Post[]} - */ - const transformedFavs = favs.map((post) => { - return { - id: post.id, - service: post.service, - user: post.user, - }; - }); - - return JSON.stringify(transformedFavs); - } catch (error) { - console.error(error); - } -} - -/** - * @param {string} service - * @param {string} user - * @param {string} post_id - */ -async function favoritePost(service, user, post_id) { - try { - const response = await kemonoFetch(`/api/v1/favorites/post/${service}/${user}/${post_id}`, { method: "POST" }); - - if (!response || !response.ok) { - alert(new KemonoError(1)); - return false; - } - - return true; - } catch (error) { - console.error(error); - } -} - -/** - * @param {string} service - * @param {string} user - * @param {string} post_id - */ -async function unfavoritePost(service, user, post_id) { - try { - const response = await kemonoFetch(`/api/v1/favorites/post/${service}/${user}/${post_id}`, { method: "DELETE" }); - - if (!response || !response.ok) { - alert(new KemonoError(2)); - return false; - } - - return true; - } catch (error) { - console.error(error); - } -} diff --git a/client/src/api/kemono/kemono-fetch.js b/client/src/api/kemono/kemono-fetch.js deleted file mode 100644 index 539609a..0000000 --- a/client/src/api/kemono/kemono-fetch.js +++ /dev/null @@ -1,46 +0,0 @@ -import { isLoggedIn } from "@wp/js/account"; - -/** - * Generic request for Kemono API. - * @param {RequestInfo} endpoint - * @param {RequestInit} options - * @returns {Promise} - */ -export async function kemonoFetch(endpoint, options) { - try { - const response = await fetch(endpoint, options); - - // doing this because the server returns `401` before redirecting - // in case of favs - if (response.status === 401) { - // server logged the account out - if (isLoggedIn) { - localStorage.removeItem("logged_in"); - localStorage.removeItem("role"); - localStorage.removeItem("favs"); - localStorage.removeItem("post_favs"); - location.href = "/account/logout"; - return; - } - const loginURL = new URL("/account/login", location.origin).toString(); - location = addURLParam(loginURL, "location", location.pathname); - return; - } - - return response; - } catch (error) { - console.error(`Kemono request error: ${error}`); - } -} - -/** - * @param {string} url - * @param {string} paramName - * @param {string} paramValue - * @returns {string} - */ -function addURLParam(url, paramName, paramValue) { - var newURL = new URL(url); - newURL.searchParams.set(paramName, paramValue); - return newURL.toString(); -} diff --git a/client/src/api/kemono/posts.js b/client/src/api/kemono/posts.js deleted file mode 100644 index b9f97bb..0000000 --- a/client/src/api/kemono/posts.js +++ /dev/null @@ -1,26 +0,0 @@ -import { kemonoFetch } from "./kemono-fetch"; -import { KemonoError } from "@wp/utils"; - -export const posts = { - attemptFlag, -}; - -/** - * @param {string} service - * @param {string} user - * @param {string} post_id - */ -async function attemptFlag(service, user, post_id) { - try { - const response = await kemonoFetch(`/api/v1/${service}/user/${user}/post/${post_id}/flag`, { method: "POST" }); - - if (!response || !response.ok) { - alert(new KemonoError(5)); - return false; - } - - return true; - } catch (error) { - console.error(error); - } -} diff --git a/client/src/api/paysites/_index.js b/client/src/api/paysites/_index.js deleted file mode 100644 index 0e1948b..0000000 --- a/client/src/api/paysites/_index.js +++ /dev/null @@ -1 +0,0 @@ -export const paysitesAPI = {}; diff --git a/client/src/api/posts/announcements.ts b/client/src/api/posts/announcements.ts new file mode 100644 index 0000000..10ace35 --- /dev/null +++ b/client/src/api/posts/announcements.ts @@ -0,0 +1,9 @@ +import { apiFetch } from "#lib/api"; +import { IAnnouncement } from "#entities/posts"; + +export async function fetchAnnouncements(service: string, profileID: string) { + const path = `/${service}/user/${profileID}/announcements`; + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/posts/flag.ts b/client/src/api/posts/flag.ts new file mode 100644 index 0000000..c7ea1fc --- /dev/null +++ b/client/src/api/posts/flag.ts @@ -0,0 +1,14 @@ +import { apiFetch } from "#lib/api"; + +interface FlagPostParams { + service: string; + creatorId: string; + postId: string; + reason: string; +} + +export async function flagPost({ service, creatorId, postId, reason }: FlagPostParams): Promise { + let path = `/${service}/user/${creatorId}/post/${postId}/flag`; + + return await apiFetch(path, { method: "POST", body: { reason } }); +} diff --git a/client/src/api/posts/index.ts b/client/src/api/posts/index.ts new file mode 100644 index 0000000..22767b0 --- /dev/null +++ b/client/src/api/posts/index.ts @@ -0,0 +1,7 @@ +export { fetchPosts } from "./posts"; +export { fetchPost, fetchPostComments, fetchPostData } from "./post"; +export { fetchPostRevision } from "./revision"; +export { fetchPopularPosts } from "./popular"; +export { fetchAnnouncements } from "./announcements"; +export { fetchRandomPost } from "./random"; +export { flagPost } from "./flag"; diff --git a/client/src/api/posts/popular.ts b/client/src/api/posts/popular.ts new file mode 100644 index 0000000..f3d4c0e --- /dev/null +++ b/client/src/api/posts/popular.ts @@ -0,0 +1,74 @@ +import { apiFetch } from "#lib/api"; +import { IPopularPostsPeriod, IPost } from "#entities/posts"; + +interface IResult { + info: { + /** + * Datetime string. + */ + date: string; + /** + * Datetime string. + */ + min_date: string; + /** + * Datetime string. + */ + max_date: string; + /** + * Value is a tuple of date strings. + */ + navigation_dates: Record; + /** + * Human description of range. + */ + range_desc: string; + scale: IPopularPostsPeriod; + }; + props: { + currentPage: "popular_posts"; + /** + * Date string. + */ + today: string; + /** + * Date string. + */ + earliest_date_for_popular: string; + limit: number; + count: number; + }; + results: IPost[]; + base: {}; + result_previews: ( + | { type: "thumbnail"; server: string; name: string; path: string } + | { type: "embed"; url: string; subject: string; description: string } + )[]; + result_attachments: { server: string; name: string; path: string }[]; + result_is_image: boolean; +} + +export async function fetchPopularPosts( + date?: string, + scale?: IPopularPostsPeriod, + offset?: number +) { + const path = `/posts/popular`; + const params = new URLSearchParams(); + + if (date && scale !== "recent") { + params.set("date", date); + } + + if (scale) { + params.set("period", scale); + } + + if (offset) { + params.set("o", String(offset)); + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/posts/post.ts b/client/src/api/posts/post.ts new file mode 100644 index 0000000..569800b --- /dev/null +++ b/client/src/api/posts/post.ts @@ -0,0 +1,71 @@ +import { apiFetch } from "#lib/api"; +import { + IComment, + IPost, + IPostAttachment, + IPostPreview, + IPostRevision, + IPostVideo, +} from "#entities/posts"; + +interface IResult { + post: IPost; + attachments: IPostAttachment[]; + previews: IPostPreview[]; + videos: IPostVideo[]; + props: { + service: string; + flagged: string | null; + revisions: [number, IPost][]; + }; +} + +export async function fetchPost( + service: string, + profileID: string, + postID: string +) { + const path = `/${service}/user/${profileID}/post/${postID}`; + const ifModifiedDate = new Date(); + + ifModifiedDate.setFullYear(ifModifiedDate.getFullYear() - 1); + + const headers = new Headers([ + ["If-Modified-Since", ifModifiedDate.toUTCString()], + ]); + const result = await apiFetch(path, { method: "GET", headers }); + + return result; +} + +export async function fetchPostComments( + service: string, + profileID: string, + postID: string +) { + const path = `/${service}/user/${profileID}/post/${postID}/comments`; + const ifModifiedDate = new Date(); + + ifModifiedDate.setFullYear(ifModifiedDate.getFullYear() - 1); + + const headers = new Headers([ + ["If-Modified-Since", ifModifiedDate.toUTCString()], + ]); + const result = await apiFetch(path, { method: "GET", headers }); + + return result; +} + +interface IPostData { + service: string; + artist_id: string; + post_id: string; +} + +export async function fetchPostData(service: string, postID: string) { + const path = `/${service}/post/${postID}`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/posts/posts.ts b/client/src/api/posts/posts.ts new file mode 100644 index 0000000..4ede419 --- /dev/null +++ b/client/src/api/posts/posts.ts @@ -0,0 +1,35 @@ +import { apiFetch } from "#lib/api"; +import { IPost } from "#entities/posts"; + +interface IResult { + count: number; + true_count: number; + posts: IPost[]; +} + +export async function fetchPosts( + offset?: number, + query?: string, + tags?: string[] +) { + const path = "/posts"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (query) { + params.set("q", query); + } + + if (tags) { + for (const tag of tags) { + params.set("tag", tag); + } + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/posts/random.ts b/client/src/api/posts/random.ts new file mode 100644 index 0000000..8414e18 --- /dev/null +++ b/client/src/api/posts/random.ts @@ -0,0 +1,15 @@ +import { apiFetch } from "#lib/api"; + +interface IResult { + service: string; + artist_id: string; + post_id: string; +} + +export async function fetchRandomPost() { + const path = `/posts/random`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/posts/revision.ts b/client/src/api/posts/revision.ts new file mode 100644 index 0000000..61a88eb --- /dev/null +++ b/client/src/api/posts/revision.ts @@ -0,0 +1,39 @@ +import { apiFetch } from "#lib/api"; +import { IArtistDetails } from "#entities/profiles"; +import { + IComment, + IPost, + IPostAttachment, + IPostPreview, + IPostRevision, + IPostVideo, +} from "#entities/posts"; + +interface IResult { + props: { + currentPage: "revisions"; + service: string; + artist: IArtistDetails; + flagged: string | null; + revisions: [number, IPostRevision][]; + }; + post: IPost; + comments: IComment[]; + result_previews: IPostPreview[]; + result_attachments: IPostAttachment[]; + videos: IPostVideo[]; + archives_enabled: boolean; +} + +export async function fetchPostRevision( + service: string, + profileID: string, + postID: string, + revisionID: string +) { + const path = `/${service}/user/${profileID}/post/${postID}/revision/${revisionID}`; + + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/profiles/discord/index.ts b/client/src/api/profiles/discord/index.ts new file mode 100644 index 0000000..9874422 --- /dev/null +++ b/client/src/api/profiles/discord/index.ts @@ -0,0 +1,29 @@ +import { apiFetch } from "#lib/api"; +import { IDiscordChannelMessage } from "#entities/posts"; + +export async function fetchDiscordServer(serverID: string) { + const path = `/discord/channel/lookup/${serverID}`; + + const result = await apiFetch<{ id: string; name: string }[]>(path, { + method: "GET", + }); + + return result; +} + +export async function fetchDiscordChannel(channelID: string, offset?: number) { + const path = `/discord/channel/${channelID}`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + const result = await apiFetch( + path, + { method: "GET" }, + params + ); + + return result; +} diff --git a/client/src/api/profiles/fancards.ts b/client/src/api/profiles/fancards.ts new file mode 100644 index 0000000..a2fea77 --- /dev/null +++ b/client/src/api/profiles/fancards.ts @@ -0,0 +1,9 @@ +import { apiFetch } from "#lib/api"; +import { IFanCard } from "#entities/files"; + +export async function fetchFanboxProfileFancards(profileID: string) { + const path = `/fanbox/user/${profileID}/fancards`; + const cards = await apiFetch(path, { method: "GET" }); + + return cards; +} diff --git a/client/src/api/profiles/index.ts b/client/src/api/profiles/index.ts new file mode 100644 index 0000000..1eedbc8 --- /dev/null +++ b/client/src/api/profiles/index.ts @@ -0,0 +1,6 @@ +export { fetchProfiles } from "./profiles"; +export { fetchRandomArtist } from "./random"; +export { fetchArtistProfile } from "./profile"; +export { fetchFanboxProfileFancards } from "./fancards"; +export { fetchProfileLinks } from "./links"; +export { fetchProfilePosts } from "./posts"; diff --git a/client/src/api/profiles/links.ts b/client/src/api/profiles/links.ts new file mode 100644 index 0000000..a60af6d --- /dev/null +++ b/client/src/api/profiles/links.ts @@ -0,0 +1,21 @@ +import { apiFetch } from "#lib/api"; + +interface IResult + extends Array<{ + id: string; + + public_id: string | null; + service: string; + name: string; + + indexed: string; + + updated: string; + }> {} + +export async function fetchProfileLinks(service: string, profileID: string) { + const path = `/${service}/user/${profileID}/links`; + const links = await apiFetch(path, { method: "GET" }); + + return links; +} diff --git a/client/src/api/profiles/posts.ts b/client/src/api/profiles/posts.ts new file mode 100644 index 0000000..84051ee --- /dev/null +++ b/client/src/api/profiles/posts.ts @@ -0,0 +1,58 @@ +import { apiFetch } from "#lib/api"; +import { IArtist } from "#entities/profiles"; +import { IPost } from "#entities/posts"; + +interface IResult { + props: { + currentPage: "posts"; + id: string; + service: string; + name: string; + count: number; + limit: number; + artist: IArtist; + display_data: { + service: string; + href: string; + }; + dm_count: number; + share_count: number; + has_links: "0" | "✔️"; + }; + + base: Record + results: IPost[] + result_previews: Record[] + result_atachments: Record[] + result_is_image: boolean[] + disable_service_icons: true +} + +export async function fetchProfilePosts( + service: string, + profileID: string, + offset?: number, + query?: string, + tags?: string[] +) { + const path = `/${service}/user/${profileID}/posts-legacy`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (query) { + params.set("q", query); + } + + if (tags && tags.length) { + for (const tag of tags) { + params.append("tag", tag); + } + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/profiles/profile.ts b/client/src/api/profiles/profile.ts new file mode 100644 index 0000000..4dbef88 --- /dev/null +++ b/client/src/api/profiles/profile.ts @@ -0,0 +1,22 @@ +import { apiFetch } from "#lib/api"; +import { IArtistDetails } from "#entities/profiles"; + +export async function fetchArtistProfile( + service: string, + artistID: string +): Promise { + const path = `/${service}/user/${artistID}/profile`; + const ifModifiedDate = new Date(); + + ifModifiedDate.setFullYear(ifModifiedDate.getFullYear() - 1); + + const headers = new Headers([ + ["If-Modified-Since", ifModifiedDate.toUTCString()], + ]); + const result = await apiFetch(path, { + method: "GET", + headers, + }); + + return result; +} diff --git a/client/src/api/profiles/profiles.ts b/client/src/api/profiles/profiles.ts new file mode 100644 index 0000000..ef37500 --- /dev/null +++ b/client/src/api/profiles/profiles.ts @@ -0,0 +1,12 @@ +import { apiFetch } from "#lib/api"; +import { IArtistWithFavs } from "#entities/profiles"; +import { IS_DEVELOPMENT } from "#env/derived-vars"; + +export async function fetchProfiles(): Promise { + const path = IS_DEVELOPMENT ? "/creators" : "/creators.txt"; + const result = await apiFetch(path, { + method: "GET", + }); + + return result; +} diff --git a/client/src/api/profiles/random.ts b/client/src/api/profiles/random.ts new file mode 100644 index 0000000..cc12a58 --- /dev/null +++ b/client/src/api/profiles/random.ts @@ -0,0 +1,14 @@ +import { apiFetch } from "#lib/api"; + +interface IArtistData { + service: string; + artist_id: string; +} + +export async function fetchRandomArtist(): Promise { + const result = await apiFetch("/artists/random", { + method: "GET", + }); + + return result; +} diff --git a/client/src/api/shares/index.ts b/client/src/api/shares/index.ts new file mode 100644 index 0000000..d2f68bf --- /dev/null +++ b/client/src/api/shares/index.ts @@ -0,0 +1,3 @@ +export { fetchShares } from "./shares"; +export { fetchShare } from "./share"; +export { fetchProfileShares } from "./profile"; diff --git a/client/src/api/shares/profile.ts b/client/src/api/shares/profile.ts new file mode 100644 index 0000000..12acbee --- /dev/null +++ b/client/src/api/shares/profile.ts @@ -0,0 +1,37 @@ +import { apiFetch } from "#lib/api"; +import { IArtist } from "#entities/profiles"; +import { IShare } from "#entities/files"; + +interface IResult { + results: IShare[]; + base: Record; + props: { + display_data: { + service: string; + href: string; + }; + service: string; + artist: IArtist; + id: string; + dm_count: number; + share_count: number; + has_links: "✔️" | "0"; + }; +} + +export async function fetchProfileShares( + service: string, + profileID: string, + offset?: number +) { + const path = `/${service}/user/${profileID}/shares`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/shares/share.ts b/client/src/api/shares/share.ts new file mode 100644 index 0000000..5a8f783 --- /dev/null +++ b/client/src/api/shares/share.ts @@ -0,0 +1,15 @@ +import { apiFetch } from "#lib/api"; +import { IShare, IShareFile } from "#entities/files"; + +interface IResult { + share: IShare; + share_files: IShareFile[]; + base: unknown; +} + +export async function fetchShare(shareID: string) { + const path = `/share/${shareID}`; + const result = await apiFetch(path, { method: "GET" }); + + return result; +} diff --git a/client/src/api/shares/shares.ts b/client/src/api/shares/shares.ts new file mode 100644 index 0000000..26ca2af --- /dev/null +++ b/client/src/api/shares/shares.ts @@ -0,0 +1,24 @@ +import { apiFetch } from "#lib/api"; +import { IShare } from "#entities/files"; + +interface IResult { + base: Record; + props: { + currentPage: "shares"; + count: number; + shares: IShare[]; + }; +} + +export async function fetchShares(offset?: number) { + const path = `/shares`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + const result = await apiFetch(path, { method: "GET" }, params); + + return result; +} diff --git a/client/src/api/tags/all.ts b/client/src/api/tags/all.ts new file mode 100644 index 0000000..09ac55b --- /dev/null +++ b/client/src/api/tags/all.ts @@ -0,0 +1,14 @@ +import { apiFetch } from "#lib/api"; +import { ITag } from "#entities/tags" + +interface IResult { + props: { currentPage: "tags" } + tags: ITag[] +} + +export async function fetchTags() { + const path = "/posts/tags" + const result = await apiFetch(path, { method: "GET" }) + + return result +} diff --git a/client/src/api/tags/index.ts b/client/src/api/tags/index.ts new file mode 100644 index 0000000..dbba2e3 --- /dev/null +++ b/client/src/api/tags/index.ts @@ -0,0 +1,2 @@ +export { fetchTags } from "./all"; +export { fetchProfileTags } from "./profile"; diff --git a/client/src/api/tags/profile.ts b/client/src/api/tags/profile.ts new file mode 100644 index 0000000..12bc242 --- /dev/null +++ b/client/src/api/tags/profile.ts @@ -0,0 +1,29 @@ +import { apiFetch } from "#lib/api"; +import { IArtist } from "#entities/profiles"; +import { ITag } from "#entities/tags"; + +interface IResult { + props: { + display_data: { + service: string; + href: string; + }; + artist: IArtist; + service: string; + id: string; + share_count: number; + dm_count: number; + has_links: "✔️" | "0"; + }; + + tags: ITag[]; + service: string; + artist: IArtist; +} + +export async function fetchProfileTags(service: string, profileID: string) { + const path = `/${service}/user/${profileID}/tags`; + const tags = await apiFetch(path, { method: "GET" }); + + return tags; +} diff --git a/client/src/browser/hooks/index.ts b/client/src/browser/hooks/index.ts new file mode 100644 index 0000000..fdb7ad5 --- /dev/null +++ b/client/src/browser/hooks/index.ts @@ -0,0 +1,3 @@ +export { ClientProvider, useClient } from "./use-client"; +export { useRoutePathPattern } from "./use-route-path-pattern"; +export { useInterval } from "./use-interval"; diff --git a/client/src/browser/hooks/use-client.tsx b/client/src/browser/hooks/use-client.tsx new file mode 100644 index 0000000..3552d76 --- /dev/null +++ b/client/src/browser/hooks/use-client.tsx @@ -0,0 +1,38 @@ +import { + ReactNode, + createContext, + useContext, + useEffect, + useState, +} from "react"; +import { isRegisteredAccount } from "#entities/account"; + +type IClientContext = undefined | { isRegistered: boolean }; + +const ClientContext = createContext(undefined); + +interface IProps { + children?: ReactNode; +} + +export function ClientProvider({ children }: IProps) { + const [client, changeClient] = useState(); + + useEffect(() => { + (async () => { + const isRegistered = isRegisteredAccount(); + const clientData: IClientContext = { isRegistered }; + changeClient(clientData); + })(); + }, []); + + return ( + {children} + ); +} + +export function useClient(): IClientContext { + const context = useContext(ClientContext); + + return context; +} diff --git a/client/src/browser/hooks/use-interval.tsx b/client/src/browser/hooks/use-interval.tsx new file mode 100644 index 0000000..bc4b1f3 --- /dev/null +++ b/client/src/browser/hooks/use-interval.tsx @@ -0,0 +1,27 @@ +import { useEffect, useRef } from "react"; + +/** + * Stolen from + * https://overreacted.io/making-setinterval-declarative-with-react-hooks/ + */ +export function useInterval(callback: () => void, delay: number | null) { + const savedCallback = useRef(); + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + // Set up the interval. + useEffect(() => { + if (delay !== null) { + let id = setInterval(tick, delay); + + return () => clearInterval(id); + } + + function tick() { + savedCallback.current!(); + } + }, [delay]); +} diff --git a/client/src/browser/hooks/use-route-path-pattern.tsx b/client/src/browser/hooks/use-route-path-pattern.tsx new file mode 100644 index 0000000..bf7a70b --- /dev/null +++ b/client/src/browser/hooks/use-route-path-pattern.tsx @@ -0,0 +1,11 @@ +import { useLocation } from "react-router"; + +/** + * TODO: path pattern without circular reference + * on the route config object. + */ +export function useRoutePathPattern(): string { + const location = useLocation(); + + return location.pathname; +} diff --git a/client/src/browser/storage/local/index.ts b/client/src/browser/storage/local/index.ts new file mode 100644 index 0000000..c1d1ef0 --- /dev/null +++ b/client/src/browser/storage/local/index.ts @@ -0,0 +1,81 @@ +const storageNames = [ + "favorites", + "logged_in", + "role", + "favs", + "post_favs", + "has_pending_review_dms", + "last_checked_has_pending_review_dms", + "sidebar_state", +] as const; + +export interface ILocalStorageSchema { + favs: { + service: string; + id: string; + }[]; + post_favs: { + id: string; + service: string; + user: string; + }[]; +} + +type ILocalStorageName = (typeof storageNames)[number]; + +let localStorageAvailable: boolean | null = null; + +function checkLocalStorageAvailability(): boolean { + if (localStorageAvailable === null) { + try { + localStorage.setItem("__storage_test__", "__storage_test__"); + localStorage.removeItem("__storage_test__"); + localStorageAvailable = true; + } catch (error) { + localStorageAvailable = false; + } + } + return localStorageAvailable; +} + +export function getLocalStorageItem(name: ILocalStorageName): string | null { + if (!checkLocalStorageAvailability()) { + console.warn("LocalStorage is not available."); + return null; + } + return localStorage.getItem(name); +} + +export function setLocalStorageItem(name: ILocalStorageName, value: string): void { + if (!checkLocalStorageAvailability()) { + console.warn("LocalStorage is not available."); + return; + } + try { + localStorage.setItem(name, value); + } catch (error) { + console.error("Failed to set item in LocalStorage:", error); + } +} + +export function deleteLocalStorageItem(name: ILocalStorageName): void { + if (!checkLocalStorageAvailability()) { + console.warn("LocalStorage is not available."); + return; + } + try { + localStorage.removeItem(name); + } catch (error) { + console.error("Failed to remove item from LocalStorage:", error); + } +} + +export function isLocalStorageAvailable() { + try { + localStorage.setItem("__storage_test__", "__storage_test__"); + localStorage.removeItem("__storage_test__"); + return true; + } catch (error) { + return false; + } +} diff --git a/client/src/components/_index.scss b/client/src/components/_index.scss new file mode 100644 index 0000000..686afef --- /dev/null +++ b/client/src/components/_index.scss @@ -0,0 +1,11 @@ +@use "layout"; +@use "pages"; +@use "images"; +@use "links"; +@use "dates"; +@use "cards"; +@use "loading"; +@use "buttons"; +@use "tooltip"; +@use "pagination"; +@use "importer_states"; diff --git a/client/src/components/advs/ads.tsx b/client/src/components/advs/ads.tsx new file mode 100644 index 0000000..a187e21 --- /dev/null +++ b/client/src/components/advs/ads.tsx @@ -0,0 +1,82 @@ +import { useLocation } from "react-router"; +import { HEADER_AD, MIDDLE_AD, FOOTER_AD, SLIDER_AD } from "#env/env-vars"; +import { DangerousContent } from "#components/dangerous-content"; +import { useEffect } from "react"; + +export function HeaderAd() { + const location = useLocation(); + const key = `${location.pathname}${location.search}`; + + return !HEADER_AD ? undefined : ( + + ); +} + +export function MiddleAd() { + const location = useLocation(); + const key = `${location.pathname}${location.search}`; + + return !MIDDLE_AD ? undefined : ( + + ); +} + +export function FooterAd() { + const location = useLocation(); + const key = `${location.pathname}${location.search}`; + + return !FOOTER_AD ? undefined : ( + + ); +} + +export function SliderAd() { + const location = useLocation(); + const key = `${location.pathname}${location.search}`; + + const observer = new MutationObserver((mutationsList) => { + for (const mutation of mutationsList) { + if (mutation.type === "childList") { + const slideAnimationElements = document.querySelectorAll('[class*="slideAnimation"]'); + const elementsToRemove = Array.from(slideAnimationElements).slice(1); + elementsToRemove.forEach((element) => { + element.remove(); + }); + } + } + }); + + observer.observe(document.body, { childList: true, subtree: true }); + + useEffect(() => { + return () => { + observer.disconnect(); + document.querySelectorAll('[class*="slideAnimation"]').forEach((element) => { + element.remove(); + }); + }; + }, []); + + return !SLIDER_AD ? undefined : ( + + ); +} diff --git a/client/src/components/advs/index.ts b/client/src/components/advs/index.ts new file mode 100644 index 0000000..7bb70ee --- /dev/null +++ b/client/src/components/advs/index.ts @@ -0,0 +1 @@ +export { MiddleAd, HeaderAd, FooterAd, SliderAd } from "./ads"; diff --git a/client/src/components/buttons/_index.scss b/client/src/components/buttons/_index.scss new file mode 100644 index 0000000..7051716 --- /dev/null +++ b/client/src/components/buttons/_index.scss @@ -0,0 +1 @@ +@use "./buttons"; diff --git a/client/src/pages/components/buttons.scss b/client/src/components/buttons/buttons.scss similarity index 100% rename from client/src/pages/components/buttons.scss rename to client/src/components/buttons/buttons.scss diff --git a/client/src/components/buttons/buttons.tsx b/client/src/components/buttons/buttons.tsx new file mode 100644 index 0000000..9d5b083 --- /dev/null +++ b/client/src/components/buttons/buttons.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from "react"; +import { IBlockProps, createBlockComponent } from "#components/meta"; + +/** + * TODO: `onClick` required + */ +interface IProps extends IBlockProps<"button"> { + className?: string; + isFocusable?: boolean; + children?: ReactNode; +} + +export const Button = createBlockComponent("button", Component); + +export function Component({ isFocusable = true, children, ...props }: IProps) { + return ( + + ); +} diff --git a/client/src/components/buttons/index.ts b/client/src/components/buttons/index.ts new file mode 100644 index 0000000..ee532b9 --- /dev/null +++ b/client/src/components/buttons/index.ts @@ -0,0 +1 @@ +export { Button } from "./buttons"; diff --git a/client/src/pages/components/cards/_index.scss b/client/src/components/cards/_index.scss similarity index 62% rename from client/src/pages/components/cards/_index.scss rename to client/src/components/cards/_index.scss index 545d0b3..37b0a13 100644 --- a/client/src/pages/components/cards/_index.scss +++ b/client/src/components/cards/_index.scss @@ -1,6 +1,6 @@ +@use "card_list"; @use "base"; -@use "account"; @use "post"; -@use "user"; +@use "profile"; @use "dm"; @use "no_results"; diff --git a/client/src/pages/components/cards/base.scss b/client/src/components/cards/base.scss similarity index 94% rename from client/src/pages/components/cards/base.scss rename to client/src/components/cards/base.scss index 08990e1..ecdfffa 100644 --- a/client/src/pages/components/cards/base.scss +++ b/client/src/components/cards/base.scss @@ -1,4 +1,4 @@ -@use "../../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .card { display: grid; diff --git a/client/src/components/cards/base.tsx b/client/src/components/cards/base.tsx new file mode 100644 index 0000000..c5c6774 --- /dev/null +++ b/client/src/components/cards/base.tsx @@ -0,0 +1,41 @@ +import clsx from "clsx"; +import { ReactNode } from "react"; + +interface ICardProps { + className?: string; + children?: ReactNode; +} + +interface ICardHeaderProps { + className?: string; + children?: ReactNode; +} + +interface ICardBodyProps { + className?: string; + children?: ReactNode; +} + +interface ICardFooterProps { + className?: string; + children?: ReactNode; +} + +export function Card({ className, children }: ICardProps) { + return
{children}
; +} +export function CardHeader({ className, children }: ICardHeaderProps) { + return ( +
{children}
+ ); +} +export function CardBody({ className, children }: ICardBodyProps) { + return ( +
{children}
+ ); +} +export function CardFooter({ className, children }: ICardFooterProps) { + return ( +
{children}
+ ); +} diff --git a/client/src/pages/components/card_list.scss b/client/src/components/cards/card_list.scss similarity index 86% rename from client/src/pages/components/card_list.scss rename to client/src/components/cards/card_list.scss index 4b13e69..6727058 100644 --- a/client/src/pages/components/card_list.scss +++ b/client/src/components/cards/card_list.scss @@ -1,4 +1,4 @@ -@use "../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .card-list { --local-flex-flow: row wrap; @@ -43,6 +43,13 @@ } } + &--loading { + * { + opacity: 0.8; + pointer-events: none; + } + } + &__item { &--no-results { --card-size: $width-phone; diff --git a/client/src/components/cards/card_list.tsx b/client/src/components/cards/card_list.tsx new file mode 100644 index 0000000..b3c465c --- /dev/null +++ b/client/src/components/cards/card_list.tsx @@ -0,0 +1,122 @@ +import clsx from "clsx"; +import { ReactNode, useEffect, useRef } from "react"; + +interface IProps { + layout?: "legacy" | "phone"; + className?: string; + children: ReactNode; +} + +const defaultThumbSize = 180; + +export function CardList({ layout = "legacy", className, children }: IProps) { + const cardListRef = useRef(null); + + useEffect(() => { + if (layout === "phone") { + return; + } + + const ref = cardListRef.current; + + if (!ref) { + return; + } + + try { + const cookies = getCookies(); + const thumbSizeValue = parseInt(cookies?.thumbSize); + let thumbSizeSetting = isNaN(thumbSizeValue) ? undefined : thumbSizeValue; + + if (!thumbSizeSetting) { + thumbSizeSetting = defaultThumbSize; + addCookie("thumbSize", String(defaultThumbSize), 399); + } + + const thumbSize = + parseInt(String(thumbSizeSetting)) === + parseInt(String(defaultThumbSize)) + ? undefined + : thumbSizeSetting; + + function handleResize() { + updateThumbsizes(ref!, defaultThumbSize, thumbSize); + } + + updateThumbsizes(ref!, defaultThumbSize, thumbSize); + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + } catch (error) { + return console.error(error); + } + }, []); + + return ( +
+
+
+ {children} +
+
+ ); +} + +function getCookies(): Record { + const cookies = document.cookie.split(";").reduce( + (cookies, cookie) => ( + // @ts-expect-error whatever + (cookies[cookie.split("=")[0].trim()] = decodeURIComponent( + cookie.split("=")[1] + )), + cookies + ), + {} + ); + + return cookies; +} + +function setCookie(name: "thumbSize", value: string, daysToExpire: number) { + const date = new Date(); + date.setTime(date.getTime() + daysToExpire * 24 * 60 * 60 * 1000); + const expires = "expires=" + date.toUTCString(); + document.cookie = name + "=" + value + "; " + expires + ";path=/"; +} + +function addCookie(name: "thumbSize", newValue: string, daysToExpire: number) { + const existingCookie = document.cookie + .split(";") + .find((cookie) => cookie.trim().startsWith(name + "=")); + + if (!existingCookie) { + setCookie(name, newValue, daysToExpire); + } +} + +/** + * TODO: move into card component + */ +function updateThumbsizes( + element: HTMLDivElement, + defaultSize: number, + thumbSizeSetting?: number +) { + let thumbSize = thumbSizeSetting ? thumbSizeSetting : defaultSize; + + if (!thumbSizeSetting) { + let viewportWidth = window.innerWidth; + let offset = 24; + let viewportWidthExcludingMargin = viewportWidth - offset; + let howManyFit = viewportWidthExcludingMargin / thumbSize; + + if (howManyFit < 2.0 && 1.5 < howManyFit) { + thumbSize = viewportWidthExcludingMargin / 2; + } else if (howManyFit > 12) { + thumbSize = defaultSize * 1.5; + } + } + element.style.setProperty("--card-size", `${thumbSize}px`); +} diff --git a/client/src/components/cards/dm.module.scss b/client/src/components/cards/dm.module.scss new file mode 100644 index 0000000..3855c0e --- /dev/null +++ b/client/src/components/cards/dm.module.scss @@ -0,0 +1,3 @@ +.content { + font-family: inherit; +} diff --git a/client/src/pages/components/cards/dm.scss b/client/src/components/cards/dm.scss similarity index 94% rename from client/src/pages/components/cards/dm.scss rename to client/src/components/cards/dm.scss index 2f57bb7..d073c8c 100644 --- a/client/src/pages/components/cards/dm.scss +++ b/client/src/components/cards/dm.scss @@ -1,4 +1,4 @@ -@use "../../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .dm-card { position: relative; diff --git a/client/src/components/cards/dm.tsx b/client/src/components/cards/dm.tsx new file mode 100644 index 0000000..b0f625a --- /dev/null +++ b/client/src/components/cards/dm.tsx @@ -0,0 +1,86 @@ +import clsx from "clsx"; +import { createProfilePageURL } from "#lib/urls"; +import { IArtist } from "#entities/profiles"; +import { IApprovedDM } from "#entities/dms"; +import { paysites } from "#entities/paysites"; +import { FancyLink } from "../links"; + +import * as styles from "./dm.module.scss" + +interface IProps { + dm: IApprovedDM; + isPrivate?: boolean; + isGlobal?: boolean; + artist?: IArtist; + className?: string; +} + +export function DMCard({ + dm, + isGlobal = false, + isPrivate = false, + artist, + className, +}: IProps) { + const { service, user } = dm; + const paysite = paysites[service]; + const profilePageURL = String( + createProfilePageURL({ service, profileID: user }) + ); + const remoteProfilePageURL = paysite.user.profile(artist?.id ?? user); + + return ( +
+ {!isGlobal ? undefined : ( +
+ + {artist?.name ?? user} + + + ({paysite.title}) + +
+ )} + + {!isPrivate ? undefined : ( +
+ + {artist?.name ?? user} + + + ({paysite.title}) + +
+ )} + +
+
+
{dm.content}
+
+
+ +
+ {dm.published ? ( +
+ Published: {dm.published.slice(0, 7)} +
+ ) : /* this is to detect if its not DM */ dm.user_id ? ( +
Added: {dm.added.slice(0, 7)}
+ ) : ( +
Added: {dm.added}
+ )} +
+
+ ); +} diff --git a/client/src/components/cards/index.ts b/client/src/components/cards/index.ts new file mode 100644 index 0000000..c38a563 --- /dev/null +++ b/client/src/components/cards/index.ts @@ -0,0 +1,7 @@ +export { CardList } from "./card_list"; +export { NoResults } from "./no_results"; +export { Card, CardHeader, CardBody, CardFooter } from "./base"; +export { PostCard } from "./post"; +export { ArtistCard } from "./profile"; +export { DMCard } from "./dm"; +export { ShareCard } from "./share"; diff --git a/client/src/pages/components/cards/no_results.scss b/client/src/components/cards/no_results.scss similarity index 66% rename from client/src/pages/components/cards/no_results.scss rename to client/src/components/cards/no_results.scss index 9b12b98..7d53f03 100644 --- a/client/src/pages/components/cards/no_results.scss +++ b/client/src/components/cards/no_results.scss @@ -1,4 +1,4 @@ -@use "../../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .card--no-results { flex: 0 1 $width-phone; diff --git a/client/src/components/cards/no_results.tsx b/client/src/components/cards/no_results.tsx new file mode 100644 index 0000000..4c9e632 --- /dev/null +++ b/client/src/components/cards/no_results.tsx @@ -0,0 +1,21 @@ +import { Card, CardBody, CardHeader } from "./base"; + +interface IProps { + title?: string; + message?: string; +} + +export function NoResults({ + title = "Nobody here but us chickens!", + message = "There are no items found.", +}: IProps) { + return ( + + +

{title}

+
+ + {message} +
+ ); +} diff --git a/client/src/pages/components/cards/post.scss b/client/src/components/cards/post.scss similarity index 74% rename from client/src/pages/components/cards/post.scss rename to client/src/components/cards/post.scss index 79c0459..4006ad2 100644 --- a/client/src/pages/components/cards/post.scss +++ b/client/src/components/cards/post.scss @@ -1,12 +1,9 @@ -@use "../../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .post-card { width: var(--card-size); height: var(--card-size); - text-shadow: - -1px -1px 0 #000, - 1px -1px 0 #000, - -1px 1px 0 #000, + text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; color: white; font-size: 80%; @@ -17,6 +14,12 @@ border-width: 2px; } + &--fav-profile { + border-bottom-color: var(--favourite-colour2-secondary); + border-bottom-style: solid; + border-bottom-width: 2px; + } + &:hover { & > a { top: -5px; @@ -52,9 +55,7 @@ padding: 0; position: relative; top: 0; - transition: - top ease 0.1s, - background ease 0.1s, + transition: top ease 0.1s, background ease 0.1s, border-bottom-color ease 0.1s; &:not(:hover):not(:active):not(:focus) { @@ -100,4 +101,15 @@ align-items: center; } } + + &__profile { + color: white; + border-color: transparent; + border-style: solid; + border-width: 2px; + + &--fav { + border-color: var(--favourite-colour2-primary); + } + } } diff --git a/client/src/components/cards/post.tsx b/client/src/components/cards/post.tsx new file mode 100644 index 0000000..e2fce08 --- /dev/null +++ b/client/src/components/cards/post.tsx @@ -0,0 +1,133 @@ +import clsx from "clsx"; +import { THUMBNAILS_PREPEND } from "#env/env-vars"; +import { createPostURL } from "#lib/urls"; +import { Timestamp } from "#components/dates"; +import { KemonoLink } from "#components/links"; +import { IPost } from "#entities/posts"; + +interface IProps { + post: IPost; + isFavourite?: boolean; + isServiceIconsDisabled?: boolean; + isFavouriteProfile?: boolean; + showFavCount?: boolean; +} + +const fileExtensions = [".gif", ".jpeg", ".jpg", ".jpe", ".png", ".webp"]; +const someServices = ["fansly", "candfans", "boosty", "gumroad"]; + +export function PostCard({ + post, + isServiceIconsDisabled, + isFavourite = false, + isFavouriteProfile, + showFavCount, +}: IProps) { + const { + service, + user: profileID, + id, + title, + content, + published, + attachments, + fav_count, + } = post; + const postLink = String(createPostURL(service, profileID, id)); + const srcNS = findNamespace(post); + const blockClassName = clsx( + "post-card", + "post-card--preview", + ); + const blockClassNameHeader = clsx( + "post-card__header", + (isFavourite || isFavouriteProfile) && "post-card__header--fav" + ); + const blockClassNameFooter = clsx( + "post-card__footer", + (isFavourite || isFavouriteProfile) && "post-card__footer--fav" + ); + const trimmedTitle = title?.trim(); + const parsedTitle = !trimmedTitle + ? undefined + : trimmedTitle.length > 50 + ? `${trimmedTitle.slice(0, 50)}...` + : trimmedTitle; + + return ( +
+ +
+ {parsedTitle && parsedTitle !== "DM" + ? parsedTitle + : !content || content?.length < 50 + ? content + : `${content.slice(0, 50)}...`} +
+ + {!srcNS.src ? undefined : ( +
+ +
+ )} + +
+
+
+ {!published ? undefined : } +
+ {!attachments?.length ? ( + <>No attachments + ) : ( + <> + {attachments.length}{" "} + {attachments.length === 1 ? "attachment" : "attachments"} + + )} + + {showFavCount && ( + <> +
+ {Math.floor(fav_count ?? 0)}{" "} + {fav_count === 1 ? "favorite" : "favorites"} + + )} +
+
+ {isServiceIconsDisabled ? undefined : ( + + )} +
+
+
+
+ ); +} + +function findNamespace(post: IPost) { + const srcNS: { found: boolean; src?: string } = { found: false }; + const path = post.file?.path?.toLowerCase(); + const isValidpath = path && fileExtensions.find((ext) => path.endsWith(ext)); + + if (isValidpath) { + srcNS.src = path; + } + + if (!isValidpath && someServices.includes(post.service)) { + const matchedFile = post.attachments.find((file) => + fileExtensions.find((ext) => file.path?.endsWith(ext)) + ); + + srcNS.src = matchedFile?.path; + } + + return srcNS; +} diff --git a/client/src/pages/components/cards/user.scss b/client/src/components/cards/profile.scss similarity index 96% rename from client/src/pages/components/cards/user.scss rename to client/src/components/cards/profile.scss index 10602a1..8539387 100644 --- a/client/src/pages/components/cards/user.scss +++ b/client/src/components/cards/profile.scss @@ -1,4 +1,4 @@ -@use "../../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .user-card { position: relative; diff --git a/client/src/components/cards/profile.tsx b/client/src/components/cards/profile.tsx new file mode 100644 index 0000000..27e23c9 --- /dev/null +++ b/client/src/components/cards/profile.tsx @@ -0,0 +1,132 @@ +import clsx from "clsx"; +import { IArtist, IArtistWithFavs } from "#entities/profiles"; +import { paysites } from "#entities/paysites"; +import { + createBannerURL, + createIconURL, + createProfilePageURL, +} from "#lib/urls"; +import { useClient } from "#hooks"; +import { Image } from "#components/images"; +import { Timestamp } from "#components/dates"; +import { KemonoLink } from "#components/links"; + +interface IProps { + artist: IArtist | IArtistWithFavs; + isUpdated?: boolean; + isIndexed?: boolean; + isCount?: boolean; + isFavorite?: boolean; + singleOf?: string; + pluralOf?: string; + isDate?: boolean; + className?: string; +} + +interface IHeaderProps { + isCount?: boolean; + isDate?: boolean; +} + +export function ArtistCard({ + artist, + isUpdated = false, + isIndexed = false, + isCount = false, + isFavorite = false, + singleOf, + pluralOf, + isDate = false, + className, +}: IProps) { + const isClient = useClient(); + const profileLink = String( + createProfilePageURL({ + service: artist.service, + profileID: artist.id, + }) + ); + const profileIcon = createIconURL(artist.service, artist.id); + const profileBanner = createBannerURL(artist.service, artist.id); + const updatedDateTime = new Date(Number(artist.updated) * 1000).toISOString(); + const indexedDateTime = new Date(Number(artist.indexed) * 1000).toISOString(); + + return ( + + {/* Icon. */} +
+
+ +
+
+ + {/* Secondary identifiers and elements. */} +
+ + {paysites[artist.service].title} + + +
{artist.name}
+ + {isUpdated && ( +
+ +
+ )} + + {isIndexed && ( +
+ +
+ )} + + {!isCount ? undefined : ( +
+ {"favorited" in artist ? ( + <> + {artist.favorited}{" "} + {artist.favorited > 1 ? pluralOf : singleOf} + + ) : ( + <>No {pluralOf ? pluralOf : "None"} + )} +
+ )} +
+
+ ); +} + +export function ArtistCardHeader({ + isCount = false, + isDate = false, +}: IHeaderProps) { + return ( +
+
Icon
+
Name
+
Service
+ + {!isCount ? undefined : ( +
Times favorited
+ )} + + {!isDate ? undefined :
Updated
} +
+ ); +} diff --git a/client/src/components/cards/share.tsx b/client/src/components/cards/share.tsx new file mode 100644 index 0000000..e83d1c0 --- /dev/null +++ b/client/src/components/cards/share.tsx @@ -0,0 +1,28 @@ +import { IShare } from "#entities/files"; +import { createSharePageURL } from "#lib/urls"; + +interface IProps { + share: IShare; +} + +export function ShareCard({ share }: IProps) { + const { id, name, description, added } = share; + + return ( + + ); +} diff --git a/client/src/components/dangerous-content/dangerous.tsx b/client/src/components/dangerous-content/dangerous.tsx new file mode 100644 index 0000000..1a022a9 --- /dev/null +++ b/client/src/components/dangerous-content/dangerous.tsx @@ -0,0 +1,44 @@ +import { + createElement, + useEffect, + useRef, + ComponentPropsWithoutRef, +} from "react"; + +interface IProps + extends Omit, "dangerouslySetInnerHTML"> { + html: string; + allowRerender?: boolean; +} + +/** + * [`dangerouslySetInnerHTML`](https://react.dev/reference/react-dom/components/common#dangerously-setting-the-inner-html) + * but also runs ` +
    + {videos.map((video) => ( + + ))} +
+ + )} + + {attachments && attachments.length !== 0 && ( + <> +

Downloads

+
    + {attachments.map((attachment, index) => ( + + ))} +
+ + )} + + {incomplete_rewards && ( +
+
{incomplete_rewards}
+
+ )} + + {poll && } + + {content && ( + <> +

Content

+ {/* TODO: rewrite without this */} +
+

+          
+ + )} + + {previews && } + + ); +} + +interface IPostAttachmentProps + extends Pick { + attachment: IPostAttachment; +} + +const archiveFileExtension = [".zip", ".rar", ".7z"]; + +function PostAttachment({ + attachment, + archives_enabled, +}: IPostAttachmentProps) { + const { name, path, server, extension, name_extension, stem } = attachment; + const isArchiveFile = Boolean( + archives_enabled && + (archiveFileExtension.includes(extension) || + archiveFileExtension.includes(name_extension)) + ); + + return ( +
  • + + Download {name} + + {isArchiveFile && ( + <> + {" "}( + browse ») + + )} +
  • + ); +} + +interface IPostPollProps { + poll: IPostPoll; +} + +function PostPoll({ poll }: IPostPollProps) { + const { + title, + description, + choices, + total_votes, + created_at, + closes_at, + allow_multiple, + } = poll; + + return ( + <> +

    Poll

    + +
    +
    +

    {title}

    + {!description ? undefined :

    {description}

    } +
    + +
      + {choices.map((choice) => { + const percentage = (choice.votes / (total_votes ?? 1)) * 100; + + return ( +
    • + {choice.text} + {choice.votes} + +
    • + ); + })} +
    + +
    +
      +
    • + {created_at} +
    • + {closes_at && ( +
    • + —{closes_at} +
    • + )} + + {allow_multiple && ( + <> + +
    • multiple choice
    • + + )} + + +
    • {total_votes} votes
    • +
    +
    +
    + {String(poll)} + + ); +} + +interface IPostPreviewsProps + extends Pick, "previews"> {} + +function PostPreviews({ previews }: IPostPreviewsProps) { + return ( + <> +

    Files

    +
    + {previews.map((preview, index) => + preview.type === "thumbnail" ? ( + + ) : ( + + ) + )} +
    + + ); +} + +interface IPreviewThumbnailProps { + preview: IPreviewThumbnail; +} + +function PreviewThumbnail({ preview }: IPreviewThumbnailProps) { + const [isExpanded, switchExpansion] = useState(false); + const { server, path, name, caption } = preview; + const url = String(createPreviewURL(path, name, server)); + const downloadName = encodeURIComponent(name); + const thumbnailRef = useRef(null); + + return ( + + ); +} + +interface IPreviewEmbedProps { + preview: IPreviewEmbed; +} + +function PreviewEmbed({ preview }: IPreviewEmbedProps) { + const { url, description, subject } = preview; + + return ( + +
    +

    + {!subject ? "(No title)" : subject} +

    + {description &&

    {description}

    } +
    +
    + ); +} + +interface IActionsProps { + service: string; + profileID: string; + postID: string; + flagged?: 0; +} diff --git a/client/src/entities/posts/overview/clean-body.ts b/client/src/entities/posts/overview/clean-body.ts new file mode 100644 index 0000000..a4f5775 --- /dev/null +++ b/client/src/entities/posts/overview/clean-body.ts @@ -0,0 +1,111 @@ +import { paysites } from "#entities/paysites"; + +/** + * Apply some fixes to the content of the post. + */ +export function cleanupBody(postBody: HTMLElement, service: string) { + const postContent = postBody.querySelector( + ".post__content > pre" + ); + const isNoPostContent = + !postContent || + (!postContent.childElementCount && !postContent.childNodes.length); + + // content is empty + if (isNoPostContent) { + return; + } + + // pixiv post + if (service === "fanbox") { + // its contents is a text node + if (!postContent.childElementCount && postContent.childNodes.length === 1) { + // wrap the text node into `
    `
    +      const [textNode] = Array.from(postContent.childNodes);
    +      const pre = document.createElement("pre");
    +      textNode.after(pre);
    +      pre.appendChild(textNode);
    +    }
    +
    +    // remove paragraphs with only `
    ` in them + const paragraphs = postContent.querySelectorAll("p"); + paragraphs.forEach((para) => { + if ( + para.childElementCount === 1 && + para.firstElementChild?.tagName === "BR" + ) { + para.remove(); + } + }); + } + + if (service === "onlyfans") { + // replace links to profiles with internal links + const links = postContent.querySelectorAll("a"); + + Array.from(links).forEach((anchour) => { + const literalHref = anchour.getAttribute("href")!; + + // it's not an internal href + if (!literalHref.startsWith("/")) { + return; + } + + const url = new URL(literalHref, anchour.href); + const pathname = url.pathname; + const pathnameSegments = url.pathname + .split("/") + .slice(1, pathname.endsWith("/") ? -1 : undefined); + + // url is not a profile + if (pathnameSegments.length !== 0) { + return; + } + + const profileID = pathnameSegments[0]; + const fixedURL = paysites["onlyfans"].user.profile(profileID); + anchour.href = fixedURL; + }); + } + + Array.from(document.links).forEach((anchour) => { + // remove links to fanbox from the post + const hostname = anchour.hostname; + if (hostname.includes("downloads.fanbox.cc")) { + if (anchour.classList.contains("image-link")) { + anchour.remove(); + } else { + let el = document.createElement("span"); + el.textContent = anchour.textContent; + anchour.replaceWith(el); + } + } else if (hostname.includes("fanbox.cc")) { + anchour.href = anchour.href.replace( + /https?:\/\/(?:[a-zA-Z0-9-]*.)?fanbox\.cc\/(?:(?:manage\/)|(?:@[a-zA-Z\d]+\/)|)posts\/(\d+)/g, + "/fanbox/post/$1" + ); + } else if (hostname.includes("patreon.com")) { + anchour.href = anchour.href.replace( + /https?:\/\/(?:[\w-]*.)?patreon\.com\/posts\/.*\b(\d+)\b(?:\?.*)?/g, + "/patreon/post/$1" + ); + } + }); + + // Remove needless spaces and empty paragraphs. + /** + * @type {NodeListOf { + if ( + paragraph.nextElementSibling && + paragraph.nextElementSibling.tagName === "BR" + ) { + paragraph.nextElementSibling.remove(); + paragraph.remove(); + } else { + paragraph.remove(); + } + }); +} diff --git a/client/src/entities/posts/overview/flag-button.module.scss b/client/src/entities/posts/overview/flag-button.module.scss new file mode 100644 index 0000000..dcffc45 --- /dev/null +++ b/client/src/entities/posts/overview/flag-button.module.scss @@ -0,0 +1,183 @@ +.button { + // display: flex; + // flex-flow: row nowrap; + // gap: 0.5em; + // justify-content: center; + // align-items: center; + // min-width: 6em; + color: hsl(0, 0%, 100%); + border: transparent; + background: transparent; + font-weight: bold; + text-shadow: + hsl(0, 0%, 0%) 0px 0px 3px, + hsl(0, 0%, 0%) -1px -1px 0px, + hsl(0, 0%, 0%) 1px 1px 0px; +} + +.disabled { + color: grey; +} + +.modal { + color: var(--colour0-primary); + background-color: var(--colour1-primary); + padding: 0; + font-size: 80%; + + form { + div { + width: 100%; + } + + label { + display: block; + width: 100%; + padding: 2px; + } + + input[type="radio"] { + display: none; + } + + input[type="radio"] ~ label:hover { + background-color: var(--colour0-tertirary); + } + + input[type="radio"]:checked ~ label { + background-color: var(--anchour-internal-colour2-primary); + } + + section { + &:first-child { + div:not(:last-child) { + border-bottom: 1px solid var(--colour0-tertirary); + } + } + + &:last-child div label { + color: var(--colour0-secondary); + } + } + } + + @media only screen and (max-width: 720px) { + @media (max-width: 400px) { + width: 90vw; + } + + height: 60vh; + + form { + height: 100%; + display: flex; + flex-direction: column; + + section { + &:first-child { + margin-bottom: 0.25em; + border-bottom: 1px solid var(--colour0-tertirary); + + div:not(:last-child) { + border-bottom: 1px solid var(--colour0-tertirary); + } + } + + &:last-child { + display: inline-flex; + flex-direction: column; + flex-grow: 1; + + div { + // &:not(:last-child) + &:first-child { + height: 100%; + + label { + cursor: default; + position: relative; + top: 50%; + transform: translateY(-50%); + } + } + + &:last-child { + flex-basis: 0; + + button { + width: 50%; + } + } + } + } + } + } + } + + @media only screen and (min-width: 721px) { + @media (max-width: 1200px) { + width: 50vw; + } + @media (max-width: 1600px) { + width: 40vw; + } + @media (min-width: 1600px) { + width: 30vw; + } + + form { + display: flex; + + section { + &:first-child { + // Reason selection box + padding-right: 0.5em; + width: max(30%, 10em); + + div { + > label { + padding-left: 4px; + } + &:first-child > label { + padding-top: 4px; + } + &:last-child > label{ + padding-bottom: 4px; + } + } + } + + &:last-child { + // Details/submit + display: flex; + flex-direction: column; + width: 70%; + margin-right: 2px; + + :first-child { + // Detailed reason box + flex-grow: 1; + top: 5vh; + + label { + cursor: default; + position: relative; + top: 50%; + transform: translateY(-50%); + } + } + + :last-child { + // Buttons' container div + text-align: center; + margin-bottom: 0.25em; + + :first-child { + margin-right: 0.25em; + } + } + } + } + } + } +} diff --git a/client/src/entities/posts/overview/flag-button.tsx b/client/src/entities/posts/overview/flag-button.tsx new file mode 100644 index 0000000..680b993 --- /dev/null +++ b/client/src/entities/posts/overview/flag-button.tsx @@ -0,0 +1,182 @@ +import { FormEvent, MouseEvent, useEffect, useRef, useState } from "react"; +import { flagPost } from "#api/posts"; +import { useClient } from "#hooks"; +import { LoadingIcon } from "#components/loading"; +import { Button } from "#components/buttons"; +import { Modal } from "#components/modal"; +import { IPostActionsProps } from "./types"; + +import * as styles from "./flag-button.module.scss"; + +const REASON_MESSAGES: {[reason: string]: string} = { + "delete-copyright": `Post contains copyrighted material that should not be shared. We will review this post to ensure legal compliance and protect creators' rights. + After submitting the flag to finalize the request please contact us via the contact page.`, + "delete-abuse": `Use this flag if the post includes illegal material or content that violates the law in your country. + After submitting the flag to finalize the request please contact us via the contact page.`, + "missing-password": "Post files or urls requires a password to access content, but no password is provided in the post.", + "offsite-expired": "If the post files rewards exist on an external link that no longer works, select this flag.", + "post-changed": `Use this option if the original post has been edited at the source with new images or URLs, revised content, or updated index posts.`, + "corrupted-files": "Images, videos, or other files that are broken or unreadable.", + "missing-files": "The post is missing files that were expected to be included, such as images, attachments, or URLs.", + "stale-comments": "The comments on the post are outdated and require reimport to give access to passwords or URLs.", + "formatting-error": "Post text has formatting issues, such as text size difference, missing highlighting, incorrect spacing, or broken layout. Reporting this will help development.", + "reason-other": "There was some other problem with this post.", +}; + +interface IFlagButtonProps + extends Pick< + IPostActionsProps, + "flagged" | "service" | "profileID" | "postID" + > {} + +export function FlagButton({ + service, + profileID: creatorId, + postID: postId, + flagged, +}: IFlagButtonProps) { + const client = useClient(); + const [isFlagged, setFlagged] = useState(Boolean(flagged)); + const [isFlagging, setFlagging] = useState(false); + // const [modalOpen, setModalOpen] = useState(false); + // const [reason, setReason] = useState(flagged ?? ""); + // const [reasonDescription, setReasonDescription] = useState(REASON_MESSAGES[reason] ?? "Select a reason") + + useEffect(() => { + setFlagged(Boolean(flagged)); + }, [service, creatorId, postId, flagged]); + + async function handleFlagging(flagReason: string) { + try { + setFlagging(true); + + let result = await flagPost({ service, creatorId, postId, reason: flagReason }); + if (result) { + setFlagged(true); + // setReason(result); + // onModalClose(); + } else { + setFlagged(false); + alert("Error flagging"); + } + } catch (e) { + alert("Error flagging: " + e); + setFlagged(false); + } finally { + setFlagging(false); + } + } + + // function showModal() { + // setModalOpen(true); + // } + + // function onModalClose() { + // setModalOpen(false); + // } + + // function submitFlag(event: MouseEvent) { + // event.preventDefault(); + // handleFlagging(reason); + // } + + // function cancelClicked(event: MouseEvent) { + // event.preventDefault(); + // setModalOpen(false); + // } + + // function reasonChanged(event: FormEvent) { + // let reasonId = (event.target as HTMLElement).id; + // setReason(reasonId); + // setReasonDescription(REASON_MESSAGES[reasonId] ?? "Invalid reason selected"); + // } + + function flagClicked(event: MouseEvent) { + event.preventDefault(); + handleFlagging("reason-other") + } + + if (!client) { + return ( + Loading... + ); + } else if (!client.isRegistered) { + return ( + ⚑ Flag + ); + } else if (isFlagging) { + return ( + Flagging... + ); + } else if (isFlagged) { + return ( + ⚑ Flagged + ); + } else { + return ( + <> + + {/* +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    */} + + ); + } +} diff --git a/client/src/entities/posts/overview/footer.module.scss b/client/src/entities/posts/overview/footer.module.scss new file mode 100644 index 0000000..3855c0e --- /dev/null +++ b/client/src/entities/posts/overview/footer.module.scss @@ -0,0 +1,3 @@ +.content { + font-family: inherit; +} diff --git a/client/src/entities/posts/overview/footer.tsx b/client/src/entities/posts/overview/footer.tsx new file mode 100644 index 0000000..610c319 --- /dev/null +++ b/client/src/entities/posts/overview/footer.tsx @@ -0,0 +1,186 @@ +import clsx from "clsx"; +import { Suspense } from "react"; +import { Await } from "react-router"; +import MicroModal from "micromodal"; +import { ICONS_PREPEND } from "#env/env-vars"; +import { Image } from "#components/images"; +import { KemonoLink, LocalLink } from "#components/links"; +import { Timestamp } from "#components/dates"; +import { Preformatted } from "#components/formatting"; +import { IComment } from "#entities/posts"; + +import * as styles from "./overview.module.scss"; +import { IPostOverviewProps } from "./types"; + +interface IPostFooterProps extends Pick { + service: string; + profileID: string; + profileName?: string; +} + +export function PostFooter({ + comments, + service, + profileID, + profileName, +}: IPostFooterProps) { + return ( +
    +

    Comments

    + {/* TODO: comment filters */} + Loading comments...

    }> + }> + {(comments: IComment[]) => ( +
    + {!comments ? ( +

    No comments found for this post.

    + ) : ( + comments.map((comment) => ( + + )) + )} +
    + )} +
    +
    +
    + ); +} + +interface IPostCommentProps { + comment: IComment; + postProfileID: string; + postProfileName?: string; + service: string; +} + +function PostComment({ + comment, + postProfileID, + postProfileName, + service, +}: IPostCommentProps) { + const { + id, + commenter, + commenter_name, + revisions, + parent_id, + content, + published, + } = comment; + const isProfileComment = commenter === postProfileID; + const modalID = `comment-revisions-${id}`; + + return ( +
    +
    + {!isProfileComment ? ( + + {commenter_name ?? "Anonymous"} + + ) : ( + <> + {/* TODO: a proper local link */} + + + + + {postProfileName ?? "Post's profile"} + + + )} + + {revisions && revisions.length !== 0 && ( + <> + MicroModal.show(modalID)} + > + ( + + edited + + ) + +
    +
    +
    +
    +
    +

    + Comment edits +

    + + +
    + +
    + {[...revisions, comment].map((revision) => ( +
    + + {revision.published ?? revision.added} + + {revision.content} +
    + ))} +
    +
    +
    +
    +
    + + )} +
    + +
    + {parent_id && ( + + )} + +

    + {service !== "boosty" ? ( + content + ) : ( + + )} +

    +
    +
    + +
    + +
    +
    + ); +} diff --git a/client/src/entities/posts/overview/header.tsx b/client/src/entities/posts/overview/header.tsx new file mode 100644 index 0000000..550a765 --- /dev/null +++ b/client/src/entities/posts/overview/header.tsx @@ -0,0 +1,276 @@ +import clsx from "clsx"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router"; +import { + createBannerURL, + createIconURL, + createPostRevisionPageURL, + createPostURL, + createProfileTagURL, + createProfilePageURL, +} from "#lib/urls"; +import { ImageBackground, ImageLink } from "#components/images"; +import { KemonoLink } from "#components/links"; +import { IPaySite } from "#entities/paysites"; +import { + addPostToFavourites, + isFavouritePost, + isRegisteredAccount, + removePostFromFavourites, +} from "#entities/account"; +import { IPostActionsProps, IPostOverviewProps } from "./types"; +import { FlagButton } from "./flag-button"; +import { Timestamp } from "#components/dates"; + +interface IPostHeaderProps + extends Pick< + IPostOverviewProps, + "post" | "profile" | "revisions" | "flagged" + > { + postTitle: string; + paysite: IPaySite; +} + +export function PostHeader({ + post, + profile, + postTitle, + paysite, + revisions, + flagged, +}: IPostHeaderProps) { + const navigate = useNavigate(); + const profileURL = String( + createProfilePageURL({ service: post.service, profileID: post.user }) + ); + const profileIcon = createIconURL(post.service, post.user); + const bannerURL = createBannerURL(post.service, post.user); + + return ( +
    +
    + + + +
    + + {!profile ? `Profile page` : profile.name} + +
    +
    + +
    +

    + {postTitle} ({paysite.title}) +

    + + {!post.published ? undefined : ( +
    +
    + Published: +
    {" "} + +
    + )} + + {!post.edited || post.edited === post.published ? undefined : ( +
    +
    + Edited: +
    {" "} + +
    + )} + +
    + {revisions.length < 2 ? ( + +
    + Imported:{" "} +
    {" "} + {post.added?.slice(0, 7) ?? "Unknown"} +
    + ) : ( + <> +
    + Imported: +
    + + + + + )} +
    + + {post.tags && post.tags.length !== 0 && ( +
    + Tags: +
    + {post.tags.map((tag, index) => ( + + {tag} + + ))} +
    +
    + )} + + +
    +
    + ); +} + +function PostActions({ + service, + profileID, + postID, + flagged, +}: IPostActionsProps) { + return ( +
    + + +
    + ); +} + +interface IFavoriteButtonProps { + service: string; + profileID: string; + postID: string; +} + +function FavoriteButton({ service, profileID, postID }: IFavoriteButtonProps) { + const [isFavorite, switchFavorite] = useState(false); + const [isLoading, switchLoading] = useState(true); + const renderKey = `${postID}${profileID}${service}`; + + useEffect(() => { + (async () => { + try { + switchLoading(true); + + const isLoggedIn = isRegisteredAccount(); + + if (!isLoggedIn) { + return; + } + + const isFav = await isFavouritePost(service, profileID, postID); + + switchFavorite(isFav); + } catch (error) { + // TODO: better error handling + console.error(error); + } finally { + switchLoading(false); + } + })(); + }, [service, profileID, postID]); + + async function handleFavorite() { + try { + switchLoading(true); + await addPostToFavourites(service, profileID, postID); + switchFavorite(true); + } catch (error) { + // TODO: better error handling + console.error(error); + } finally { + switchLoading(false); + } + } + async function handleUnfavorite() { + try { + switchLoading(true); + await removePostFromFavourites(service, profileID, postID); + switchFavorite(false); + } catch (error) { + // TODO: better error handling + console.error(error); + } finally { + switchLoading(false); + } + } + + return isFavorite ? ( + + ) : ( + + ); +} diff --git a/client/src/entities/posts/overview/overview.module.scss b/client/src/entities/posts/overview/overview.module.scss new file mode 100644 index 0000000..79c6283 --- /dev/null +++ b/client/src/entities/posts/overview/overview.module.scss @@ -0,0 +1,7 @@ +@use "../../../css/config/variables/sass.scss" as *; + +.list { + display: flex; + flex-flow: column nowrap; + gap: $size-normal; +} diff --git a/client/src/entities/posts/overview/overview.tsx b/client/src/entities/posts/overview/overview.tsx new file mode 100644 index 0000000..80a2375 --- /dev/null +++ b/client/src/entities/posts/overview/overview.tsx @@ -0,0 +1,92 @@ +import { useEffect } from "react"; +import { PostHeader } from "./header"; +import { PostBody } from "./body"; +import { PostFooter } from "./footer"; +import { IPostOverviewProps } from "./types"; + +import "fluid-player/src/css/fluidplayer.css"; + +export function PostOverview({ + post, + profile, + revisions, + flagged, + videos, + attachments, + previews, + archives_enabled, + comments, + postTitle, + paysite, +}: IPostOverviewProps) { + useEffect(() => { + document.addEventListener("DOMContentLoaded", handleShowTagsButton); + window.addEventListener("resize", handleShowTagsButton); + + return () => { + document.removeEventListener("DOMContentLoaded", handleShowTagsButton); + window.removeEventListener("resize", handleShowTagsButton); + }; + }, []); + + function handleShowTagsButton() { + addShowTagsButton(); + } + + return ( + <> + + + + + + + ); +} + +function addShowTagsButton() { + let div = document.querySelector("#post-tags > div"); + + if (document.getElementById("show-tag-overflow-button")) { + // @ts-expect-error no fucking idea what it does + document.getElementById("show-tag-overflow-button").remove(); + } + // @ts-expect-error no fucking idea what it does + + if (div && div.offsetWidth < div.scrollWidth) { + // tags overflow + let button = document.createElement("a"); + button.href = "javascript:void 0"; + button.id = "show-tag-overflow-button"; + button.textContent = "Show all »"; + button.onclick = (e) => { + if (div.classList.contains("show-overflow")) { + div.classList.remove("show-overflow"); + button.textContent = "Show all»"; + } else { + div.classList.add("show-overflow"); + button.textContent = "« Hide"; + } + }; + // @ts-expect-error no fucking idea what it does + div.parentElement.appendChild(button); + } +} diff --git a/client/src/entities/posts/overview/types.ts b/client/src/entities/posts/overview/types.ts new file mode 100644 index 0000000..f1c8885 --- /dev/null +++ b/client/src/entities/posts/overview/types.ts @@ -0,0 +1,32 @@ +import { fetchPost } from "#api/posts"; +import { IPaySite } from "#entities/paysites"; +import { + IComment, + IPost, + IPostAttachment, + IPostPreview, + IPostVideo, +} from "#entities/posts"; +import { IArtistDetails } from "#entities/profiles"; + +export interface IPostOverviewProps { + post: IPost; + + profile: IArtistDetails; + revisions: Awaited>["props"]["revisions"]; + + flagged: string | null; + videos?: IPostVideo[]; + attachments?: IPostAttachment[]; + previews?: IPostPreview[]; + archives_enabled?: boolean; + comments: Promise; + postTitle: string; + paysite: IPaySite; +} + +export interface IPostActionsProps extends Pick { + service: string; + profileID: string; + postID: string; +} diff --git a/client/src/entities/posts/overview/video.tsx b/client/src/entities/posts/overview/video.tsx new file mode 100644 index 0000000..3b6a396 --- /dev/null +++ b/client/src/entities/posts/overview/video.tsx @@ -0,0 +1,96 @@ +import { useEffect, useRef } from "react"; +import { VIDEO_AD } from "#env/env-vars"; +import { IPostVideo } from "#entities/posts"; + +interface IPostVideoProps { + video: IPostVideo; +} + +export function PostVideo({ video }: IPostVideoProps) { + const videoRef = useRef(null); + + useEffect(() => { + const videoElement = videoRef.current; + + if (!videoElement) { + return; + } + + let fluidPlayer: FluidPlayerInstance | undefined = undefined; + + console.time(`${video.path}-create`); + console.time(`${video.path}-vast`); + import("fluid-player") + .then(({ default: fluidPlayerModule }) => { + fluidPlayer = fluidPlayerModule(videoElement, { + layoutControls: { + fillToContainer: false, + preload: "none", + }, + vastOptions: { + adList: JSON.parse(atob(VIDEO_AD)), + adTextPosition: "top left", + maxAllowedVastTagRedirects: 2, + vastAdvanced: { + vastLoadedCallback: () => { + console.timeLog(`${video.path}-vast`, "Loaded VAST video."); + }, + noVastVideoCallback: () => { + console.timeLog(`${video.path}-vast`, "No VAST video loaded."); + }, + vastVideoSkippedCallback: () => { + console.timeLog(`${video.path}-vast`, "Skipped VAST video."); + }, + vastVideoEndedCallback: () => { + console.timeLog(`${video.path}-vast`, "VAST video ended."); + }, + }, + }, + } satisfies Partial); + }) + .then(() => { + console.timeEnd(`${video.path}-create`); + }) + .catch((error) => { + const message = `Failed to initialize Fluid Player for video "${video.path}".`; + console.error(new Error(message, { cause: error })); + }); + + return () => { + const onEndedEvent = new Event("ended") + videoRef.current?.dispatchEvent(onEndedEvent) + // will not fix the issue of a possible memory leak + // but will stop the video from playing + videoRef.current?.pause() + + if (fluidPlayer) { + console.time(`${video.path}-destroy`); + fluidPlayer.destroy(); + console.timeEnd(`${video.path}-destroy`); + console.timeEnd(`${video.path}-vast`); + } + }; + }, [video.path]); + + return ( +
  • + {video.name} + + {!video.caption ? undefined : {video.caption}} + + +
  • + ); +} diff --git a/client/src/entities/posts/period.ts b/client/src/entities/posts/period.ts new file mode 100644 index 0000000..8e9754f --- /dev/null +++ b/client/src/entities/posts/period.ts @@ -0,0 +1,15 @@ +const periods = ["recent", "day", "week", "month"] as const; + +export type IPopularPostsPeriod = (typeof periods)[number]; + +export function isValidPeriod(value: unknown): value is IPopularPostsPeriod { + return periods.includes(value as IPopularPostsPeriod); +} + +export function validatePeriod( + value: unknown +): asserts value is IPopularPostsPeriod { + if (!isValidPeriod(value)) { + throw new Error(`"${value}" is not a valid period.`); + } +} diff --git a/client/src/entities/posts/types.ts b/client/src/entities/posts/types.ts new file mode 100644 index 0000000..8f578a9 --- /dev/null +++ b/client/src/entities/posts/types.ts @@ -0,0 +1,153 @@ +export interface IPost { + service: string; + /** + * ID of the profile. + */ + user: string; + id: string; + title?: string; + content?: string; + file?: { + path?: string; + name: string; + }; + shared_file: boolean; + embed: {}; + attachments: IPostAttachment[]; + added?: string; + published?: string; + edited?: string; + prev?: string; + next?: string; + revision_id?: string; + tags: string[] | null; + incomplete_rewards?: string; + poll?: IPostPoll; + + fav_count?: number; + isFavourite?: boolean; + isFavouriteProfile?: boolean; +} + +export interface IPostRevision { + revision_id?: string; + added?: string; +} + +export interface IPostVideo { + index: string; + name: string; + caption: string; + extension: string; + server?: string; + path: string; +} + +export interface IPostAttachment { + path: string; + name: string; + server?: string; + extension: string; + name_extension: string; + stem: string; +} + +export interface IPostPoll { + title: string; + description?: string; + total_votes?: number; + choices: { votes: number; text: string }[]; + created_at: string; + closes_at: string; + allow_multiple?: boolean; +} + +export type IPostPreview = IPreviewThumbnail | IPreviewEmbed; + +export interface IPreviewThumbnail { + type: "thumbnail"; + server?: string; + path: string; + name: string; + caption?: string; +} + +export interface IPreviewEmbed { + type: "embed"; + url: string; + subject?: string; + description?: string; +} + +export interface IComment { + id: string; + commenter: string; + commenter_name?: string; + revisions?: ICommentRevision[]; + parent_id?: string; + published: string; + added?: string; + content: string; +} + +export interface ICommentRevision { + id: string; + published?: string; + added?: string; + content: string; +} + +export interface IAnnouncement { + service: string; + /** + * ID of profile. + */ + user_id: string; + /** + * SHA-256 hash. + */ + hash: string; + content: string; + /** + * ISO datetime without timezone. + */ + added: string; + /** + * ISO datetime without timezone. + */ + published?: string; +} + +export interface IDiscordChannelMessage { + id: string; + author: IDiscordChannelMessageAuthor; + server: string; + channel: string; + content: string; + added: string; + published: string; + edited: string; + embeds: IDiscordEmbed[]; + mentions: unknown[]; + attachments: IDiscordAttachment[]; +} + +export interface IDiscordChannelMessageAuthor { + id: string; + avatar: string; + username: string; + public_flags: number; + + discriminator: string; +} + +export interface IDiscordAttachment { + name: string; + path: string; +} + +export interface IDiscordEmbed { + url: string; + title?: string; + description?: string; +} diff --git a/client/src/entities/profiles/headers.tsx b/client/src/entities/profiles/headers.tsx new file mode 100644 index 0000000..4a7643a --- /dev/null +++ b/client/src/entities/profiles/headers.tsx @@ -0,0 +1,185 @@ +import clsx from "clsx"; +import { useEffect, useState } from "react"; +import { + createBannerURL, + createFileUploadPageURL, + createIconURL, + createProfilePageURL, +} from "#lib/urls"; +import { ImageBackground, ImageLink } from "#components/images"; +import { paysites } from "#entities/paysites"; +import { + addProfileToFavourites, + isFavouriteProfile, + isRegisteredAccount, + removeProfileFromFavourites, +} from "#entities/account"; + +interface IProps { + service: string; + profileID: string; + profileName?: string; +} + +/** + * TODO: asset imports instead of literal paths + */ +const paysiteIcons = { + patreon: "/static/patreon.svg", + fanbox: "/static/fanbox.svg", + boosty: "/static/boosty.svg", + gumroad: "/static/gumroad.svg", + subscribestar: "/static/subscribestar.png", + dlsite: "/static/dlsite.png", + fantia: "/static/fantia.png", + onlyfans: "/static/onlyfans.svg", + fansly: "/static/fansly.svg", + candfans: "/static/candfans.png", +} as const; + +export function ProfileHeader({ service, profileID, profileName }: IProps) { + const artistIcon = createIconURL(service, profileID); + const artistBanner = createBannerURL(service, profileID); + const externalProfileURL = paysites[service].user.profile(profileID); + const paysiteIconURL = paysiteIcons[service as keyof typeof paysiteIcons]; + + return ( +
    + + {/* TODO: remove self-referencing link */} + + + +
    + ); +} + +interface IFavouriteButtonProps { + service: string; + profileID: string; +} + +function FavouriteButton({ service, profileID }: IFavouriteButtonProps) { + const [isFavourited, switchFavourite] = useState(false); + const [isLoading, switchLoading] = useState(true); + const renderKey = `${service}${profileID}`; + + useEffect(() => { + (async () => { + try { + switchLoading(true); + const isLoggedIn = isRegisteredAccount(); + + if (!isLoggedIn) { + return; + } + + const result = await isFavouriteProfile(service, profileID); + + switchFavourite(result); + } catch (error) { + // TODO: better error handling + console.error(error); + } finally { + switchLoading(false); + } + })(); + }, [service, profileID]); + + async function handleFavouriting() { + try { + switchLoading(true); + await addProfileToFavourites(service, profileID); + switchFavourite(true); + } catch (error) { + // TODO: better error handling + console.error(error); + } finally { + switchLoading(false); + } + } + + async function handleUnfavouriting() { + try { + switchLoading(true); + await removeProfileFromFavourites(service, profileID); + switchFavourite(false); + } catch (error) { + // TODO: better error handling + console.error(error); + } finally { + switchLoading(false); + } + } + + return !isFavourited ? ( + + ) : ( + + ); +} diff --git a/client/src/entities/profiles/index.ts b/client/src/entities/profiles/index.ts new file mode 100644 index 0000000..92f7f68 --- /dev/null +++ b/client/src/entities/profiles/index.ts @@ -0,0 +1,4 @@ +export { getArtists, getArtist } from "./lib/get"; +export { ProfileHeader } from "./headers"; +export { Tabs } from "./tabs"; +export type { IArtistDetails, IArtist, IArtistWithFavs } from "./types"; diff --git a/client/src/entities/profiles/lib/get.ts b/client/src/entities/profiles/lib/get.ts new file mode 100644 index 0000000..ff75385 --- /dev/null +++ b/client/src/entities/profiles/lib/get.ts @@ -0,0 +1,152 @@ +import { PAGINATION_LIMIT } from "#lib/pagination"; +import { fetchArtistProfile, fetchProfiles } from "#api/profiles"; +import { findFavouriteProfiles } from "#entities/account"; +import { IArtistDetails, IArtistWithFavs } from "../types"; + +// the original page is a clusterfuck which pulls an entire artist list +// and does sorting/ordering/filtering on client +// rewriting it requires rewriting backend endpoints +// so for now it does the same +// TODO: rewrite it on backend +let allArtists: Awaited> | undefined = + undefined; + +export interface IGetArtistsArgs { + service?: string; + offset?: number; + order?: "asc" | "desc"; + sort_by?: "favorited" | "indexed" | "updated" | "name" | "service"; + query?: string; +} + +interface IGetArtistsResult { + artists: (IArtistWithFavs & { isFavourite: boolean })[]; + count: number; +} + +export async function getArtists({ + offset = 0, + service, + order = "desc", + sort_by = "favorited", + query, +}: IGetArtistsArgs): Promise { + if (!allArtists) { + allArtists = await fetchProfiles(); + } + + const normalizedQuery = query?.trim().toLowerCase(); + // MDN recommends doing this for large arrays + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare + const compare = new Intl.Collator().compare; + + const filteredArtists = allArtists + .filter((profile) => { + let isServiceMatched = true + + if (service) { + isServiceMatched = profile.service === service; + } + + let isQueryMatched = true + + if (normalizedQuery) { + const normalizedName = profile.name.trim().toLowerCase(); + const normalizedID = profile.id.trim().toLowerCase(); + + isQueryMatched = normalizedID.includes(normalizedQuery) || normalizedName.includes(normalizedQuery); + } + + return isServiceMatched && isQueryMatched; + }) + .sort((prev, next) => { + switch (sort_by) { + case "favorited": { + return prev.favorited === next.favorited + ? 0 + : prev.favorited > next.favorited + ? 1 + : -1; + } + + case "service": { + return compare(prev.service, next.service); + } + + case "name": { + return compare(prev.name, next.name); + } + + case "indexed": { + // @ts-expect-error fuck dates + const prevIndexed = prev.indexed * 1000; + // @ts-expect-error fuck dates + const nextIndexed = next.indexed * 1000; + + return prevIndexed === nextIndexed + ? 0 + : prevIndexed > nextIndexed + ? 1 + : -1; + } + case "updated": { + // @ts-expect-error fuck dates + const prevUpdated = prev.updated * 1000; + // @ts-expect-error fuck dates + const nextUpdated = next.updated * 1000; + + return prevUpdated === nextUpdated + ? 0 + : prevUpdated > nextUpdated + ? 1 + : -1; + } + + default: { + throw new Error(`Unknown sorting type "${sort_by satisfies never}".`); + } + } + }); + + // artists are only sorted by one field + // so we can get away with "desc" just slicing off the end + // without changing sorting logic + const slicedArtists = + order === "asc" + ? filteredArtists.slice(offset, offset + PAGINATION_LIMIT) + : filteredArtists + .slice( + -offset + -PAGINATION_LIMIT, + offset === 0 ? undefined : -offset + ) + .reverse(); + const profilesData: Parameters[0] = + slicedArtists.map(({ id, service }) => { + return { id, service }; + }); + const favArtists = await findFavouriteProfiles(profilesData); + const resultArtists: IGetArtistsResult["artists"] = slicedArtists.map( + (artist) => { + const fav = favArtists.find( + ({ id, service }) => id === artist.id && service === artist.service + ); + + return { + ...artist, + isFavourite: fav === undefined ? false : true, + }; + } + ); + const count = filteredArtists.length; + + return { artists: resultArtists, count }; +} + +export async function getArtist( + service: string, + id: string +): Promise { + const profile = await fetchArtistProfile(service, id); + + return profile; +} diff --git a/client/src/entities/profiles/tabs.tsx b/client/src/entities/profiles/tabs.tsx new file mode 100644 index 0000000..313279e --- /dev/null +++ b/client/src/entities/profiles/tabs.tsx @@ -0,0 +1,119 @@ +import { ReactNode } from "react"; +import { + createProfileAnnouncementsURL, + createProfileDMsURL, + createProfileFancardsURL, + createProfileLinksURL, + createProfileSharesURL, + createProfileTagsURL, + createProfilePageURL, +} from "#lib/urls"; +import { KemonoLink } from "#components/links"; + +interface IProps { + currentPage: + | "posts" + | "fancards" + | "announcements" + | "tabs" + | "dms" + | "shares" + | "linked_accounts"; + service: string; + artistID: string; + dmCount?: number; + shareCount?: number; + hasLinks?: boolean; +} + +interface ITabProps { + href: string; + isActive?: boolean; + children: ReactNode; +} + +const announcementServices = ["fanbox", "patreon"]; +const tabServices = ["patreon", "onlyfans", "fansly", "candfans"]; + +export function Tabs({ + currentPage, + service, + artistID, + dmCount, + shareCount, + hasLinks, +}: IProps) { + return ( +
      + + Posts + + + {service !== "fanbox" ? undefined : ( + + Fancards + + )} + + {!announcementServices.includes(service) ? undefined : ( + + Announcements + + )} + + {/* TODO: fix the typos mismatch */} + {!tabServices.includes(service) ? undefined : ( + + Tags + + )} + + {!dmCount ? undefined : ( + + DMs ({dmCount}) + + )} + + {!shareCount ? undefined : ( + + Uploads ({shareCount}) + + )} + + + {!hasLinks ? <>Linked Accounts : <>Linked Accounts (✔️)} + +
    + ); +} + +function Tab({ href, isActive, children }: ITabProps) { + return ( +
  • + + {children} + +
  • + ); +} diff --git a/client/src/entities/profiles/types.ts b/client/src/entities/profiles/types.ts new file mode 100644 index 0000000..60162b7 --- /dev/null +++ b/client/src/entities/profiles/types.ts @@ -0,0 +1,23 @@ +export interface IArtistDetails { + id: string + public_id: string | null + service: string + name: string + indexed: string + updated: string +} + +export interface IArtist { + id: string; + name: string; + service: string; + indexed: string; + updated: string; + public_id: string; + relation_id: number; +} + +export interface IArtistWithFavs extends IArtist { + count: number; + favorited: number +} diff --git a/client/src/entities/tags/index.ts b/client/src/entities/tags/index.ts new file mode 100644 index 0000000..18cafdc --- /dev/null +++ b/client/src/entities/tags/index.ts @@ -0,0 +1,2 @@ +export { getTags } from "./lib/get"; +export type { ITag } from "./types"; diff --git a/client/src/entities/tags/lib/get.ts b/client/src/entities/tags/lib/get.ts new file mode 100644 index 0000000..c290e30 --- /dev/null +++ b/client/src/entities/tags/lib/get.ts @@ -0,0 +1,7 @@ +import { fetchProfileTags } from "#api/tags"; + +export async function getTags(service: string, profileID: string) { + const result = await fetchProfileTags(service, profileID); + + return result; +} diff --git a/client/src/entities/tags/types.ts b/client/src/entities/tags/types.ts new file mode 100644 index 0000000..c89621e --- /dev/null +++ b/client/src/entities/tags/types.ts @@ -0,0 +1,4 @@ +export interface ITag { + tag: string; + post_count: number; +} diff --git a/client/src/env/derived-vars.js b/client/src/env/derived-vars.js deleted file mode 100644 index 674271a..0000000 --- a/client/src/env/derived-vars.js +++ /dev/null @@ -1,4 +0,0 @@ -import { KEMONO_SITE, NODE_ENV } from "./env-vars.js"; - -export const IS_DEVELOPMENT = NODE_ENV === "development"; -export const SITE_HOSTNAME = new URL(KEMONO_SITE).hostname; diff --git a/client/src/env/derived-vars.ts b/client/src/env/derived-vars.ts new file mode 100644 index 0000000..0212991 --- /dev/null +++ b/client/src/env/derived-vars.ts @@ -0,0 +1,30 @@ +import { + KEMONO_SITE, + NODE_ENV, + PAYSITE_LIST, + ARTISTS_OR_CREATORS, + API_SERVER_BASE_URL, + API_SERVER_PORT, +} from "./env-vars"; +import { IPaySite, paysites } from "#entities/paysites"; + +export const IS_DEVELOPMENT = NODE_ENV === "development"; +export const SITE_HOSTNAME = new URL(KEMONO_SITE).hostname; +export const AVAILABLE_PAYSITES = PAYSITE_LIST.reduce( + (availablePaysites, paysiteName) => { + const paysite = paysites[paysiteName]; + availablePaysites[paysiteName] = paysite; + + return availablePaysites; + }, + {} +); +export const AVAILABLE_PAYSITE_LIST = PAYSITE_LIST.map< + IPaySite & { name: string } +>((paysiteName) => { + return { ...AVAILABLE_PAYSITES[paysiteName], name: paysiteName }; +}); +export const ARTISTS_OR_CREATORS_LOWERCASE = ARTISTS_OR_CREATORS.toLowerCase(); +export const API_SERVER_URL = !API_SERVER_BASE_URL + ? "" + : `${API_SERVER_BASE_URL}${!API_SERVER_PORT ? "" : `:${API_SERVER_PORT}`}`; diff --git a/client/src/env/env-vars.js b/client/src/env/env-vars.js deleted file mode 100644 index 50a3f5d..0000000 --- a/client/src/env/env-vars.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - https://webpack.js.org/plugins/define-plugin/ -*/ - -/** - * @type {string} - */ -export const KEMONO_SITE = BUNDLER_ENV_KEMONO_SITE; -/** - * @type {string} - */ -export const NODE_ENV = BUNDLER_ENV_NODE_ENV; -/** - * @type {string} - */ -export const ICONS_PREPEND = BUNDLER_ENV_ICONS_PREPEND; -/** - * @type {string} - */ -export const BANNERS_PREPEND = BUNDLER_ENV_BANNERS_PREPEND; -/** - * @type {string} - */ -export const THUMBNAILS_PREPEND = BUNDLER_ENV_THUMBNAILS_PREPEND; -/** - * @type {string} - */ -export const CREATORS_LOCATION = BUNDLER_ENV_CREATORS_LOCATION; -/** - * @type {string} - */ diff --git a/client/src/env/env-vars.ts b/client/src/env/env-vars.ts new file mode 100644 index 0000000..a646c51 --- /dev/null +++ b/client/src/env/env-vars.ts @@ -0,0 +1,139 @@ +/* + https://webpack.js.org/plugins/define-plugin/ +*/ + +import type { INavItem } from "#components/layout"; + +export const KEMONO_SITE = BUNDLER_ENV_KEMONO_SITE; + +export const SENTRY_DSN = BUNDLER_ENV_SENTRY_DSN; + +export const SITE_NAME = BUNDLER_ENV_SITE_NAME; + +export const NODE_ENV = process.env.NODE_ENV; + +export const ICONS_PREPEND = BUNDLER_ENV_ICONS_PREPEND; + +export const BANNERS_PREPEND = BUNDLER_ENV_BANNERS_PREPEND; + +export const THUMBNAILS_PREPEND = BUNDLER_ENV_THUMBNAILS_PREPEND; + +export const ARTISTS_OR_CREATORS = BUNDLER_ENV_ARTISTS_OR_CREATORS; + +export const DISABLE_DMS = BUNDLER_ENV_DISABLE_DMS; + +export const DISABLE_FAQ = BUNDLER_ENV_DISABLE_FAQ; + +export const DISABLE_FILEHAUS = BUNDLER_ENV_DISABLE_FILEHAUS; + +export const SIDEBAR_ITEMS = BUNDLER_ENV_SIDEBAR_ITEMS; + +export const FOOTER_ITEMS = BUNDLER_ENV_FOOTER_ITEMS; + +/** + * b64-encoded string + */ +export const BANNER_GLOBAL = BUNDLER_ENV_BANNER_GLOBAL; + +/** + * b64-encoded string + */ +export const ANNOUNCEMENT_BANNER_GLOBAL = BUNDLER_ENV_ANNOUNCEMENT_BANNER_GLOBAL; + +/** + * b64-encoded string + */ +export const BANNER_WELCOME = BUNDLER_ENV_BANNER_WELCOME; + +export const HOME_BACKGROUND_IMAGE = BUNDLER_ENV_HOME_BACKGROUND_IMAGE; + +export const HOME_MASCOT_PATH = BUNDLER_ENV_HOME_MASCOT_PATH; + +export const HOME_LOGO_PATH = BUNDLER_ENV_HOME_LOGO_PATH; + +/** + * b64-encoded string + */ +export const HOME_WELCOME_CREDITS = BUNDLER_ENV_HOME_WELCOME_CREDITS; + +export const PAYSITE_LIST = BUNDLER_ENV_PAYSITE_LIST; + +/** + * b64-encoded string + */ +export const HEADER_AD = BUNDLER_ENV_HEADER_AD; + +/** + * b64-encoded string + */ +export const MIDDLE_AD = BUNDLER_ENV_MIDDLE_AD; + +/** + * b64-encoded string + */ +export const FOOTER_AD = BUNDLER_ENV_FOOTER_AD; + +/** + * b64-encoded string + */ +export const SLIDER_AD = BUNDLER_ENV_SLIDER_AD; + +/** + * b64-encoded JSON string + */ +export const VIDEO_AD = BUNDLER_ENV_VIDEO_AD; + +export const IS_ARCHIVER_ENABLED = BUNDLER_ENV_IS_ARCHIVER_ENABLED; + +export const API_SERVER_BASE_URL = BUNDLER_ENV_API_SERVER_BASE_URL; + +export const API_SERVER_PORT = BUNDLER_ENV_API_SERVER_PORT; + +export const GIT_COMMIT_HASH = BUNDLER_ENV_GIT_COMMIT_HASH; + +export const BUILD_DATE = BUNDLER_ENV_BUILD_DATE; + +export const IS_FILE_SERVING_ENABLED = BUNDLER_ENV_IS_FILE_SERVING_ENABLED; + +// stolen from https://stackoverflow.com/a/76844373 +declare global { + const BUNDLER_ENV_KEMONO_SITE: string; + const BUNDLER_ENV_SENTRY_DSN: string | undefined; + const BUNDLER_ENV_SITE_NAME: string; + const BUNDLER_ENV_ICONS_PREPEND: string; + const BUNDLER_ENV_BANNERS_PREPEND: string; + const BUNDLER_ENV_THUMBNAILS_PREPEND: string; + const BUNDLER_ENV_CREATORS_LOCATION: string; + const BUNDLER_ENV_ARTISTS_OR_CREATORS: string; + const BUNDLER_ENV_DISABLE_DMS: boolean; + const BUNDLER_ENV_DISABLE_FAQ: boolean; + const BUNDLER_ENV_DISABLE_FILEHAUS: boolean; + const BUNDLER_ENV_SIDEBAR_ITEMS: INavItem[]; + const BUNDLER_ENV_FOOTER_ITEMS: unknown[] | undefined; + const BUNDLER_ENV_BANNER_GLOBAL: string | undefined; + const BUNDLER_ENV_ANNOUNCEMENT_BANNER_GLOBAL: string | undefined; + const BUNDLER_ENV_BANNER_WELCOME: string | undefined; + const BUNDLER_ENV_HOME_BACKGROUND_IMAGE: string | undefined; + const BUNDLER_ENV_HOME_MASCOT_PATH: string | undefined; + const BUNDLER_ENV_HOME_LOGO_PATH: string | undefined; + const BUNDLER_ENV_HOME_WELCOME_CREDITS: string; + const BUNDLER_ENV_PAYSITE_LIST: string[]; + const BUNDLER_ENV_HOME_ANNOUNCEMENTS: IAnnouncement[] | undefined; + const BUNDLER_ENV_HEADER_AD: string; + const BUNDLER_ENV_MIDDLE_AD: string; + const BUNDLER_ENV_FOOTER_AD: string; + const BUNDLER_ENV_SLIDER_AD: string; + const BUNDLER_ENV_VIDEO_AD: string; + const BUNDLER_ENV_IS_ARCHIVER_ENABLED: boolean; + const BUNDLER_ENV_API_SERVER_BASE_URL: string | undefined; + const BUNDLER_ENV_API_SERVER_PORT: number | undefined; + const BUNDLER_ENV_GIT_COMMIT_HASH: string | undefined; + const BUNDLER_ENV_BUILD_DATE: string | undefined; + const BUNDLER_ENV_IS_FILE_SERVING_ENABLED: boolean; +} + +interface IAnnouncement { + title: string; + date: string; + content: string; +} diff --git a/client/src/index.scss b/client/src/index.scss new file mode 100644 index 0000000..d344024 --- /dev/null +++ b/client/src/index.scss @@ -0,0 +1,3 @@ +@use "./css"; +@use "./components"; +@use "./pages"; diff --git a/client/src/index.tsx b/client/src/index.tsx new file mode 100644 index 0000000..79b44e8 --- /dev/null +++ b/client/src/index.tsx @@ -0,0 +1,24 @@ +// TODO: nuke/inline these styles +import "purecss/build/base-min.css"; +import "purecss/build/grids-min.css"; +import "purecss/build/grids-responsive-min.css"; + +import "./index.scss"; +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RouterProvider } from "react-router/dom"; +import { router } from "./router"; + +const rootElement = document.getElementById("root"); + +if (!rootElement) { + throw new Error("Root element is missing."); +} + +const root = createRoot(rootElement); + +root.render( + + + +); diff --git a/client/src/js/account.js b/client/src/js/account.js deleted file mode 100644 index 9701d8f..0000000 --- a/client/src/js/account.js +++ /dev/null @@ -1 +0,0 @@ -export const isLoggedIn = localStorage.getItem("logged_in") === "yes"; diff --git a/client/src/js/admin.js b/client/src/js/admin.js deleted file mode 100644 index 99fd033..0000000 --- a/client/src/js/admin.js +++ /dev/null @@ -1,7 +0,0 @@ -import "./admin.scss"; -import { fixImageLinks } from "@wp/utils"; -import { initSections } from "./page-loader"; -import { adminPageScripts } from "@wp/pages"; - -fixImageLinks(document.images); -initSections(adminPageScripts); diff --git a/client/src/js/admin.scss b/client/src/js/admin.scss deleted file mode 100644 index 9a46059..0000000 --- a/client/src/js/admin.scss +++ /dev/null @@ -1,3 +0,0 @@ -@use "../css"; -@use "../pages/components"; -@use "../pages/account/administrator"; diff --git a/client/src/js/component-factory.js b/client/src/js/component-factory.js deleted file mode 100644 index 8dfaabb..0000000 --- a/client/src/js/component-factory.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @type {Map} - */ -const components = new Map(); - -/** - * @param {HTMLElement} footer - */ -export function initComponentFactory(footer) { - const container = footer.querySelector(".component-container"); - /** - * @type {NodeListOf *`); - - componentElements.forEach((component) => { - components.set(component.className.trim(), component); - }); - container.remove(); -} - -/** - * @param {string} className - */ -export function createComponent(className) { - const componentSkeleton = components.get(className); - - if (!componentSkeleton) { - return console.error(`Component "${className}" doesn't exist.`); - } - - const newInstance = componentSkeleton.cloneNode(true); - - return newInstance; -} diff --git a/client/src/js/favorites.js b/client/src/js/favorites.js deleted file mode 100644 index e678435..0000000 --- a/client/src/js/favorites.js +++ /dev/null @@ -1,207 +0,0 @@ -import { kemonoAPI } from "@wp/api"; - -export async function initFavorites() { - let artistFavs = localStorage.getItem("favs"); - let postFavs = localStorage.getItem("post_favs"); - - if (!artistFavs || artistFavs === "undefined") { - /** - * @type {string} - */ - const favs = await kemonoAPI.favorites.retrieveFavoriteArtists(); - - if (favs) { - localStorage.setItem("favs", favs); - } - } - - if (!postFavs || postFavs === "undefined") { - /** - * @type {string} - */ - const favs = await kemonoAPI.favorites.retrieveFavoritePosts(); - - if (favs) { - localStorage.setItem("post_favs", favs); - } - } -} - -async function saveFavouriteArtists() { - try { - const favs = await kemonoAPI.favorites.retrieveFavoriteArtists(); - - if (!favs) { - alert("Could not retrieve favorite artists"); - return false; - } - - localStorage.setItem("favs", favs); - return true; - } catch (error) { - console.error(error); - } -} - -async function saveFavouritePosts() { - try { - const favs = await kemonoAPI.favorites.retrieveFavoritePosts(); - - if (!favs) { - alert("Could not retrieve favorite posts"); - return false; - } - - localStorage.setItem("post_favs", favs); - return true; - } catch (error) { - console.error(error); - } -} - -/** - * @param {string} id - * @param {string} service - * @returns {Promise | undefined} - */ -export async function findFavouriteArtist(id, service) { - /** - * @type {KemonoAPI.Favorites.User[]} - */ - let favList; - - try { - favList = JSON.parse(localStorage.getItem("favs")); - } catch (error) { - // corrupted entry - if (error instanceof SyntaxError) { - const isSaved = await saveFavouriteArtists(); - - if (!isSaved) { - return undefined; - } - - return await findFavouriteArtist(id, service); - } - } - - if (!favList) { - return undefined; - } - - const favArtist = favList.find((favItem) => { - return favItem.id === id && favItem.service === service; - }); - - return favArtist; -} - -/** - * @param {string} service - * @param {string} user - * @param {string} postID - * @returns {Promise | undefined} - */ -export async function findFavouritePost(service, user, postID) { - /** - * @type {KemonoAPI.Favorites.Post[]} - */ - let favList; - - try { - favList = JSON.parse(localStorage.getItem("post_favs")); - - if (!favList) { - return undefined; - } - - const favPost = favList.find((favItem) => { - const isMatch = favItem.id === postID && favItem.service === service && favItem.user === user; - return isMatch; - }); - - return favPost; - } catch (error) { - // corrupted entry - if (error instanceof SyntaxError) { - const isSaved = await saveFavouritePosts(); - - if (!isSaved) { - return undefined; - } - - return await findFavouritePost(service, user, postID); - } - } -} - -/** - * @param {string} id - * @param {string} service - */ -export async function addFavouriteArtist(id, service) { - const isFavorited = await kemonoAPI.favorites.favoriteArtist(service, id); - - if (!isFavorited) { - return false; - } - - const newFavs = await kemonoAPI.favorites.retrieveFavoriteArtists(); - localStorage.setItem("favs", newFavs); - - return true; -} - -/** - * @param {string} id - * @param {string} service - */ -export async function removeFavouriteArtist(id, service) { - const isUnfavorited = await kemonoAPI.favorites.unfavoriteArtist(service, id); - - if (!isUnfavorited) { - return false; - } - - const favItems = await kemonoAPI.favorites.retrieveFavoriteArtists(); - localStorage.setItem("favs", favItems); - - return true; -} - -/** - * @param {string} service - * @param {string} user - * @param {string} postID - */ -export async function addFavouritePost(service, user, postID) { - const isFavorited = await kemonoAPI.favorites.favoritePost(service, user, postID); - - if (!isFavorited) { - return false; - } - - const newFavs = await kemonoAPI.favorites.retrieveFavoritePosts(); - localStorage.setItem("post_favs", newFavs); - - return true; -} - -/** - * @param {string} service - * @param {string} user - * @param {string} postID - * @returns - */ -export async function removeFavouritePost(service, user, postID) { - const isUnfavorited = await kemonoAPI.favorites.unfavoritePost(service, user, postID); - - if (!isUnfavorited) { - return false; - } - - const favItems = await kemonoAPI.favorites.retrieveFavoritePosts(); - localStorage.setItem("post_favs", favItems); - - return true; -} diff --git a/client/src/js/feature-detect.js b/client/src/js/feature-detect.js deleted file mode 100644 index 0286d95..0000000 --- a/client/src/js/feature-detect.js +++ /dev/null @@ -1,13 +0,0 @@ -export const features = { - localStorage: isLocalStorageAvailable(), -}; - -function isLocalStorageAvailable() { - try { - localStorage.setItem("__storage_test__", "__storage_test__"); - localStorage.removeItem("__storage_test__"); - return true; - } catch (error) { - return false; - } -} diff --git a/client/src/js/global.js b/client/src/js/global.js deleted file mode 100644 index 333a110..0000000 --- a/client/src/js/global.js +++ /dev/null @@ -1,18 +0,0 @@ -import "./global.scss"; -import "purecss/build/base-min.css"; -import "purecss/build/grids-min.css"; -import "purecss/build/grids-responsive-min.css"; -import 'whatwg-fetch'; /* fetch polyfill */ -import { isLoggedIn } from "@wp/js/account"; -import { initFavorites } from "@wp/js/favorites"; -import { fixImageLinks } from "@wp/utils"; -import { globalPageScripts } from "@wp/pages"; -import { initSections } from "./page-loader"; -import { initPendingReviewDms } from "@wp/js/pending-review-dms"; - -if (isLoggedIn) { - initFavorites(); - initPendingReviewDms(); -} -fixImageLinks(document.images); -initSections(globalPageScripts); diff --git a/client/src/js/global.scss b/client/src/js/global.scss deleted file mode 100644 index a6e8a86..0000000 --- a/client/src/js/global.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use "../css"; -@use "../pages"; diff --git a/client/src/js/moderator.js b/client/src/js/moderator.js deleted file mode 100644 index ed160e6..0000000 --- a/client/src/js/moderator.js +++ /dev/null @@ -1,7 +0,0 @@ -import "./moderator.scss"; -import { fixImageLinks } from "@wp/utils"; -import { initSections } from "./page-loader"; -import { moderatorPageScripts } from "@wp/pages"; - -fixImageLinks(document.images); -initSections(moderatorPageScripts); diff --git a/client/src/js/moderator.scss b/client/src/js/moderator.scss deleted file mode 100644 index ab8a9c1..0000000 --- a/client/src/js/moderator.scss +++ /dev/null @@ -1 +0,0 @@ -@use "../pages/moderator"; diff --git a/client/src/js/page-loader.js b/client/src/js/page-loader.js deleted file mode 100644 index 46c7d8c..0000000 --- a/client/src/js/page-loader.js +++ /dev/null @@ -1,37 +0,0 @@ -import { initShell } from "@wp/components"; -import { initComponentFactory } from "./component-factory"; - -/** - * Initialises the scripts on the page. - * @param {Map void>} pages The map of page names and their callbacks. - */ -export function initSections(pages) { - const sidebar = document.querySelector(".global-sidebar"); - const main = document.querySelector("main"); - /** - * @type {HTMLElement} - */ - const footer = document.querySelector(".global-footer"); - /** - * @type {NodeListOf} - */ - const sections = main.querySelectorAll("main > .site-section"); - - initComponentFactory(footer); - initShell(sidebar); - sections.forEach((section) => { - const sectionNames = section.className.match(/site-section--([a-z\-]+)/ig); - - if (sectionNames && sectionNames.length > 0) { - sectionNames.forEach((sectionName) => { - sectionName = sectionName.replace('site-section--', ''); - if (pages.has(sectionName)) { - const sectionCallbacks = pages.get(sectionName); - sectionCallbacks.forEach((sectionCallback) => { - sectionCallback(section); - }); - } - }); - } - }); -} diff --git a/client/src/js/pending-review-dms.js b/client/src/js/pending-review-dms.js deleted file mode 100644 index ff04258..0000000 --- a/client/src/js/pending-review-dms.js +++ /dev/null @@ -1,19 +0,0 @@ -import { kemonoAPI } from "@wp/api"; - -export async function initPendingReviewDms(forceReload= false, minutesForRecheck = 30) { - let HasPendingReviewDms = localStorage.getItem("has_pending_review_dms") === "true"; - let LastCheckedHasPendingReviewDms = parseInt(localStorage.getItem("last_checked_has_pending_review_dms")); - - if (forceReload || !LastCheckedHasPendingReviewDms || (LastCheckedHasPendingReviewDms < Date.now() - minutesForRecheck * 60 * 1000)) { - /** - * @type {string} - */ - HasPendingReviewDms = await kemonoAPI.dms.retrieveHasPendingDMs(); - localStorage.setItem("has_pending_review_dms", HasPendingReviewDms.toString()); - localStorage.setItem("last_checked_has_pending_review_dms", Date.now().toString()); - } - if (HasPendingReviewDms) - document.querySelector(".review_dms > img").src = "/static/menu/red_dm.svg"; - else - document.querySelector(".review_dms > img").src = "/static/menu/dm.svg"; -} diff --git a/client/src/js/resumable.js b/client/src/js/resumable.js deleted file mode 100644 index bf05040..0000000 --- a/client/src/js/resumable.js +++ /dev/null @@ -1,1242 +0,0 @@ -/* - * MIT Licensed - * http://www.23developer.com/opensource - * http://github.com/23/resumable.js - * Steffen Tiedemann Christensen, steffen@23company.com - */ - -(function () { - "use strict"; - - var Resumable = function (opts) { - if (!(this instanceof Resumable)) { - return new Resumable(opts); - } - this.version = 1.0; - // SUPPORTED BY BROWSER? - // Check if these features are support by the browser: - // - File object type - // - Blob object type - // - FileList object type - // - slicing files - this.support = - typeof File !== "undefined" && - typeof Blob !== "undefined" && - typeof FileList !== "undefined" && - (!!Blob.prototype.webkitSlice || !!Blob.prototype.mozSlice || !!Blob.prototype.slice || false); - if (!this.support) return false; - - // PROPERTIES - var $ = this; - $.files = []; - $.defaults = { - chunkSize: 1 * 1024 * 1024, - forceChunkSize: false, - simultaneousUploads: 3, - fileParameterName: "file", - chunkNumberParameterName: "resumableChunkNumber", - chunkSizeParameterName: "resumableChunkSize", - currentChunkSizeParameterName: "resumableCurrentChunkSize", - totalSizeParameterName: "resumableTotalSize", - typeParameterName: "resumableType", - identifierParameterName: "resumableIdentifier", - fileNameParameterName: "resumableFilename", - relativePathParameterName: "resumableRelativePath", - totalChunksParameterName: "resumableTotalChunks", - dragOverClass: "dragover", - throttleProgressCallbacks: 0.5, - query: {}, - headers: {}, - preprocess: null, - preprocessFile: null, - method: "multipart", - uploadMethod: "POST", - testMethod: "GET", - prioritizeFirstAndLastChunk: false, - target: "/", - testTarget: null, - parameterNamespace: "", - testChunks: true, - generateUniqueIdentifier: null, - getTarget: null, - maxChunkRetries: 100, - chunkRetryInterval: undefined, - permanentErrors: [400, 401, 403, 404, 409, 415, 500, 501], - maxFiles: undefined, - withCredentials: false, - xhrTimeout: 0, - clearInput: true, - chunkFormat: "blob", - setChunkTypeFromFile: false, - maxFilesErrorCallback: function (files, errorCount) { - var maxFiles = $.getOpt("maxFiles"); - alert("Please upload no more than " + maxFiles + " file" + (maxFiles === 1 ? "" : "s") + " at a time."); - }, - minFileSize: 1, - minFileSizeErrorCallback: function (file, errorCount) { - alert( - file.fileName || - file.name + - " is too small, please upload files larger than " + - $h.formatSize($.getOpt("minFileSize")) + - ".", - ); - }, - maxFileSize: undefined, - maxFileSizeErrorCallback: function (file, errorCount) { - alert( - file.fileName || - file.name + " is too large, please upload files less than " + $h.formatSize($.getOpt("maxFileSize")) + ".", - ); - }, - fileType: [], - fileTypeErrorCallback: function (file, errorCount) { - alert( - file.fileName || - file.name + " has type not allowed, please upload files of type " + $.getOpt("fileType") + ".", - ); - }, - }; - $.opts = opts || {}; - $.getOpt = function (o) { - var $opt = this; - // Get multiple option if passed an array - if (o instanceof Array) { - var options = {}; - $h.each(o, function (option) { - options[option] = $opt.getOpt(option); - }); - return options; - } - // Otherwise, just return a simple option - if ($opt instanceof ResumableChunk) { - if (typeof $opt.opts[o] !== "undefined") { - return $opt.opts[o]; - } else { - $opt = $opt.fileObj; - } - } - if ($opt instanceof ResumableFile) { - if (typeof $opt.opts[o] !== "undefined") { - return $opt.opts[o]; - } else { - $opt = $opt.resumableObj; - } - } - if ($opt instanceof Resumable) { - if (typeof $opt.opts[o] !== "undefined") { - return $opt.opts[o]; - } else { - return $opt.defaults[o]; - } - } - }; - $.indexOf = function (array, obj) { - if (array.indexOf) { - return array.indexOf(obj); - } - for (var i = 0; i < array.length; i++) { - if (array[i] === obj) { - return i; - } - } - return -1; - }; - - // EVENTS - // catchAll(event, ...) - // fileSuccess(file), fileProgress(file), fileAdded(file, event), filesAdded(files, filesSkipped), fileRetry(file), - // fileError(file, message), complete(), progress(), error(message, file), pause() - $.events = []; - $.on = function (event, callback) { - $.events.push(event.toLowerCase(), callback); - }; - $.fire = function () { - // `arguments` is an object, not array, in FF, so: - var args = []; - for (var i = 0; i < arguments.length; i++) args.push(arguments[i]); - // Find event listeners, and support pseudo-event `catchAll` - var event = args[0].toLowerCase(); - for (var i = 0; i <= $.events.length; i += 2) { - if ($.events[i] == event) $.events[i + 1].apply($, args.slice(1)); - if ($.events[i] == "catchall") $.events[i + 1].apply(null, args); - } - if (event == "fileerror") $.fire("error", args[2], args[1]); - if (event == "fileprogress") $.fire("progress"); - }; - - // INTERNAL HELPER METHODS (handy, but ultimately not part of uploading) - var $h = { - stopEvent: function (e) { - e.stopPropagation(); - e.preventDefault(); - }, - each: function (o, callback) { - if (typeof o.length !== "undefined") { - for (var i = 0; i < o.length; i++) { - // Array or FileList - if (callback(o[i]) === false) return; - } - } else { - for (i in o) { - // Object - if (callback(i, o[i]) === false) return; - } - } - }, - generateUniqueIdentifier: function (file, event) { - var custom = $.getOpt("generateUniqueIdentifier"); - if (typeof custom === "function") { - return custom(file, event); - } - var relativePath = file.webkitRelativePath || file.relativePath || file.fileName || file.name; // Some confusion in different versions of Firefox - var size = file.size; - return size + "-" + relativePath.replace(/[^0-9a-zA-Z_-]/gim, ""); - }, - contains: function (array, test) { - var result = false; - - $h.each(array, function (value) { - if (value == test) { - result = true; - return false; - } - return true; - }); - - return result; - }, - formatSize: function (size) { - if (size < 1024) { - return size + " bytes"; - } else if (size < 1024 * 1024) { - return (size / 1024.0).toFixed(0) + " KB"; - } else if (size < 1024 * 1024 * 1024) { - return (size / 1024.0 / 1024.0).toFixed(1) + " MB"; - } else { - return (size / 1024.0 / 1024.0 / 1024.0).toFixed(1) + " GB"; - } - }, - getTarget: function (request, params) { - var target = $.getOpt("target"); - - if (request === "test" && $.getOpt("testTarget")) { - target = $.getOpt("testTarget") === "/" ? $.getOpt("target") : $.getOpt("testTarget"); - } - - if (typeof target === "function") { - return target(params); - } - - var separator = target.indexOf("?") < 0 ? "?" : "&"; - var joinedParams = params.join("&"); - - if (joinedParams) target = target + separator + joinedParams; - - return target; - }, - }; - - var onDrop = function (e) { - e.currentTarget.classList.remove($.getOpt("dragOverClass")); - $h.stopEvent(e); - - //handle dropped things as items if we can (this lets us deal with folders nicer in some cases) - if (e.dataTransfer && e.dataTransfer.items) { - loadFiles(e.dataTransfer.items, e); - } - //else handle them as files - else if (e.dataTransfer && e.dataTransfer.files) { - loadFiles(e.dataTransfer.files, e); - } - }; - var onDragLeave = function (e) { - e.currentTarget.classList.remove($.getOpt("dragOverClass")); - }; - var onDragOverEnter = function (e) { - e.preventDefault(); - var dt = e.dataTransfer; - if ($.indexOf(dt.types, "Files") >= 0) { - // only for file drop - e.stopPropagation(); - dt.dropEffect = "copy"; - dt.effectAllowed = "copy"; - e.currentTarget.classList.add($.getOpt("dragOverClass")); - } else { - // not work on IE/Edge.... - dt.dropEffect = "none"; - dt.effectAllowed = "none"; - } - }; - - /** - * processes a single upload item (file or directory) - * @param {Object} item item to upload, may be file or directory entry - * @param {string} path current file path - * @param {File[]} items list of files to append new items to - * @param {Function} cb callback invoked when item is processed - */ - function processItem(item, path, items, cb) { - var entry; - if (item.isFile) { - // file provided - return item.file(function (file) { - file.relativePath = path + file.name; - items.push(file); - cb(); - }); - } else if (item.isDirectory) { - // item is already a directory entry, just assign - entry = item; - } else if (item instanceof File) { - items.push(item); - } - if ("function" === typeof item.webkitGetAsEntry) { - // get entry from file object - entry = item.webkitGetAsEntry(); - } - if (entry && entry.isDirectory) { - // directory provided, process it - return processDirectory(entry, path + entry.name + "/", items, cb); - } - if ("function" === typeof item.getAsFile) { - // item represents a File object, convert it - item = item.getAsFile(); - if (item instanceof File) { - item.relativePath = path + item.name; - items.push(item); - } - } - cb(); // indicate processing is done - } - - /** - * cps-style list iteration. - * invokes all functions in list and waits for their callback to be - * triggered. - * @param {Function[]} items list of functions expecting callback parameter - * @param {Function} cb callback to trigger after the last callback has been invoked - */ - function processCallbacks(items, cb) { - if (!items || items.length === 0) { - // empty or no list, invoke callback - return cb(); - } - // invoke current function, pass the next part as continuation - items[0](function () { - processCallbacks(items.slice(1), cb); - }); - } - - /** - * recursively traverse directory and collect files to upload - * @param {Object} directory directory to process - * @param {string} path current path - * @param {File[]} items target list of items - * @param {Function} cb callback invoked after traversing directory - */ - function processDirectory(directory, path, items, cb) { - var dirReader = directory.createReader(); - var allEntries = []; - - function readEntries() { - dirReader.readEntries(function (entries) { - if (entries.length) { - allEntries = allEntries.concat(entries); - return readEntries(); - } - - // process all conversion callbacks, finally invoke own one - processCallbacks( - allEntries.map(function (entry) { - // bind all properties except for callback - return processItem.bind(null, entry, path, items); - }), - cb, - ); - }); - } - - readEntries(); - } - - /** - * process items to extract files to be uploaded - * @param {File[]} items items to process - * @param {Event} event event that led to upload - */ - function loadFiles(items, event) { - if (!items.length) { - return; // nothing to do - } - $.fire("beforeAdd"); - var files = []; - processCallbacks( - Array.prototype.map.call(items, function (item) { - // bind all properties except for callback - var entry = item; - if ("function" === typeof item.webkitGetAsEntry) { - entry = item.webkitGetAsEntry(); - } - return processItem.bind(null, entry, "", files); - }), - function () { - if (files.length) { - // at least one file found - appendFilesFromFileList(files, event); - } - }, - ); - } - - var appendFilesFromFileList = function (fileList, event) { - // check for uploading too many files - var errorCount = 0; - var o = $.getOpt([ - "maxFiles", - "minFileSize", - "maxFileSize", - "maxFilesErrorCallback", - "minFileSizeErrorCallback", - "maxFileSizeErrorCallback", - "fileType", - "fileTypeErrorCallback", - ]); - if (typeof o.maxFiles !== "undefined" && o.maxFiles < fileList.length + $.files.length) { - // if single-file upload, file is already added, and trying to add 1 new file, simply replace the already-added file - if (o.maxFiles === 1 && $.files.length === 1 && fileList.length === 1) { - $.removeFile($.files[0]); - } else { - o.maxFilesErrorCallback(fileList, errorCount++); - return false; - } - } - var files = [], - filesSkipped = [], - remaining = fileList.length; - var decreaseReamining = function () { - if (!--remaining) { - // all files processed, trigger event - if (!files.length && !filesSkipped.length) { - // no succeeded files, just skip - return; - } - window.setTimeout(function () { - $.fire("filesAdded", files, filesSkipped); - }, 0); - } - }; - $h.each(fileList, function (file) { - var fileName = file.name; - var fileType = file.type; // e.g video/mp4 - if (o.fileType.length > 0) { - var fileTypeFound = false; - for (var index in o.fileType) { - // For good behaviour we do some inital sanitizing. Remove spaces and lowercase all - o.fileType[index] = o.fileType[index].replace(/\s/g, "").toLowerCase(); - - // Allowing for both [extension, .extension, mime/type, mime/*] - var extension = (o.fileType[index].match(/^[^.][^/]+$/) ? "." : "") + o.fileType[index]; - - if ( - fileName.substr(-1 * extension.length).toLowerCase() === extension || - //If MIME type, check for wildcard or if extension matches the files tiletype - (extension.indexOf("/") !== -1 && - ((extension.indexOf("*") !== -1 && - fileType.substr(0, extension.indexOf("*")) === extension.substr(0, extension.indexOf("*"))) || - fileType === extension)) - ) { - fileTypeFound = true; - break; - } - } - if (!fileTypeFound) { - o.fileTypeErrorCallback(file, errorCount++); - return true; - } - } - - if (typeof o.minFileSize !== "undefined" && file.size < o.minFileSize) { - o.minFileSizeErrorCallback(file, errorCount++); - return true; - } - if (typeof o.maxFileSize !== "undefined" && file.size > o.maxFileSize) { - o.maxFileSizeErrorCallback(file, errorCount++); - return true; - } - - function addFile(uniqueIdentifier) { - if (!$.getFromUniqueIdentifier(uniqueIdentifier)) { - (function () { - file.uniqueIdentifier = uniqueIdentifier; - var f = new ResumableFile($, file, uniqueIdentifier); - $.files.push(f); - files.push(f); - f.container = typeof event != "undefined" ? event.srcElement : null; - window.setTimeout(function () { - $.fire("fileAdded", f, event); - }, 0); - })(); - } else { - filesSkipped.push(file); - } - decreaseReamining(); - } - // directories have size == 0 - var uniqueIdentifier = $h.generateUniqueIdentifier(file, event); - if (uniqueIdentifier && typeof uniqueIdentifier.then === "function") { - // Promise or Promise-like object provided as unique identifier - uniqueIdentifier.then( - function (uniqueIdentifier) { - // unique identifier generation succeeded - addFile(uniqueIdentifier); - }, - function () { - // unique identifier generation failed - // skip further processing, only decrease file count - decreaseReamining(); - }, - ); - } else { - // non-Promise provided as unique identifier, process synchronously - addFile(uniqueIdentifier); - } - }); - }; - - // INTERNAL OBJECT TYPES - function ResumableFile(resumableObj, file, uniqueIdentifier) { - var $ = this; - $.opts = {}; - $.getOpt = resumableObj.getOpt; - $._prevProgress = 0; - $.resumableObj = resumableObj; - $.file = file; - $.fileName = file.fileName || file.name; // Some confusion in different versions of Firefox - $.size = file.size; - $.relativePath = file.relativePath || file.webkitRelativePath || $.fileName; - $.uniqueIdentifier = uniqueIdentifier; - $._pause = false; - $.container = ""; - $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished - var _error = uniqueIdentifier !== undefined; - - // Callback when something happens within the chunk - var chunkEvent = function (event, message) { - // event can be 'progress', 'success', 'error' or 'retry' - switch (event) { - case "progress": - $.resumableObj.fire("fileProgress", $, message); - break; - case "error": - $.abort(); - _error = true; - $.chunks = []; - $.resumableObj.fire("fileError", $, message); - break; - case "success": - if (_error) return; - $.resumableObj.fire("fileProgress", $, message); // it's at least progress - if ($.isComplete()) { - $.resumableObj.fire("fileSuccess", $, message); - } - break; - case "retry": - $.resumableObj.fire("fileRetry", $); - break; - } - }; - - // Main code to set up a file object with chunks, - // packaged to be able to handle retries if needed. - $.chunks = []; - $.abort = function () { - // Stop current uploads - var abortCount = 0; - $h.each($.chunks, function (c) { - if (c.status() == "uploading") { - c.abort(); - abortCount++; - } - }); - if (abortCount > 0) $.resumableObj.fire("fileProgress", $); - }; - $.cancel = function () { - // Reset this file to be void - var _chunks = $.chunks; - $.chunks = []; - // Stop current uploads - $h.each(_chunks, function (c) { - if (c.status() == "uploading") { - c.abort(); - $.resumableObj.uploadNextChunk(); - } - }); - $.resumableObj.removeFile($); - $.resumableObj.fire("fileProgress", $); - }; - $.retry = function () { - $.bootstrap(); - var firedRetry = false; - $.resumableObj.on("chunkingComplete", function () { - if (!firedRetry) $.resumableObj.upload(); - firedRetry = true; - }); - }; - $.bootstrap = function () { - $.abort(); - _error = false; - // Rebuild stack of chunks from file - $.chunks = []; - $._prevProgress = 0; - var round = $.getOpt("forceChunkSize") ? Math.ceil : Math.floor; - var maxOffset = Math.max(round($.file.size / $.getOpt("chunkSize")), 1); - for (var offset = 0; offset < maxOffset; offset++) { - (function (offset) { - $.chunks.push(new ResumableChunk($.resumableObj, $, offset, chunkEvent)); - $.resumableObj.fire("chunkingProgress", $, offset / maxOffset); - })(offset); - } - window.setTimeout(function () { - $.resumableObj.fire("chunkingComplete", $); - }, 0); - }; - $.progress = function () { - if (_error) return 1; - // Sum up progress across everything - var ret = 0; - var error = false; - $h.each($.chunks, function (c) { - if (c.status() == "error") error = true; - ret += c.progress(true); // get chunk progress relative to entire file - }); - ret = error ? 1 : ret > 0.99999 ? 1 : ret; - ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused - $._prevProgress = ret; - return ret; - }; - $.isUploading = function () { - var uploading = false; - $h.each($.chunks, function (chunk) { - if (chunk.status() == "uploading") { - uploading = true; - return false; - } - }); - return uploading; - }; - $.isComplete = function () { - var outstanding = false; - if ($.preprocessState === 1) { - return false; - } - $h.each($.chunks, function (chunk) { - var status = chunk.status(); - if (status == "pending" || status == "uploading" || chunk.preprocessState === 1) { - outstanding = true; - return false; - } - }); - return !outstanding; - }; - $.pause = function (pause) { - if (typeof pause === "undefined") { - $._pause = $._pause ? false : true; - } else { - $._pause = pause; - } - }; - $.isPaused = function () { - return $._pause; - }; - $.preprocessFinished = function () { - $.preprocessState = 2; - $.upload(); - }; - $.upload = function () { - var found = false; - if ($.isPaused() === false) { - var preprocess = $.getOpt("preprocessFile"); - if (typeof preprocess === "function") { - switch ($.preprocessState) { - case 0: - $.preprocessState = 1; - preprocess($); - return true; - case 1: - return true; - case 2: - break; - } - } - $h.each($.chunks, function (chunk) { - if (chunk.status() == "pending" && chunk.preprocessState !== 1) { - chunk.send(); - found = true; - return false; - } - }); - } - return found; - }; - $.markChunksCompleted = function (chunkNumber) { - if (!$.chunks || $.chunks.length <= chunkNumber) { - return; - } - for (var num = 0; num < chunkNumber; num++) { - $.chunks[num].markComplete = true; - } - }; - - // Bootstrap and return - $.resumableObj.fire("chunkingStart", $); - $.bootstrap(); - return this; - } - - function ResumableChunk(resumableObj, fileObj, offset, callback) { - var $ = this; - $.opts = {}; - $.getOpt = resumableObj.getOpt; - $.resumableObj = resumableObj; - $.fileObj = fileObj; - $.fileObjSize = fileObj.size; - $.fileObjType = fileObj.file.type; - $.offset = offset; - $.callback = callback; - $.lastProgressCallback = new Date(); - $.tested = false; - $.retries = 0; - $.pendingRetry = false; - $.preprocessState = 0; // 0 = unprocessed, 1 = processing, 2 = finished - $.markComplete = false; - - // Computed properties - var chunkSize = $.getOpt("chunkSize"); - $.loaded = 0; - $.startByte = $.offset * chunkSize; - $.endByte = Math.min($.fileObjSize, ($.offset + 1) * chunkSize); - if ($.fileObjSize - $.endByte < chunkSize && !$.getOpt("forceChunkSize")) { - // The last chunk will be bigger than the chunk size, but less than 2*chunkSize - $.endByte = $.fileObjSize; - } - $.xhr = null; - - // test() makes a GET request without any data to see if the chunk has already been uploaded in a previous session - $.test = function () { - // Set up request and listen for event - $.xhr = new XMLHttpRequest(); - - var testHandler = function (e) { - $.tested = true; - var status = $.status(); - if (status == "success") { - $.callback(status, $.message()); - $.resumableObj.uploadNextChunk(); - } else { - $.send(); - } - }; - $.xhr.addEventListener("load", testHandler, false); - $.xhr.addEventListener("error", testHandler, false); - $.xhr.addEventListener("timeout", testHandler, false); - - // Add data from the query options - var params = []; - var parameterNamespace = $.getOpt("parameterNamespace"); - var customQuery = $.getOpt("query"); - if (typeof customQuery == "function") customQuery = customQuery($.fileObj, $); - $h.each(customQuery, function (k, v) { - params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join("=")); - }); - // Add extra data to identify chunk - params = params.concat( - [ - // define key/value pairs for additional parameters - ["chunkNumberParameterName", $.offset + 1], - ["chunkSizeParameterName", $.getOpt("chunkSize")], - ["currentChunkSizeParameterName", $.endByte - $.startByte], - ["totalSizeParameterName", $.fileObjSize], - ["typeParameterName", $.fileObjType], - ["identifierParameterName", $.fileObj.uniqueIdentifier], - ["fileNameParameterName", $.fileObj.fileName], - ["relativePathParameterName", $.fileObj.relativePath], - ["totalChunksParameterName", $.fileObj.chunks.length], - ] - .filter(function (pair) { - // include items that resolve to truthy values - // i.e. exclude false, null, undefined and empty strings - return $.getOpt(pair[0]); - }) - .map(function (pair) { - // map each key/value pair to its final form - return [parameterNamespace + $.getOpt(pair[0]), encodeURIComponent(pair[1])].join("="); - }), - ); - // Append the relevant chunk and send it - $.xhr.open($.getOpt("testMethod"), $h.getTarget("test", params)); - $.xhr.timeout = $.getOpt("xhrTimeout"); - $.xhr.withCredentials = $.getOpt("withCredentials"); - // Add data from header options - var customHeaders = $.getOpt("headers"); - if (typeof customHeaders === "function") { - customHeaders = customHeaders($.fileObj, $); - } - $h.each(customHeaders, function (k, v) { - $.xhr.setRequestHeader(k, v); - }); - $.xhr.send(null); - }; - - $.preprocessFinished = function () { - $.preprocessState = 2; - $.send(); - }; - - // send() uploads the actual data in a POST call - $.send = function () { - var preprocess = $.getOpt("preprocess"); - if (typeof preprocess === "function") { - switch ($.preprocessState) { - case 0: - $.preprocessState = 1; - preprocess($); - return; - case 1: - return; - case 2: - break; - } - } - if ($.getOpt("testChunks") && !$.tested) { - $.test(); - return; - } - - // Set up request and listen for event - $.xhr = new XMLHttpRequest(); - - // Progress - $.xhr.upload.addEventListener( - "progress", - function (e) { - if (new Date() - $.lastProgressCallback > $.getOpt("throttleProgressCallbacks") * 1000) { - $.callback("progress"); - $.lastProgressCallback = new Date(); - } - $.loaded = e.loaded || 0; - }, - false, - ); - $.loaded = 0; - $.pendingRetry = false; - $.callback("progress"); - - // Done (either done, failed or retry) - var doneHandler = function (e) { - var status = $.status(); - if (status == "success" || status == "error") { - $.callback(status, $.message()); - $.resumableObj.uploadNextChunk(); - } else { - $.callback("retry", $.message()); - $.abort(); - $.retries++; - var retryInterval = $.getOpt("chunkRetryInterval"); - if (retryInterval !== undefined) { - $.pendingRetry = true; - setTimeout($.send, retryInterval); - } else { - $.send(); - } - } - }; - $.xhr.addEventListener("load", doneHandler, false); - $.xhr.addEventListener("error", doneHandler, false); - $.xhr.addEventListener("timeout", doneHandler, false); - - // Set up the basic query data from Resumable - var query = [ - ["chunkNumberParameterName", $.offset + 1], - ["chunkSizeParameterName", $.getOpt("chunkSize")], - ["currentChunkSizeParameterName", $.endByte - $.startByte], - ["totalSizeParameterName", $.fileObjSize], - ["typeParameterName", $.fileObjType], - ["identifierParameterName", $.fileObj.uniqueIdentifier], - ["fileNameParameterName", $.fileObj.fileName], - ["relativePathParameterName", $.fileObj.relativePath], - ["totalChunksParameterName", $.fileObj.chunks.length], - ] - .filter(function (pair) { - // include items that resolve to truthy values - // i.e. exclude false, null, undefined and empty strings - return $.getOpt(pair[0]); - }) - .reduce(function (query, pair) { - // assign query key/value - query[$.getOpt(pair[0])] = pair[1]; - return query; - }, {}); - // Mix in custom data - var customQuery = $.getOpt("query"); - if (typeof customQuery == "function") customQuery = customQuery($.fileObj, $); - $h.each(customQuery, function (k, v) { - query[k] = v; - }); - - var func = $.fileObj.file.slice - ? "slice" - : $.fileObj.file.mozSlice - ? "mozSlice" - : $.fileObj.file.webkitSlice - ? "webkitSlice" - : "slice"; - var bytes = $.fileObj.file[func]( - $.startByte, - $.endByte, - $.getOpt("setChunkTypeFromFile") ? $.fileObj.file.type : "", - ); - var data = null; - var params = []; - - var parameterNamespace = $.getOpt("parameterNamespace"); - if ($.getOpt("method") === "octet") { - // Add data from the query options - data = bytes; - $h.each(query, function (k, v) { - params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join("=")); - }); - } else { - // Add data from the query options - data = new FormData(); - $h.each(query, function (k, v) { - data.append(parameterNamespace + k, v); - params.push([encodeURIComponent(parameterNamespace + k), encodeURIComponent(v)].join("=")); - }); - if ($.getOpt("chunkFormat") == "blob") { - data.append(parameterNamespace + $.getOpt("fileParameterName"), bytes, $.fileObj.fileName); - } else if ($.getOpt("chunkFormat") == "base64") { - var fr = new FileReader(); - fr.onload = function (e) { - data.append(parameterNamespace + $.getOpt("fileParameterName"), fr.result); - $.xhr.send(data); - }; - fr.readAsDataURL(bytes); - } - } - - var target = $h.getTarget("upload", params); - var method = $.getOpt("uploadMethod"); - - $.xhr.open(method, target); - if ($.getOpt("method") === "octet") { - $.xhr.setRequestHeader("Content-Type", "application/octet-stream"); - } - $.xhr.timeout = $.getOpt("xhrTimeout"); - $.xhr.withCredentials = $.getOpt("withCredentials"); - // Add data from header options - var customHeaders = $.getOpt("headers"); - if (typeof customHeaders === "function") { - customHeaders = customHeaders($.fileObj, $); - } - - $h.each(customHeaders, function (k, v) { - $.xhr.setRequestHeader(k, v); - }); - - if ($.getOpt("chunkFormat") == "blob") { - $.xhr.send(data); - } - }; - $.abort = function () { - // Abort and reset - if ($.xhr) $.xhr.abort(); - $.xhr = null; - }; - $.status = function () { - // Returns: 'pending', 'uploading', 'success', 'error' - if ($.pendingRetry) { - // if pending retry then that's effectively the same as actively uploading, - // there might just be a slight delay before the retry starts - return "uploading"; - } else if ($.markComplete) { - return "success"; - } else if (!$.xhr) { - return "pending"; - } else if ($.xhr.readyState < 4) { - // Status is really 'OPENED', 'HEADERS_RECEIVED' or 'LOADING' - meaning that stuff is happening - return "uploading"; - } else { - if ($.xhr.status == 200 || $.xhr.status == 201) { - // HTTP 200, 201 (created) - return "success"; - } else if ( - $h.contains($.getOpt("permanentErrors"), $.xhr.status) || - $.retries >= $.getOpt("maxChunkRetries") - ) { - // HTTP 400, 404, 409, 415, 500, 501 (permanent error) - return "error"; - } else { - // this should never happen, but we'll reset and queue a retry - // a likely case for this would be 503 service unavailable - $.abort(); - return "pending"; - } - } - }; - $.message = function () { - return $.xhr ? $.xhr.responseText : ""; - }; - $.progress = function (relative) { - if (typeof relative === "undefined") relative = false; - var factor = relative ? ($.endByte - $.startByte) / $.fileObjSize : 1; - if ($.pendingRetry) return 0; - if ((!$.xhr || !$.xhr.status) && !$.markComplete) factor *= 0.95; - var s = $.status(); - switch (s) { - case "success": - case "error": - return 1 * factor; - case "pending": - return 0 * factor; - default: - return ($.loaded / ($.endByte - $.startByte)) * factor; - } - }; - return this; - } - - // QUEUE - $.uploadNextChunk = function () { - var found = false; - - // In some cases (such as videos) it's really handy to upload the first - // and last chunk of a file quickly; this let's the server check the file's - // metadata and determine if there's even a point in continuing. - if ($.getOpt("prioritizeFirstAndLastChunk")) { - $h.each($.files, function (file) { - if (file.chunks.length && file.chunks[0].status() == "pending" && file.chunks[0].preprocessState === 0) { - file.chunks[0].send(); - found = true; - return false; - } - if ( - file.chunks.length > 1 && - file.chunks[file.chunks.length - 1].status() == "pending" && - file.chunks[file.chunks.length - 1].preprocessState === 0 - ) { - file.chunks[file.chunks.length - 1].send(); - found = true; - return false; - } - }); - if (found) return true; - } - - // Now, simply look for the next, best thing to upload - $h.each($.files, function (file) { - found = file.upload(); - if (found) return false; - }); - if (found) return true; - - // The are no more outstanding chunks to upload, check is everything is done - var outstanding = false; - $h.each($.files, function (file) { - if (!file.isComplete()) { - outstanding = true; - return false; - } - }); - if (!outstanding) { - // All chunks have been uploaded, complete - $.fire("complete"); - } - return false; - }; - - // PUBLIC METHODS FOR RESUMABLE.JS - $.assignBrowse = function (domNodes, isDirectory) { - if (typeof domNodes.length == "undefined") domNodes = [domNodes]; - $h.each(domNodes, function (domNode) { - var input; - if (domNode.tagName === "INPUT" && domNode.type === "file") { - input = domNode; - } else { - input = document.createElement("input"); - input.setAttribute("type", "file"); - input.style.display = "none"; - domNode.addEventListener( - "click", - function () { - input.style.opacity = 0; - input.style.display = "block"; - input.focus(); - input.click(); - input.style.display = "none"; - }, - false, - ); - domNode.appendChild(input); - } - var maxFiles = $.getOpt("maxFiles"); - if (typeof maxFiles === "undefined" || maxFiles != 1) { - input.setAttribute("multiple", "multiple"); - } else { - input.removeAttribute("multiple"); - } - if (isDirectory) { - input.setAttribute("webkitdirectory", "webkitdirectory"); - } else { - input.removeAttribute("webkitdirectory"); - } - var fileTypes = $.getOpt("fileType"); - if (typeof fileTypes !== "undefined" && fileTypes.length >= 1) { - input.setAttribute( - "accept", - fileTypes - .map(function (e) { - e = e.replace(/\s/g, "").toLowerCase(); - if (e.match(/^[^.][^/]+$/)) { - e = "." + e; - } - return e; - }) - .join(","), - ); - } else { - input.removeAttribute("accept"); - } - // When new files are added, simply append them to the overall list - input.addEventListener( - "change", - function (e) { - appendFilesFromFileList(e.target.files, e); - var clearInput = $.getOpt("clearInput"); - if (clearInput) { - e.target.value = ""; - } - }, - false, - ); - }); - }; - $.assignDrop = function (domNodes) { - if (typeof domNodes.length == "undefined") domNodes = [domNodes]; - - $h.each(domNodes, function (domNode) { - domNode.addEventListener("dragover", onDragOverEnter, false); - domNode.addEventListener("dragenter", onDragOverEnter, false); - domNode.addEventListener("dragleave", onDragLeave, false); - domNode.addEventListener("drop", onDrop, false); - }); - }; - $.unAssignDrop = function (domNodes) { - if (typeof domNodes.length == "undefined") domNodes = [domNodes]; - - $h.each(domNodes, function (domNode) { - domNode.removeEventListener("dragover", onDragOverEnter); - domNode.removeEventListener("dragenter", onDragOverEnter); - domNode.removeEventListener("dragleave", onDragLeave); - domNode.removeEventListener("drop", onDrop); - }); - }; - $.isUploading = function () { - var uploading = false; - $h.each($.files, function (file) { - if (file.isUploading()) { - uploading = true; - return false; - } - }); - return uploading; - }; - $.upload = function () { - // Make sure we don't start too many uploads at once - if ($.isUploading()) return; - // Kick off the queue - $.fire("uploadStart"); - for (var num = 1; num <= $.getOpt("simultaneousUploads"); num++) { - $.uploadNextChunk(); - } - }; - $.pause = function () { - // Resume all chunks currently being uploaded - $h.each($.files, function (file) { - file.abort(); - }); - $.fire("pause"); - }; - $.cancel = function () { - $.fire("beforeCancel"); - for (var i = $.files.length - 1; i >= 0; i--) { - $.files[i].cancel(); - } - $.fire("cancel"); - }; - $.progress = function () { - var totalDone = 0; - var totalSize = 0; - // Resume all chunks currently being uploaded - $h.each($.files, function (file) { - totalDone += file.progress() * file.size; - totalSize += file.size; - }); - return totalSize > 0 ? totalDone / totalSize : 0; - }; - $.addFile = function (file, event) { - appendFilesFromFileList([file], event); - }; - $.addFiles = function (files, event) { - appendFilesFromFileList(files, event); - }; - $.removeFile = function (file) { - for (var i = $.files.length - 1; i >= 0; i--) { - if ($.files[i] === file) { - $.files.splice(i, 1); - } - } - }; - $.getFromUniqueIdentifier = function (uniqueIdentifier) { - var ret = false; - $h.each($.files, function (f) { - if (f.uniqueIdentifier == uniqueIdentifier) ret = f; - }); - return ret; - }; - $.getSize = function () { - var totalSize = 0; - $h.each($.files, function (file) { - totalSize += file.size; - }); - return totalSize; - }; - $.handleDropEvent = function (e) { - onDrop(e); - }; - $.handleChangeEvent = function (e) { - appendFilesFromFileList(e.target.files, e); - e.target.value = ""; - }; - $.updateQuery = function (query) { - $.opts.query = query; - }; - - return this; - }; - - // Node.js-style export for Node and Component - if (typeof module != "undefined") { - // left here for backwards compatibility - module.exports = Resumable; - module.exports.Resumable = Resumable; - } else if (typeof define === "function" && define.amd) { - // AMD/requirejs: Define the module - define(function () { - return Resumable; - }); - } else { - // Browser: Expose to window - window.Resumable = Resumable; - } -})(); diff --git a/client/src/jsconfig.json b/client/src/jsconfig.json deleted file mode 100644 index 2f547e4..0000000 --- a/client/src/jsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@wp/pages": ["./pages/_index.js"], - "@wp/components": ["./pages/components/_index.js"], - "@wp/env/*": ["./env/*"], - "@wp/lib/*": ["./lib/*"], - "@wp/js/*": ["./js/*"], - "@wp/css/*": ["./css/*"], - "@wp/assets/*": ["./assets/*"], - "@wp/api": ["./api/_index.js"], - "@wp/utils": ["./utils/_index.js"] - }, - "target": "es6", - "module": "es6", - "lib": ["dom", "dom.iterable"], - "moduleResolution": "node", - "allowSyntheticDefaultImports": true - } -} diff --git a/client/src/lib/_index.js b/client/src/lib/_index.js deleted file mode 100644 index 4e67407..0000000 --- a/client/src/lib/_index.js +++ /dev/null @@ -1 +0,0 @@ -export { validateImportKey } from "./imports/_index.js"; diff --git a/client/src/lib/api/error-v2.ts b/client/src/lib/api/error-v2.ts new file mode 100644 index 0000000..e092c33 --- /dev/null +++ b/client/src/lib/api/error-v2.ts @@ -0,0 +1,42 @@ +import { InvalidErrorType } from "#lib/errors"; +import { IResponseError } from "./types"; + +interface IAPIV2Error extends Error { + pathSpec: string; + request: Request; + response: Response; + error?: IResponseError; +} + +interface IAPIV2ErrorOptions extends ErrorOptions { + pathSpec: string; + request: Request; + response: Response; + error?: IResponseError; +} + +export class APIV2Error extends Error implements IAPIV2Error { + pathSpec: string; + request: Request; + response: Response; + error?: IResponseError; + + constructor(message: string, options: IAPIV2ErrorOptions) { + super(message); + + this.pathSpec = options.pathSpec; + this.request = options.request; + this.response = options.response; + this.error = options.error; + } +} + +export function isAPIV2Error(input: unknown): input is APIV2Error { + return input instanceof APIV2Error; +} + +export function ensureAPIV2Error(input: unknown): asserts input is APIV2Error { + if (!isAPIV2Error(input)) { + throw new InvalidErrorType(input); + } +} diff --git a/client/src/lib/api/error.ts b/client/src/lib/api/error.ts new file mode 100644 index 0000000..da2ac38 --- /dev/null +++ b/client/src/lib/api/error.ts @@ -0,0 +1,33 @@ +import { InvalidErrorType } from "#lib/errors"; + +interface IAPIError extends Error { + request: Request; + response: Response; +} + +interface IAPIErrorOptions extends ErrorOptions { + request: Request; + response: Response; +} + +export class APIError extends Error implements IAPIError { + request: Request; + response: Response; + + constructor(message: string, options: IAPIErrorOptions) { + super(message); + + this.request = options.request; + this.response = options.response; + } +} + +export function isAPIError(input: unknown): input is APIError { + return input instanceof APIError; +} + +export function ensureAPIError(input: unknown): asserts input is APIError { + if (!isAPIError(input)) { + throw new InvalidErrorType(input); + } +} diff --git a/client/src/lib/api/fetch.ts b/client/src/lib/api/fetch.ts new file mode 100644 index 0000000..230c1b1 --- /dev/null +++ b/client/src/lib/api/fetch.ts @@ -0,0 +1,149 @@ +import { logoutAccount } from "#entities/account"; +import { HTTP_STATUS, mergeHeaders } from "#lib/http"; +import { customFetch } from "#lib/fetch"; +import { APIError } from "#lib/api"; + +const urlBase = `/api/v1`; +const jsonHeaders = new Headers(); +jsonHeaders.append("Content-Type", "application/json"); + +/** + * TODO: discriminated union with JSONable body signature + */ +interface IOptions extends Omit { + method: "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "CONNECT" | "OPTIONS" | "TRACE" | "PATCH"; + body?: any; + headers?: Headers; +} + +// TODO: Make this not throw. +/** + * Generic request for Kemono API. + * @param path + * A path to the endpoint, realtive to the base API path. + */ +export async function apiFetch( + path: string, + options: IOptions, + searchParams?: URLSearchParams +): Promise { + // `URL` constructor requires a full origin + // to be present in either of arguments + // but the argument for `fetch()` accepts relative paths just fine + // so we are doing some gymnastics in order not to depend + // on browser context (does not exist on server) + // or an env variable (not needed if the origin is the same). + const url = new URL(`${urlBase}${path}`, "https://example.com"); + url.search = !searchParams ? "" : String(searchParams); + + url.searchParams.sort(); + + const apiPath = `${url.pathname}${ + // `URL.search` param includes `?` even with no params + // so we include it conditionally + searchParams?.size !== 0 ? url.search : "" + }`; + + let finalOptions: RequestInit; + { + if (!options.body) { + finalOptions = { + ...options, + credentials: "same-origin", + }; + } else { + const jsonBody = JSON.stringify(options.body); + finalOptions = { + ...options, + headers: options.headers + ? mergeHeaders(options.headers, jsonHeaders) + : jsonHeaders, + body: jsonBody, + credentials: "same-origin", + }; + } + } + const request = new Request(apiPath, finalOptions); + const response = await customFetch(request); + + if (!response.ok) { + // server logged the account out + if (response.status === 401) { + await logoutAccount(true); + + throw new APIError( + `Failed to fetch from API due to lack of credentials. Reason: ${response.status} - ${response.statusText}.`, + { request, response } + ); + } + + if (response.status === 400 || response.status === 422) { + let body: string | undefined; + // doing it this way because response doesn't allow + // parsing body several times + // and cloning response is a bit too much + const text = (await response.text()).trim(); + + try { + const json = JSON.parse(text); + body = JSON.stringify(json); + } catch (error) { + body = text; + } + + throw new APIError( + `Failed to fetch from API due to client inputs. Reason: ${ + response.status + } - ${response.statusText}.${!body ? "" : ` ${body}`}`, + { request, response } + ); + } + + if (response.status === 404) { + let body: string | undefined; + // doing it this way because response doesn't allow + // parsing body several times + // and cloning response is a bit too much + const text = (await response.text()).trim(); + + try { + const json = JSON.parse(text); + body = JSON.stringify(json); + } catch (error) { + body = text; + } + + throw new APIError( + `Failed to fetch from API because path "${ + response.url + }" doesn't exist. Reason: ${response.status} - ${response.statusText}.${ + !body ? "" : ` ${body}` + }`, + { request, response } + ); + } + + if (response.status === HTTP_STATUS.SERVICE_UNAVAILABLE) { + throw new APIError("API is in maintenance or not available.", { + request, + response, + }); + } + + if (response.status >= 500) { + throw new APIError("Failed to fetch from API due to server error.", { + request, + response, + }); + } + + throw new APIError("Failed to fetch from API for unknown reasons.", { + request, + response, + }); + } + + const resultBody: ReturnShape = await response.json(); + + return resultBody; +} diff --git a/client/src/lib/api/index.ts b/client/src/lib/api/index.ts new file mode 100644 index 0000000..3e7d77a --- /dev/null +++ b/client/src/lib/api/index.ts @@ -0,0 +1,4 @@ +export { apiFetch } from "./fetch"; +export { APIError, ensureAPIError, isAPIError } from "./error"; +export { apiV2Fetch } from "./v2"; +export { APIV2Error, ensureAPIV2Error, isAPIV2Error } from "./error-v2"; diff --git a/client/src/lib/api/types.ts b/client/src/lib/api/types.ts new file mode 100644 index 0000000..a888a98 --- /dev/null +++ b/client/src/lib/api/types.ts @@ -0,0 +1,25 @@ +export type IMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + +export const requestBodyType = "Sneed's Feed & Seed (formerly Chuck's)"; +export const responseBodyTypeSuccess = "Chuck's Fuck & Suck (formerly Boyle's)"; +export const responseBodyTypeError = "Boyle's Foil & Soil (formerly Sneed's)"; + +export interface IRequestBody { + type: typeof requestBodyType; + data?: DataShape; +} + +export interface IResponseBodySuccess { + type: typeof responseBodyTypeSuccess; + data: DataShape; +} + +export interface IResponseBodyError { + type: typeof responseBodyTypeError; + error: IResponseError; +} + +export interface IResponseError { + type: string; + message?: string; +} diff --git a/client/src/lib/api/v2.ts b/client/src/lib/api/v2.ts new file mode 100644 index 0000000..4b62778 --- /dev/null +++ b/client/src/lib/api/v2.ts @@ -0,0 +1,145 @@ +import { HTTP_STATUS, mergeHeaders } from "#lib/http"; +import { customFetch } from "#lib/fetch"; +import { logoutAccount } from "#entities/account"; +import { APIV2Error } from "./error-v2"; +import { + IMethod, + IRequestBody, + IResponseBodySuccess, + IResponseBodyError, + requestBodyType, + IResponseError, +} from "./types"; + +const urlBase = `/api/v2`; +const jsonHeaders = new Headers(); +jsonHeaders.append("Content-Type", "application/json"); + +interface IOptions extends Omit { + body?: any; + headers?: Headers; + searchParams?: URLSearchParams; +} + +/** + * Generic request for API V2. + * @param pathSpec + * @param method + * @param path + * A path to the endpoint, relative to the base API path. + * @param options + */ +export async function apiV2Fetch( + pathSpec: string, + method: IMethod, + path: string, + options?: IOptions +): Promise { + const searchParams = options?.searchParams; + // `URL` constructor requires a full origin + // to be present in either of arguments + // but the argument for `fetch()` accepts relative paths just fine + // so we are doing some gymnastics in order not to depend + // on browser context (does not exist on server) + // or an env variable (not needed if the origin is the same). + const url = new URL(`${urlBase}${path}`, "https://example.com"); + url.search = !searchParams ? "" : String(searchParams); + + url.searchParams.sort(); + + const apiPath = `${url.pathname}${ + // `URL.search` param includes `?` even with no params + // so we include it conditionally + searchParams?.size !== 0 ? url.search : "" + }`; + + let finalOptions: RequestInit; + { + if (!options?.body) { + finalOptions = { + ...options, + method, + credentials: "same-origin", + }; + } else { + const requestBody = { + type: requestBodyType, + data: options.body, + } satisfies IRequestBody; + const jsonBody = JSON.stringify(requestBody); + finalOptions = { + ...options, + method, + headers: options.headers + ? mergeHeaders(options.headers, jsonHeaders) + : jsonHeaders, + body: jsonBody, + credentials: "same-origin", + }; + } + } + + const request = new Request(apiPath, finalOptions); + const response = await customFetch(request); + + if (!response.ok) { + let error: IResponseError | undefined = undefined; + + // doing it this way because response doesn't allow + // parsing body several times + // and cloning response is a bit too much + const text = (await response.text()).trim(); + + try { + const responseBody: IResponseBodyError = JSON.parse(text); + error = responseBody.error; + } catch (error) {} + + let message: string; + + switch (response.status) { + case HTTP_STATUS.BAD_REQUEST: + case HTTP_STATUS.UNPROCESSABLE_CONTENT: { + message = "Failed to fetch from API due to client inputs."; + break; + } + + case HTTP_STATUS.UNAUTHORIZED: { + await logoutAccount(true); + message = "Failed to fetch from API due to lack of credentials."; + break; + } + + case HTTP_STATUS.NOT_FOUND: { + message = `Failed to fetch from API because path "${response.url} doesn't exist.`; + break; + } + + case HTTP_STATUS.SERVICE_UNAVAILABLE: { + message = "API is in maintenance or not available."; + break; + } + + default: { + message = + response.status >= 500 + ? "Failed to fetch from API due to server error." + : "Failed to fetch from API for unknown reasons."; + break; + } + } + + const errorOptions = { + pathSpec, + request, + response, + error, + } satisfies ConstructorParameters["1"]; + + throw new APIV2Error(message, errorOptions); + } + + const result: IResponseBodySuccess = await response.json(); + + return result.data; +} diff --git a/client/src/lib/errors/error.ts b/client/src/lib/errors/error.ts new file mode 100644 index 0000000..7527883 --- /dev/null +++ b/client/src/lib/errors/error.ts @@ -0,0 +1,11 @@ +import { InvalidErrorType } from "./invalid"; + +export function isError(input: unknown): input is Error { + return input instanceof Error; +} + +export function ensureError(input: unknown): asserts input is Error { + if (!isError(input)) { + throw new InvalidErrorType(input); + } +} diff --git a/client/src/lib/errors/index.ts b/client/src/lib/errors/index.ts new file mode 100644 index 0000000..4b08512 --- /dev/null +++ b/client/src/lib/errors/index.ts @@ -0,0 +1,2 @@ +export { isError, ensureError } from "./error"; +export { InvalidErrorType } from "./invalid"; diff --git a/client/src/lib/errors/invalid.ts b/client/src/lib/errors/invalid.ts new file mode 100644 index 0000000..4473fe5 --- /dev/null +++ b/client/src/lib/errors/invalid.ts @@ -0,0 +1,16 @@ +interface IInvalidErrorType extends Error { + payload: unknown; +} + +/** + * An error for when the value thrown is not a subclass of `Error` class. + */ +export class InvalidErrorType extends Error implements IInvalidErrorType { + payload: unknown; + + constructor(payload: unknown) { + super("Invalid input error type."); + + this.payload = payload; + } +} diff --git a/client/src/lib/fetch/errors.ts b/client/src/lib/fetch/errors.ts new file mode 100644 index 0000000..a16d56e --- /dev/null +++ b/client/src/lib/fetch/errors.ts @@ -0,0 +1,36 @@ +import { InvalidErrorType } from "#lib/errors"; + +interface IFetchError extends Error { + request: Request; +} + +interface IFetchErrorOptions extends ErrorOptions { + request: Request; +} + +/** + * `fetch()` by itself doesn't throw, it only does so due to some client settings + * i.e. improperly formed headers/body/url by the client code + * or browser extensions/adblockers terminating it early. + * + * Since it throws before doing any actual request, the response object is not available to it. + */ +export class FetchError extends Error implements IFetchError { + request: Request; + + constructor(message: string, options: IFetchErrorOptions) { + super(message); + + this.request = options.request; + } +} + +export function isFetchError(input: unknown): input is FetchError { + return input instanceof FetchError; +} + +export function ensureFetchError(input: unknown): asserts input is FetchError { + if (!isFetchError(input)) { + throw new InvalidErrorType(input); + } +} diff --git a/client/src/lib/fetch/fetch.ts b/client/src/lib/fetch/fetch.ts new file mode 100644 index 0000000..8825fec --- /dev/null +++ b/client/src/lib/fetch/fetch.ts @@ -0,0 +1,18 @@ +import { FetchError } from "./errors"; + +export async function customFetch(...args: Parameters) { + const request = new Request(...args); + + + try { + const response = await fetch(request); + + return response; + } catch (error) { + + throw new FetchError("Failed to fetch due to client settings.", { + cause: error, + request, + }); + } +} diff --git a/client/src/lib/fetch/index.ts b/client/src/lib/fetch/index.ts new file mode 100644 index 0000000..b584930 --- /dev/null +++ b/client/src/lib/fetch/index.ts @@ -0,0 +1,2 @@ +export { customFetch } from "./fetch"; +export { FetchError, isFetchError, ensureFetchError } from "./errors"; diff --git a/client/src/lib/http/headers.ts b/client/src/lib/http/headers.ts new file mode 100644 index 0000000..adeed42 --- /dev/null +++ b/client/src/lib/http/headers.ts @@ -0,0 +1,12 @@ +export function mergeHeaders( + firstHeaders: Headers, + secondHeaders: Headers +): Headers { + const resultHeaders = new Headers(firstHeaders); + + secondHeaders.forEach((value, key) => { + resultHeaders.append(key, value); + }); + + return resultHeaders; +} diff --git a/client/src/lib/http/index.ts b/client/src/lib/http/index.ts new file mode 100644 index 0000000..a14dead --- /dev/null +++ b/client/src/lib/http/index.ts @@ -0,0 +1,2 @@ +export { HTTP_STATUS } from "./status"; +export { mergeHeaders } from "./headers"; diff --git a/client/src/lib/http/status.ts b/client/src/lib/http/status.ts new file mode 100644 index 0000000..e20801f --- /dev/null +++ b/client/src/lib/http/status.ts @@ -0,0 +1,66 @@ +export const HTTP_STATUS = { + /** + * The request succeeded. The result and meaning of "success" depends on the HTTP method: + * + * `GET`: The resource has been fetched and transmitted in the message body. + * + * `HEAD`: Representation headers are included in the response without any message body. + * + * `PUT` or `POST`: The resource describing the result of the action is transmitted in the message body. + * + * `TRACE`: The message body contains the request as received by the server. + */ + OK: 200, + /** + * The URL of the requested resource has been changed permanently. The new URL is given in the response. + */ + MOVED_PERMANENTLY: 301, + /** + * This response code means that the URI of requested resource has been changed temporarily. Further changes in the URI might be made in the future, so the same URI should be used by the client in future requests. + */ + FOUND: 302, + /** + * The server sent this response to direct the client to get the requested resource at another URI with a GET request. + */ + SEE_OTHER: 303, + /** + * The server sends this response to direct the client to get the requested resource at another URI with the same method that was used in the prior request. This has the same semantics as the 302 Found response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the redirected request. + */ + TEMPORARY_REDIRECT: 307, + /** + * This means that the resource is now permanently located at another URI, specified by the Location response header. This has the same semantics as the 301 Moved Permanently HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request. + */ + PERMANENT_REDIRECT: 308, + /** + * The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). + */ + BAD_REQUEST: 400, + /** + * Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. + */ + UNAUTHORIZED: 401, + /** + * The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. + */ + FORBIDDEN: 403, + /** + * The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. + */ + NOT_FOUND: 404, + /** + * This response is sent when a request conflicts with the current state of the server. In WebDAV remote web authoring, 409 responses are errors sent to the client so that a user might be able to resolve a conflict and resubmit the request. + */ + CONFLICT: 409, + /** + * The request was well-formed but was unable to be followed due to semantic errors. + */ + UNPROCESSABLE_CONTENT: 422, + /** + * The server has encountered a situation it does not know how to handle. This error is generic, indicating that the server cannot find a more appropriate 5XX status code to respond with. + */ + INTERNAL_SERVER_ERROR: 500, + /** + * The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded. Note that together with this response, a user-friendly page explaining the problem should be sent. This response should be used for temporary conditions and the Retry-After HTTP header should, if possible, contain the estimated time before the recovery of the service. The webmaster must also take care about the caching-related headers that are sent along with this response, as these temporary condition responses should usually not be cached. + */ + SERVICE_UNAVAILABLE: 503, +} as const; diff --git a/client/src/lib/imports/lib.js b/client/src/lib/imports/lib.js deleted file mode 100644 index e2be172..0000000 --- a/client/src/lib/imports/lib.js +++ /dev/null @@ -1,148 +0,0 @@ -import { isLowerCase } from "@wp/utils"; - -/** - * @typedef ValidationResult - * @property {boolean} isValid - * @property {string[]} [errors] - * @property {any} [result] A modified result, if any. - */ - -/** - * @callback KeyValidator - * @param {string} key - * @param {string[]} errors - * @returns {string[]} An array of error messages, if any. - */ - -const maxLength = 1024; - -/** - * @type {Record} - */ -const serviceConstraints = { - patreon: patreonKey, - fanbox: fanboxKey, - gumroad: gumroadKey, - subscribestar: subscribestarKey, - dlsite: dlsiteKey, - discord: discordKey, - fantia: fantiaKey, -}; - -/** - * Validates the key according to these rules: - * - Trim spaces from both sides. - * @param {string} key - * @param {string} service - * @returns {ValidationResult} - */ -export function validateImportKey(key, service) { - const formattedKey = key.trim(); - const errors = serviceConstraints[service](key, []); - - return { - isValid: !errors.length, - errors, - result: formattedKey, - }; -} - -/** - * @type KeyValidator - */ -function patreonKey(key, errors) { - const reqLength = 43; - if (key.length !== reqLength) { - errors.push(`The key length of "${key.length}" is not a valid Patreon key. Required length: "${reqLength}".`); - } - - return errors; -} - -/** - * @type KeyValidator - */ -function fanboxKey(key, errors) { - const pattern = /^\d+_\w+$/i; - - if (key.length > maxLength) { - errors.push(`The key length of "${key.length}" is over the maximum of "${maxLength}".`); - } - - if (!key.match(pattern)) { - errors.push(`The key doesn't match the required pattern of "${String(pattern)}"`); - } - - return errors; -} - -/** - * @type KeyValidator - */ -function fantiaKey(key, errors) { - const reqLengths = [32, 64]; - - if (reqLengths.map((reqLength) => key.length !== reqLength).every((v) => v === false)) { - errors.push( - `The key length of "${key.length}" is not a valid Fantia key. ` + `Accepted lengths: ${reqLengths.join(", ")}.`, - ); - } - - if (!isLowerCase(key)) { - errors.push(`The key is not in lower case.`); - } - - return errors; -} - -/** - * @type KeyValidator - */ -function gumroadKey(key, errors) { - const minLength = 200; - - if (key.length < minLength) { - errors.push(`The key length of "${key.length}" is less than minimum required "${minLength}".`); - } - - if (key.length > maxLength) { - errors.push(`The key length of "${key.length}" is over the maximum of "${maxLength}".`); - } - - return errors; -} - -/** - * @type KeyValidator - */ -function subscribestarKey(key, errors) { - if (key.length > maxLength) { - errors.push(`The key length of "${key.length}" is over the maximum of "${maxLength}".`); - } - - return errors; -} - -/** - * @type KeyValidator - */ -function dlsiteKey(key, errors) { - if (key.length > maxLength) { - errors.push(`The key length of "${key.length}" is over the maximum of "${maxLength}".`); - } - - return errors; -} - -/** - * @type KeyValidator - */ -function discordKey(key, errors) { - const pattern = /(mfa.[a-z0-9_-]{20,})|([a-z0-9_-]{23,28}.[a-z0-9_-]{6,7}.[a-z0-9_-]{27})/i; - - if (!key.match(pattern)) { - errors.push(`The key doesn't match the required pattern of "${String(pattern)}".`); - } - - return errors; -} diff --git a/client/src/lib/numbers/index.ts b/client/src/lib/numbers/index.ts new file mode 100644 index 0000000..03ff98a --- /dev/null +++ b/client/src/lib/numbers/index.ts @@ -0,0 +1,3 @@ +export function parseInt(value: string) { + return Number.parseInt(value, 10); +} diff --git a/client/src/lib/pagination/index.ts b/client/src/lib/pagination/index.ts new file mode 100644 index 0000000..592b16c --- /dev/null +++ b/client/src/lib/pagination/index.ts @@ -0,0 +1,7 @@ +export { + PAGINATION_LIMIT, + parseOffset, + parsePageNumber, + createPagination, +} from "./lib"; +export type { IPagination } from "./types"; diff --git a/client/src/lib/pagination/lib.ts b/client/src/lib/pagination/lib.ts new file mode 100644 index 0000000..dcc1a28 --- /dev/null +++ b/client/src/lib/pagination/lib.ts @@ -0,0 +1,51 @@ +import { IPagination } from "./types"; + +export const PAGINATION_LIMIT = 50; + +export function parseOffset(offset: string | number, limit = PAGINATION_LIMIT) { + const parsedOffset = + typeof offset === "number" ? offset : parseInt(offset.trim(), 10); + + if (parsedOffset % limit !== 0) { + throw new Error(`Offset ${offset} is not a multiple of ${limit}.`); + } + + return parsedOffset; +} + +export function parsePageNumber(page: string | number | undefined): number { + if (page === undefined) { + throw new Error("Page number is required."); + } + + const parsedPage = + typeof page === "number" ? page : Number.parseInt(page.trim(), 10); + + if (parsedPage < 1) { + throw new Error("Page number must be positive."); + } + + return parsedPage; +} + +export function createPagination( + totalCount: number, + currentPage?: number +): IPagination { + const limit = PAGINATION_LIMIT; + const totalPages = Math.ceil(totalCount / limit); + const current = currentPage ?? totalPages; + const currentMin = Math.min((current - 1) * limit + 1, totalCount); + const currentMax = Math.min(currentMin - 1 + limit, totalCount); + + const pagination: IPagination = { + total_count: totalCount, + limit, + total_pages: totalPages, + current_page: current, + current_min: currentMin, + current_max: currentMax, + }; + + return pagination; +} diff --git a/client/src/lib/pagination/types.ts b/client/src/lib/pagination/types.ts new file mode 100644 index 0000000..a3837f8 --- /dev/null +++ b/client/src/lib/pagination/types.ts @@ -0,0 +1,8 @@ +export interface IPagination { + total_count: number; + limit: number; + current_page: number; + total_pages: number; + current_min: number; + current_max: number; +} diff --git a/client/src/lib/types/index.ts b/client/src/lib/types/index.ts new file mode 100644 index 0000000..8164bbd --- /dev/null +++ b/client/src/lib/types/index.ts @@ -0,0 +1,19 @@ +/** + * Stolen from [StackOverflow answer](https://stackoverflow.com/questions/46376468/how-to-get-type-of-array-items). + */ +export type ElementType> = + ContainerType extends Iterable ? ElementType : never; + +export type StrictOmit = Omit; + +/** + * Make selected keys optional. + */ +export type IPartialSome = Omit & + Pick, Key>; + +/** + * Make selected key required. + */ +export type IRequiredSome = Omit & + Pick, Key>; diff --git a/client/src/lib/urls/account.ts b/client/src/lib/urls/account.ts new file mode 100644 index 0000000..2651147 --- /dev/null +++ b/client/src/lib/urls/account.ts @@ -0,0 +1,94 @@ +import { InternalURL } from "./internal-url"; + +export function createAccountPageURL() { + const path = "/account"; + + return new InternalURL(path); +} + +export function createModeratorPageURL() { + const path = `/account/moderator`; + + return new InternalURL(path); +} + +export function createAdministratorPageURL() { + const path = `/account/administrator`; + + return new InternalURL(path); +} + +export function createAccountNotificationsPageURL() { + const path = `/account/notifications`; + + return new InternalURL(path); +} + +export function createAccountImportKeysPageURL() { + const path = `/account/keys`; + + return new InternalURL(path); +} + +export function createAccountDMsReviewPageURL(status?: string) { + const path = `/account/review_dms`; + const params = new URLSearchParams(); + + if (status) { + params.set("status", status); + } + + return new InternalURL(path, params); +} + +export function createAccountPasswordChangePageURL() { + const path = `/account/change_password`; + + return new InternalURL(path); +} + +export function createAccountFavoriteProfilesPageURL( + offset?: number, + sortBy?: string, + order?: string +) { + const path = "/account/favorites/artists"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (sortBy) { + params.set("sort_by", sortBy); + } + + if (order) { + params.set("order", order); + } + + return new InternalURL(path, params); +} + +export function createAccountFavoritePostsPageURL( + offset?: number, + sortBy?: string, + order?: string +) { + const path = "/account/favorites/posts"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (sortBy) { + params.set("sort_by", sortBy); + } + + if (order) { + params.set("order", order); + } + + return new InternalURL(path, params); +} diff --git a/client/src/lib/urls/administrator.ts b/client/src/lib/urls/administrator.ts new file mode 100644 index 0000000..894be06 --- /dev/null +++ b/client/src/lib/urls/administrator.ts @@ -0,0 +1,29 @@ +import { IAccount } from "#entities/account"; +import { InternalURL } from "./internal-url"; + +export function createAccountsPageURL( + page?: number, + name?: string, + role?: string +) { + const path = !page + ? `/account/administrator/accounts` + : `/account/administrator/accounts/${page}`; + const params = new URLSearchParams(); + + if (name) { + params.set("name", name); + } + + if (role) { + params.set("role", role); + } + + return new InternalURL(path, params); +} + +export function createAccountDetailsPageURL(id: IAccount["id"]) { + const path = `/account/administrator/account/${id}`; + + return new InternalURL(path); +} diff --git a/client/src/lib/urls/artists.ts b/client/src/lib/urls/artists.ts new file mode 100644 index 0000000..e659358 --- /dev/null +++ b/client/src/lib/urls/artists.ts @@ -0,0 +1,45 @@ +import { InternalURL } from "./internal-url"; + +export function createArtistsPageURL( + offset?: number, + query?: string, + service?: string, + sortBy?: string, + order?: string +): InternalURL { + const path = "/artists"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (query) { + params.set("q", query); + } + + if (service) { + params.set("service", service); + } + + if (sortBy) { + params.set("sort_by", sortBy); + } + + if (order) { + params.set("order", order); + } + + return new InternalURL(path, params); +} + +export function createArtistsUpdatedPageURL(offset?: number): InternalURL { + const path = "/artists/updated"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + return new InternalURL(path, params); +} diff --git a/client/src/lib/urls/authentication.ts b/client/src/lib/urls/authentication.ts new file mode 100644 index 0000000..2c5cdfc --- /dev/null +++ b/client/src/lib/urls/authentication.ts @@ -0,0 +1,21 @@ +import { InternalURL } from "./internal-url"; + +export function createRegistrationPageURL(location: string) { + const path = `/authentication/register`; + const params = new URLSearchParams([["location", location]]); + + return new InternalURL(path, params); +} + +export function createLoginPageURL(location: string) { + const path = `/authentication/login`; + const params = new URLSearchParams([["location", location]]); + + return new InternalURL(path, params); +} + +export function createLogoutPageURL() { + const path = `/authentication/logout`; + + return new InternalURL(path); +} diff --git a/client/src/lib/urls/dms.ts b/client/src/lib/urls/dms.ts new file mode 100644 index 0000000..78e5e10 --- /dev/null +++ b/client/src/lib/urls/dms.ts @@ -0,0 +1,16 @@ +import { InternalURL } from "./internal-url"; + +export function createDMsPageURL(offset?: number, query?: string) { + const path = "/dms"; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (query) { + params.set("q", String(query)); + } + + return new InternalURL(path, params); +} diff --git a/client/src/lib/urls/documentation.ts b/client/src/lib/urls/documentation.ts new file mode 100644 index 0000000..d758e8b --- /dev/null +++ b/client/src/lib/urls/documentation.ts @@ -0,0 +1,7 @@ +import { InternalURL } from "./internal-url"; + +export function createAPIDocumentationPageURL() { + const path = `/documentation/api`; + + return new InternalURL(path); +} diff --git a/client/src/lib/urls/files.ts b/client/src/lib/urls/files.ts new file mode 100644 index 0000000..1137da3 --- /dev/null +++ b/client/src/lib/urls/files.ts @@ -0,0 +1,35 @@ +import { InternalURL } from "./internal-url"; + +export function createFileURL(hash: string, extension: string) { + const path = `/${hash.slice(0, 2)}/${hash.slice(2, 4)}/${hash}.${extension}`; + + return new InternalURL(path); +} + +export function createThumbnailURL(filePath: string) { + const path = `/thumbnail/data${filePath}`; + + return new InternalURL(path); +} + +export function createFilePageURL(fileHash: string) { + const path = `/file/${fileHash}`; + + return new InternalURL(path); +} + +export function createArchiveFileURL( + archiveHash: string, + archiveExtension: string, + fileName: string, + password?: string +) { + const path = `/archive_files/${archiveHash}${archiveExtension}`; + const params = new URLSearchParams([["file_name", fileName]]); + + if (password) { + params.set("password", password); + } + + return new InternalURL(path, params); +} diff --git a/client/src/lib/urls/importer.ts b/client/src/lib/urls/importer.ts new file mode 100644 index 0000000..3ad1514 --- /dev/null +++ b/client/src/lib/urls/importer.ts @@ -0,0 +1,7 @@ +import { InternalURL } from "./internal-url"; + +export function createImporterStatusPageURL(importID: string) { + const path = `/importer/status/${importID}`; + + return new InternalURL(path); +} diff --git a/client/src/lib/urls/index.ts b/client/src/lib/urls/index.ts new file mode 100644 index 0000000..e66278f --- /dev/null +++ b/client/src/lib/urls/index.ts @@ -0,0 +1,58 @@ +export { createIconURL, createBannerURL } from "./kemono"; +export { + createProfilePageURL, + createProfileFancardsURL, + createProfileAnnouncementsURL, + createProfileTagsURL, + createProfileTagURL, + createProfileDMsURL, + createProfileSharesURL, + createProfileLinksURL, + createProfileNewLinksPageURL, + createProfilesSharesPageURL, + createDiscordChannelPageURL, + createDiscordServerPageURL, + createProfileTagsPageURL +} from "./profiles"; +export { createArtistsPageURL, createArtistsUpdatedPageURL } from "./artists"; +export { + createPostsPageURL, + createPostURL, + createPostRevisionPageURL, + createFileUploadPageURL, + createAttachmentURL, + createPreviewURL, + createPopularPostsPageURL, +} from "./posts"; +export { createTagPageURL } from "./tags"; +export { createSharePageURL, createSharesPageURL } from "./shares"; +export { createDMsPageURL } from "./dms"; +export { + createRegistrationPageURL, + createLoginPageURL, + createLogoutPageURL, +} from "./authentication"; +export { + createAccountPageURL, + createModeratorPageURL, + createAdministratorPageURL, + createAccountNotificationsPageURL, + createAccountImportKeysPageURL, + createAccountDMsReviewPageURL, + createAccountPasswordChangePageURL, + createAccountFavoriteProfilesPageURL, + createAccountFavoritePostsPageURL, +} from "./account"; +export { + createFileURL, + createThumbnailURL, + createFilePageURL, + createArchiveFileURL, +} from "./files"; +export { createImporterStatusPageURL } from "./importer"; +export { + createAccountsPageURL, + createAccountDetailsPageURL, +} from "./administrator"; +export { createProfileLinkRequestsPageURL } from "./moderator"; +export { createAPIDocumentationPageURL } from "./documentation"; diff --git a/client/src/lib/urls/internal-url.ts b/client/src/lib/urls/internal-url.ts new file mode 100644 index 0000000..c95d109 --- /dev/null +++ b/client/src/lib/urls/internal-url.ts @@ -0,0 +1,40 @@ +export class InternalURL extends URL { + constructor( + pathname: string, + searchParams?: URLSearchParams, + fragment?: string + ) { + // `URL` constructor requires a full origin + // to be present in either of arguments + // but in the context of DOM elements the relative URL is just fine + // so we are doing some gymnastics in order not to depend + // on browser context (does not exist on server) + // or an env variable (not needed if the origin is the same). + super(pathname, "https://example.com"); + + if (searchParams && searchParams.size !== 0) { + this.search = searchParams.toString(); + } + + if (fragment) { + this.hash = fragment; + } + } + + toString(): string { + const isParams = this.searchParams.size !== 0; + + if (isParams) { + this.searchParams.sort(); + } + + const params = !isParams ? "" : this.search; + const fragment = this.hash.slice(1).length === 0 ? "" : this.hash; + + return `${this.pathname}${params}${fragment}`; + } + + toJSON(): string { + return this.toString(); + } +} diff --git a/client/src/lib/urls/kemono.ts b/client/src/lib/urls/kemono.ts new file mode 100644 index 0000000..bab3423 --- /dev/null +++ b/client/src/lib/urls/kemono.ts @@ -0,0 +1,13 @@ +import { BANNERS_PREPEND, ICONS_PREPEND } from "#env/env-vars"; + +export function createIconURL(service: string, artistID: string) { + const path = `${ICONS_PREPEND}/icons/${service}/${artistID}`; + + return path; +} + +export function createBannerURL(service: string, artistID: string) { + const path = `${BANNERS_PREPEND}/banners/${service}/${artistID}`; + + return path; +} diff --git a/client/src/lib/urls/moderator.ts b/client/src/lib/urls/moderator.ts new file mode 100644 index 0000000..a148d57 --- /dev/null +++ b/client/src/lib/urls/moderator.ts @@ -0,0 +1,7 @@ +import { InternalURL } from "./internal-url"; + +export function createProfileLinkRequestsPageURL() { + const path = `/account/moderator/tasks/creator_links`; + + return new InternalURL(path); +} diff --git a/client/src/lib/urls/posts.ts b/client/src/lib/urls/posts.ts new file mode 100644 index 0000000..6bc367b --- /dev/null +++ b/client/src/lib/urls/posts.ts @@ -0,0 +1,113 @@ +import { IPopularPostsPeriod } from "#entities/posts"; +import { InternalURL } from "./internal-url"; + +export function createPostsPageURL( + offset?: number, + query?: string, + tags?: string[] +) { + const path = `posts`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + if (query) { + params.set("q", query); + } + + if (tags) { + for (const tag of tags) { + params.set("tag", tag); + } + } + + return new InternalURL(path, params); +} + +export function createPostURL( + service: string, + profileID: string, + postID: string +) { + const path = `/${service}/user/${profileID}/post/${postID}`; + + return new InternalURL(path); +} + +export function createPostRevisionPageURL( + service: string, + profileID: string, + postID: string, + revisionID: string +) { + const path = `/${service}/user/${profileID}/post/${postID}/revision/${revisionID}`; + + return new InternalURL(path); +} + +export function createFileUploadPageURL(service: string, profileID: string) { + const path = "/posts/upload"; + const searchParams = new URLSearchParams([ + ["service", service], + ["user", profileID], + ]); + + return new InternalURL(path, searchParams); +} + +export function createAttachmentURL( + path: string, + name: string, + server?: string +) { + const pathname = `/data${path}`; + const params = new URLSearchParams([["f", name]]); + + if (server) { + const url = new URL(pathname, server); + url.search = String(params); + + return url; + } + + return new InternalURL(pathname, params); +} + +export function createPreviewURL(path: string, name: string, server?: string) { + const pathname = `/data${path}`; + const params = new URLSearchParams([["f", name]]); + + if (server) { + const url = new URL(pathname, server); + url.search = String(params); + + return url; + } + + return new InternalURL(pathname, params); +} + +export function createPopularPostsPageURL( + date?: string, + scale?: IPopularPostsPeriod, + offset?: number +) { + const path = `/posts/popular`; + const params = new URLSearchParams(); + + if (date) { + params.set("date", date); + } + + if (scale) { + params.set("period", scale); + } + + if (offset) { + params.set("o", String(offset)); + } + + return new InternalURL(path, params); +} diff --git a/client/src/lib/urls/profiles.ts b/client/src/lib/urls/profiles.ts new file mode 100644 index 0000000..b6c40ed --- /dev/null +++ b/client/src/lib/urls/profiles.ts @@ -0,0 +1,126 @@ +import { InternalURL } from "./internal-url"; + +interface ICreateProfileURLArg { + service: string; + profileID: string; + offset?: number; + query?: string; + tags?: string[]; +} + +export function createProfilePageURL({ + service, + profileID, + offset, + query, + tags, +}: ICreateProfileURLArg) { + const segment = service === "discord" ? "server" : "user"; + const path = `/${service}/${segment}/${profileID}`; + const searchParams = new URLSearchParams(); + + if (offset) { + searchParams.set("o", String(offset)); + } + + if (query) { + searchParams.set("q", query); + } + + if (tags) { + for (const tag of tags) { + searchParams.set("tag", tag); + } + } + + return new InternalURL(path, searchParams); +} + +export function createProfileFancardsURL(service: string, profileID: string) { + const path = `/${service}/user/${profileID}/fancards`; + + return new InternalURL(path); +} + +export function createProfileAnnouncementsURL( + service: string, + profileID: string +) { + return new InternalURL(`/${service}/user/${profileID}/announcements`); +} + +export function createProfileTagsURL(service: string, profileID: string) { + return new InternalURL(`/${service}/user/${profileID}/tags`); +} + +export function createProfileTagURL( + service: string, + profileID: string, + tag: string +) { + const path = `/${service}/user/${profileID}`; + const params = new URLSearchParams([["tag", tag]]); + + return new InternalURL(path, params); +} + +export function createProfileDMsURL(service: string, profileID: string) { + return new InternalURL(`/${service}/user/${profileID}/dms`); +} + +export function createProfileSharesURL(service: string, profileID: string) { + return new InternalURL(`/${service}/user/${profileID}/shares`); +} + +export function createProfileLinksURL(service: string, profileID: string) { + return new InternalURL(`/${service}/user/${profileID}/links`); +} + +export function createProfileNewLinksPageURL( + service: string, + profileID: string +) { + return new InternalURL(`/account/${service}/user/${profileID}/links/new`); +} + +export function createProfilesSharesPageURL( + service: string, + profileID: string, + offset?: number +) { + const path = `/${service}/user/${profileID}/shares`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + return new InternalURL(path, params); +} + +export function createProfileTagsPageURL(service: string, profileID: string) { + const path = `/${service}/user/${profileID}/tags`; + + return new InternalURL(path) +} + +export function createDiscordServerPageURL(serverID: string) { + const path = `/discord/server/${serverID}`; + + return new InternalURL(path); +} + +export function createDiscordChannelPageURL( + serverID: string, + channelID: string, + offset?: number +) { + const path = `/discord/server/${serverID}/${channelID}`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + return new InternalURL(path, params); +} diff --git a/client/src/lib/urls/shares.ts b/client/src/lib/urls/shares.ts new file mode 100644 index 0000000..5378fd6 --- /dev/null +++ b/client/src/lib/urls/shares.ts @@ -0,0 +1,21 @@ +import { IShare } from "#entities/files"; +import { InternalURL } from "./internal-url"; + +export function createSharesPageURL( + offset?: number +) { + const path = `/shares`; + const params = new URLSearchParams(); + + if (offset) { + params.set("o", String(offset)); + } + + return new InternalURL(path, params); +} + +export function createSharePageURL(shareID: IShare["id"]) { + const path = `/share/${shareID}`; + + return new InternalURL(path); +} diff --git a/client/src/lib/urls/tags.ts b/client/src/lib/urls/tags.ts new file mode 100644 index 0000000..d9d6992 --- /dev/null +++ b/client/src/lib/urls/tags.ts @@ -0,0 +1,8 @@ +import { InternalURL } from "./internal-url"; + +export function createTagPageURL(tag: string) { + const path = "/posts"; + const searchParams = new URLSearchParams([["tag", tag]]); + + return new InternalURL(path, searchParams); +} diff --git a/client/src/pages/2257.tsx b/client/src/pages/2257.tsx new file mode 100644 index 0000000..131b614 --- /dev/null +++ b/client/src/pages/2257.tsx @@ -0,0 +1,49 @@ +import { PageSkeleton } from "#components/pages"; + +export function Compliance2257Page() { + const title = "18 U.S.C. 2257 Compliance Statement"; + const heading = "18 U.S.C. 2257 Compliance Statement"; + + return ( + +

    + Kemono is not a producer (whether primary or secondary as defined in 18 + U.S.C. 2257) of any of the content found on this website. The website's + activities, with respect to such content, are limited to the + transmission, storage, retrieval, and/or hosting of content on behalf of + third party users. +

    + +

    + Please direct any requests you may have regarding 2257 records in + relation to any content found on Kemono directly to the respective + uploader, artist, or producer of said content. +

    + +

    + Kemono abides by the following procedures regarding uploaded content to + ensure compliance: +

    + +
      +
    • + Requiring all users to be over 18 years old to use the site, register + an account, or upload content. +
    • +
    • + Prohibiting the upload of any photographs or videos of any real person + who is or appears to be under the age of 18. +
    • +
    • + Moderating all uploaded content and expeditiously removing any content + found to be in violation of these policies. +
    • +
    + +

    + For further assistance, or for any questions regarding this notice, + please contact us. +

    +
    + ); +} diff --git a/client/src/pages/_index.js b/client/src/pages/_index.js deleted file mode 100644 index 76948bd..0000000 --- a/client/src/pages/_index.js +++ /dev/null @@ -1,43 +0,0 @@ -import { userPage } from "./user"; -import { viewLinkedAccountsPage } from "./artist/linked_accounts.js"; -import { newLinkedAccountPage } from "./artist/new_linked_account.js"; -import { changePasswordPage, registerPage } from "./account/_index.js"; -import { postPage } from "./post"; -import { importerPage } from "./importer_list"; -import { importerStatusPage } from "./importer_status"; -import { postsPage } from "./posts"; -import { artistsPage } from "./artists"; -import { updatedPage } from "./updated"; -import { uploadPage } from "./upload"; -import { searchHashPage } from "./search_hash"; -import { registerPaginatorKeybinds } from "@wp/components"; -import { reviewDMsPage } from "./review_dms/dms"; -import { creatorLinksPage } from "./account/moderator/creator_links"; - -export { adminPageScripts } from "./account/administrator/_index.js"; -export { moderatorPageScripts } from "./account/moderator/_index.js"; -/** - * The map of page names and their callbacks. - */ -export const globalPageScripts = new Map([ - ["user", [userPage]], - ["register", [registerPage]], - ["change-password", [changePasswordPage]], - ["linked-account", [viewLinkedAccountsPage]], - ["new-linked-account", [newLinkedAccountPage]], - ["post", [postPage]], - ["importer", [importerPage]], - ["importer-status", [importerStatusPage]], - ["posts", [postsPage]], - ["popular-posts", [postsPage]], - ["artists", [artistsPage]], - ["updated", [updatedPage]], - ["upload", [uploadPage]], - ["all-dms", [registerPaginatorKeybinds]], - ["favorites", [registerPaginatorKeybinds]], - ["file-hash-search", [searchHashPage]], - ["review-dms", [reviewDMsPage]], - // trying to load moderator scripts by initSections(moderatorPageScripts) breaks this pile of junk - // so it's going here instead - ["moderator-creator-links", [creatorLinksPage]], -]); diff --git a/client/src/pages/_index.scss b/client/src/pages/_index.scss index 76d2a0e..cf64db7 100644 --- a/client/src/pages/_index.scss +++ b/client/src/pages/_index.scss @@ -1,12 +1,9 @@ -@use "components"; @use "home"; @use "post"; -@use "artist"; -@use "user"; +@use "profile/index"; +@use "profile.scss"; @use "review_dms"; -@use "importer_status"; +@use "importer/importer_status.scss"; @use "posts"; -@use "favorites"; @use "account"; @use "upload"; -@use "tags"; diff --git a/client/src/pages/account/_index.js b/client/src/pages/account/_index.js deleted file mode 100644 index 43d0ac4..0000000 --- a/client/src/pages/account/_index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { registerPage } from "./register.js"; -export { changePasswordPage } from "./change_password.js"; diff --git a/client/src/pages/account/_index.scss b/client/src/pages/account/_index.scss index 78ca863..76df913 100644 --- a/client/src/pages/account/_index.scss +++ b/client/src/pages/account/_index.scss @@ -1,5 +1,5 @@ @use "home"; -@use "components"; @use "notifications"; @use "keys"; @use "moderator"; +@use "register"; diff --git a/client/src/pages/account/administrator/_index.js b/client/src/pages/account/administrator/_index.js deleted file mode 100644 index b892de3..0000000 --- a/client/src/pages/account/administrator/_index.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @type {Map void>} - */ -export const adminPageScripts = new Map(); diff --git a/client/src/pages/account/administrator/_index.scss b/client/src/pages/account/administrator/_index.scss index 08e6287..240eaae 100644 --- a/client/src/pages/account/administrator/_index.scss +++ b/client/src/pages/account/administrator/_index.scss @@ -1,2 +1 @@ @use "accounts"; -@use "shell"; diff --git a/client/src/pages/account/administrator/account.module.scss b/client/src/pages/account/administrator/account.module.scss new file mode 100644 index 0000000..81ec887 --- /dev/null +++ b/client/src/pages/account/administrator/account.module.scss @@ -0,0 +1,6 @@ +@use "../../../css/config/variables/sass" as *; + +// TODO: nuke this declaration once page skeleton is a proper grid +.overview { + margin: 0 auto; +} diff --git a/client/src/pages/account/administrator/account.tsx b/client/src/pages/account/administrator/account.tsx new file mode 100644 index 0000000..62702b8 --- /dev/null +++ b/client/src/pages/account/administrator/account.tsx @@ -0,0 +1,102 @@ +import { + ActionFunctionArgs, + LoaderFunctionArgs, + redirect, + useLoaderData, +} from "react-router"; +import { createAccountDetailsPageURL } from "#lib/urls"; +import { + apiChangeTargetAccountRole, + apiFetchAccount, +} from "#api/account/administrator"; +import { + PageSkeleton, + validateAdministratorPageAction, + validateAdministratorPageLoader, +} from "#components/pages"; +import { IAccount, ensureAccountRole } from "#entities/account"; +import { AccountOverview } from "#entities/administrator"; + +import * as styles from "./account.module.scss"; + +interface IProps { + account: IAccount; +} + +export function AdministratorAccountOverviewPage() { + const { account } = useLoaderData() as IProps; + const title = "Account overview"; + const heading = "Account Overview"; + + return ( + + + + ); +} + +export async function loader(args: LoaderFunctionArgs) { + await validateAdministratorPageLoader(args); + + const { params } = args; + + const accountID = params.account_id?.trim(); + + if (!accountID) { + throw new Error("Account ID is required."); + } + + const parsedAccountID = Number(accountID); + + if (parsedAccountID === 0) { + throw new Error("Account ID must be positive."); + } + + const account = await apiFetchAccount(parsedAccountID); + + const props: IProps = { + account, + }; + + return props; +} + +export async function action(args: ActionFunctionArgs) { + try { + await validateAdministratorPageAction(args); + + const { params, request } = args; + + const accountID = params.account_id?.trim(); + + if (!accountID) { + throw new Error("Account ID is required."); + } + + const parsedAccountID = Number(accountID); + + if (parsedAccountID === 0) { + throw new Error("Account ID must be positive."); + } + + const data = await request.formData(); + + const role = (data.get("role") as string | null)?.trim(); + + if (!role) { + throw new Error("Role is required."); + } + + ensureAccountRole(role); + + const targetAccountID = await apiChangeTargetAccountRole( + parsedAccountID, + role + ); + const url = String(createAccountDetailsPageURL(targetAccountID)); + + return redirect(url); + } catch (error) { + return error; + } +} diff --git a/client/src/pages/account/administrator/account_files.html b/client/src/pages/account/administrator/account_files.html deleted file mode 100644 index f3ec702..0000000 --- a/client/src/pages/account/administrator/account_files.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'account/administrator/shell.html' %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/account/administrator/account_info.html b/client/src/pages/account/administrator/account_info.html deleted file mode 100644 index 3fccee2..0000000 --- a/client/src/pages/account/administrator/account_info.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends 'account/administrator/shell.html' %} - -{% from 'components/timestamp.html' import timestamp %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/account/administrator/accounts.html b/client/src/pages/account/administrator/accounts.html deleted file mode 100644 index 4043707..0000000 --- a/client/src/pages/account/administrator/accounts.html +++ /dev/null @@ -1,141 +0,0 @@ -{% extends 'account/administrator/shell.html' %} - -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/account.html' import account_card %} -{% from 'components/paginator_new.html' import paginator, paginator_controller %} - -{% block content %} -
    -
    -

    - Accounts -

    -
    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - -
    - {{ paginator('account-pages', request, props.pagination) }} - {% call card_list('legacy') %} - {% for account in props.accounts %} - {% if account.role == 'moderator' %} - - {% elif account.role == 'consumer'%} - - {% else %} - {% endif %} - {% else %} -

    No accounts found.

    - {% endfor %} - {% endcall %} - {# {{ paginator('account-pages', request, props.pagination) }} #} - {% if props.accounts | length %} -
    - -
    - {% endif %} -
    - {{ paginator_controller( - 'account-pages', - request, - props.pagination - ) }} -
    -{% endblock content %} diff --git a/client/src/pages/account/administrator/accounts.module.scss b/client/src/pages/account/administrator/accounts.module.scss new file mode 100644 index 0000000..3359c53 --- /dev/null +++ b/client/src/pages/account/administrator/accounts.module.scss @@ -0,0 +1,6 @@ +@use "../../../css/config/variables/sass" as *; + +.filter { + max-width: 360px; + margin: 0 auto; +} diff --git a/client/src/pages/account/administrator/accounts.scss b/client/src/pages/account/administrator/accounts.scss index 4fe69ef..2f1af67 100644 --- a/client/src/pages/account/administrator/accounts.scss +++ b/client/src/pages/account/administrator/accounts.scss @@ -1,4 +1,4 @@ -@use "../../../css/config/variables" as *; +@use "../../../css/config/variables/sass" as *; .site-section--admin-accounts { .account { @@ -54,6 +54,7 @@ transition-duration: var(--duration-global); transition-property: border-color; + // TODO: nuke this declaration after rewriting page-specific styles & .account-card { height: 100%; border-radius: 0; diff --git a/client/src/pages/account/administrator/accounts.tsx b/client/src/pages/account/administrator/accounts.tsx new file mode 100644 index 0000000..7749d29 --- /dev/null +++ b/client/src/pages/account/administrator/accounts.tsx @@ -0,0 +1,174 @@ +import { LoaderFunctionArgs, redirect, useLoaderData } from "react-router"; +import { + IPagination, + createPagination, + parsePageNumber, +} from "#lib/pagination"; +import { createAccountsPageURL } from "#lib/urls"; +import { apiCountAccounts, apiFetchAccounts } from "#api/account/administrator"; +import { FormRouter } from "#components/forms"; +import { Option } from "#components/forms/inputs"; +import { FormSectionSelect, FormSectionText } from "#components/forms/sections"; +import { Pagination, PaginationInfo } from "#components/pagination"; +import { CardList } from "#components/cards"; +import { + PageSkeleton, + validateAdministratorPageLoader, +} from "#components/pages"; +import { Details } from "#components/details"; +import { + AccountPreview, + IAccontRole, + IAccount, + accountRoles, + ensureAccountRole, +} from "#entities/account"; + +import * as styles from "./accounts.module.scss"; + +interface IProps { + name?: string; + role?: IAccontRole; + pagination: IPagination; + accounts: IAccount[]; +} + +export function AdministratorAccountsPage() { + const { name, role, pagination, accounts } = useLoaderData() as IProps; + const title = "Accounts"; + const heading = "Accounts"; + + return ( + +
    + "Search"} + > + + + + + {accountRoles.map((role) => ( + + ))} + + } + /> + +
    + + + + + {accounts.length === 0 ? ( +

    No accounts found.

    + ) : ( + accounts.map((account) => ( + + )) + )} +
    + + String(createAccountsPageURL(page, name, role))} + extraValues={{ name, role }} + /> +
    + ); +} + +export async function loader(args: LoaderFunctionArgs): Promise { + await validateAdministratorPageLoader(args); + + const { request, params } = args; + const searchParams = new URL(request.url).searchParams; + + const page = parsePageNumber(params.page?.trim()); + const name = searchParams.get("name")?.trim(); + + let role: undefined | IAccontRole; + { + const inputValue = searchParams.get("role")?.trim(); + + if (inputValue) { + ensureAccountRole(inputValue); + + role = inputValue; + } + } + + const totalCount = await apiCountAccounts(name, role); + + if (totalCount === 0) { + throw new Error("No accounts found."); + } + + const pagination = createPagination(totalCount, page); + const accounts = await apiFetchAccounts(pagination.current_page, name, role); + + return { + accounts, + pagination, + name, + role, + }; +} + +export async function baseLoader(args: LoaderFunctionArgs) { + await validateAdministratorPageLoader(args); + + const { request } = args; + const searchParams = new URL(request.url).searchParams; + + const name = searchParams.get("name")?.trim(); + + let page: number | undefined = undefined; + { + const inputValue = searchParams.get("page")?.trim(); + if (inputValue) { + page = parsePageNumber(inputValue); + } + } + + let role: undefined | IAccontRole; + { + const inputValue = searchParams.get("role")?.trim(); + + if (inputValue) { + ensureAccountRole(inputValue); + + role = inputValue; + } + } + + const totalCount = await apiCountAccounts(name, role); + + if (totalCount === 0) { + throw new Error("No accounts found."); + } + + const pagination = createPagination(totalCount, page); + const url = String( + createAccountsPageURL(pagination.current_page, name, role) + ); + + return redirect(url); +} diff --git a/client/src/pages/account/administrator/dashboard.html b/client/src/pages/account/administrator/dashboard.html deleted file mode 100644 index 7ba801e..0000000 --- a/client/src/pages/account/administrator/dashboard.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'account/administrator/shell.html' %} - -{% block content %} -
    -
    -

    - Admin dashboard -

    -
    - -
    -{% endblock content %} diff --git a/client/src/pages/account/administrator/dashboard.module.scss b/client/src/pages/account/administrator/dashboard.module.scss new file mode 100644 index 0000000..81ec887 --- /dev/null +++ b/client/src/pages/account/administrator/dashboard.module.scss @@ -0,0 +1,6 @@ +@use "../../../css/config/variables/sass" as *; + +// TODO: nuke this declaration once page skeleton is a proper grid +.overview { + margin: 0 auto; +} diff --git a/client/src/pages/account/administrator/dashboard.tsx b/client/src/pages/account/administrator/dashboard.tsx new file mode 100644 index 0000000..eaee974 --- /dev/null +++ b/client/src/pages/account/administrator/dashboard.tsx @@ -0,0 +1,28 @@ +import { createAccountsPageURL } from "#lib/urls"; +import { createAccountPageLoader, PageSkeleton } from "#components/pages"; +import { KemonoLink } from "#components/links"; +import { Overview, OverviewHeader } from "#components/overviews"; + +import * as styles from "./dashboard.module.scss"; + +export function AdministratorDashboardPage() { + return ( + + + + + + + + ); +} + +export const loader = createAccountPageLoader(); diff --git a/client/src/pages/account/administrator/mods_actions.html b/client/src/pages/account/administrator/mods_actions.html deleted file mode 100644 index 44a8f89..0000000 --- a/client/src/pages/account/administrator/mods_actions.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'account/administrator/shell.html' %} - -{% block content %} -
    -
    -

    - Moderator actions -

    -
    -
      - {% for action in props.actions %} -
    • action
    • - {% else %} -
    • No actions found
    • - {% endfor %} -
    -
    -{% endblock content %} diff --git a/client/src/pages/account/administrator/shell.html b/client/src/pages/account/administrator/shell.html deleted file mode 100644 index 8a8724f..0000000 --- a/client/src/pages/account/administrator/shell.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'components/shell.html' %} - -{# TODO: filter only admin entry #} -{% block bundler_output %} - <% for (const css in htmlWebpackPlugin.files.css) { %> - <% if (htmlWebpackPlugin.files.css[css].startsWith("/static/bundle/css/admin")) { %> - - <% } %> - <% } %> - <% for (const chunk in htmlWebpackPlugin.files.chunks) { %> - - <% } %> - <% for (const scriptPath in htmlWebpackPlugin.files.js) { %> - <% if (htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/admin") || htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/runtime") || htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/vendors")) { %> - - <% } %> - <% } %> -{% endblock bundler_output %} diff --git a/client/src/pages/account/administrator/shell.scss b/client/src/pages/account/administrator/shell.scss deleted file mode 100644 index 0dc0330..0000000 --- a/client/src/pages/account/administrator/shell.scss +++ /dev/null @@ -1 +0,0 @@ -@use "../../components/shell"; diff --git a/client/src/pages/account/change_password.html b/client/src/pages/account/change_password.html deleted file mode 100644 index 5064837..0000000 --- a/client/src/pages/account/change_password.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "components/shell.html" %} - -{% block content %} - -{% endblock %} diff --git a/client/src/pages/account/change_password.js b/client/src/pages/account/change_password.js deleted file mode 100644 index 5119ac6..0000000 --- a/client/src/pages/account/change_password.js +++ /dev/null @@ -1,45 +0,0 @@ -export function changePasswordPage() { - let passwordInput = () => document.getElementById("current-password"); - let newPasswordInput = () => document.getElementById("new-password"); - let newPasswordConfirmationInput = () => document.getElementById("new-password-confirmation"); - let submitButton = () => document.getElementById("submit"); - - let passCharCount = () => document.getElementById("password-char-count"); - let newPassCharCount = () => document.getElementById("new-password-char-count"); - let passMatches = () => document.getElementById("password-confirm-matches"); - - function doValidate(e) { - let password = passwordInput().value; - let newPassword = newPasswordInput().value; - let newPasswordConfirmation = newPasswordConfirmationInput().value; - let errors = false; - - if (password.length < 5) { - errors = true; - passCharCount().classList.add("invalid"); - } else { - passCharCount().classList.remove("invalid"); - } - - if (newPassword.length < 5) { - errors = true; - newPassCharCount().classList.add("invalid"); - } else { - newPassCharCount().classList.remove("invalid"); - } - - if (newPassword != newPasswordConfirmation || newPassword.length < 5) { - errors = true; - passMatches().classList.add("invalid"); - } else { - passMatches().classList.remove("invalid"); - } - - submitButton().disabled = errors; - } - - doValidate(); - passwordInput().addEventListener("input", doValidate); - newPasswordInput().addEventListener("input", doValidate); - newPasswordConfirmationInput().addEventListener("input", doValidate); -} diff --git a/client/src/pages/account/change_password.tsx b/client/src/pages/account/change_password.tsx new file mode 100644 index 0000000..d50fc21 --- /dev/null +++ b/client/src/pages/account/change_password.tsx @@ -0,0 +1,116 @@ +import { ActionFunctionArgs, redirect } from "react-router"; +import { createAccountPageURL } from "#lib/urls"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { FormRouter } from "#components/forms"; +import { fetchAccountChangePassword } from "#api/account"; + +export function AccountChangePasswordPage() { + const title = "Change password"; + const heading = "Change Password"; + + return ( + + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +
    + ); +} + +export const loader = createAccountPageLoader(); + +export async function action({ request }: ActionFunctionArgs) { + try { + if (request.method !== "POST") { + throw new Error(`Unknown method "${request.method}".`); + } + + const data = await request.formData(); + + const currentPassword = data.get("current-password") as string | null; + + if (!currentPassword) { + throw new Error("Password cannot be empty."); + } + + const newPassword = (data.get("new-password") as string | null)?.trim(); + + if (!newPassword) { + throw new Error("New password cannot be empty."); + } + + if (newPassword.length < 5) { + throw new Error("New password must have at least 5 characters."); + } + + const newPasswordConfirmation = ( + data.get("new-password-confirmation") as string | null + )?.trim(); + + if (newPassword !== newPasswordConfirmation) { + throw new Error("New password and confirmation do not match."); + } + + await fetchAccountChangePassword( + currentPassword, + newPassword, + newPasswordConfirmation + ); + + return redirect(String(createAccountPageURL())); + } catch (error) { + return error; + } +} diff --git a/client/src/pages/account/components/notification.html b/client/src/pages/account/components/notification.html deleted file mode 100644 index 81aaf48..0000000 --- a/client/src/pages/account/components/notification.html +++ /dev/null @@ -1,20 +0,0 @@ -{% from 'components/timestamp.html' import timestamp %} - -{% macro ACCOUNT_ROLE_CHANGE(extra_info) %} - Your role was changed from {{ extra_info.old_role }} to {{ extra_info.new_role }}. -{% endmacro %} - -{% set notification_types = { - 1: ACCOUNT_ROLE_CHANGE -} %} - -{% macro notification_item(notification) %} -
  • - - {{ timestamp(notification.created_at) }} - - - {{ notification_types[notification.type](notification.extra_info) }} - -
  • -{% endmacro %} diff --git a/client/src/pages/account/components/service_key.html b/client/src/pages/account/components/service_key.html deleted file mode 100644 index 0c0d04a..0000000 --- a/client/src/pages/account/components/service_key.html +++ /dev/null @@ -1,50 +0,0 @@ -{% from 'components/cards/base.html' import card, card_header, card_body, card_footer %} -{% from 'components/timestamp.html' import timestamp %} - -{% macro service_key_card(service_key, import_ids, class_name= none) %} - {% set paysite = g.paysites[service_key.service] %} - - {% call card(class_name= class_name) %} - {% call card_header() %} -

    - {{ paysite.title }} -

    - {% endcall %} - - {% call card_body() %} -
    -
    -
    Status:
    - {% if not service_key.dead %} -
    - Alive -
    - {% else %} -
    - Dead -
    - {% endif %} -
    -
    - {% if import_ids %} -
    Logs
    - - {% endif %} -
    -
    - {% endcall %} - - {% call card_footer() %} -
    -
    -
    Added:
    -
    {{ timestamp(service_key.added) }}
    -
    -
    - {% endcall %} - {% endcall %} -{% endmacro %} diff --git a/client/src/pages/account/favorites/legacy.tsx b/client/src/pages/account/favorites/legacy.tsx new file mode 100644 index 0000000..dbb7231 --- /dev/null +++ b/client/src/pages/account/favorites/legacy.tsx @@ -0,0 +1,6 @@ +import { redirect } from "react-router"; +import { createAccountFavoriteProfilesPageURL } from "#lib/urls"; + +export async function loader() { + return redirect(String(createAccountFavoriteProfilesPageURL())); +} diff --git a/client/src/pages/account/favorites/posts.module.scss b/client/src/pages/account/favorites/posts.module.scss new file mode 100644 index 0000000..df694a8 --- /dev/null +++ b/client/src/pages/account/favorites/posts.module.scss @@ -0,0 +1,14 @@ +.dropdowns { + display: grid; + grid-template-columns: max-content max-content; + grid-gap: 5px; + justify-content: center; +} + +.label { + text-align: right; + + ::after { + content: ":"; + } +} diff --git a/client/src/pages/account/favorites/posts.tsx b/client/src/pages/account/favorites/posts.tsx new file mode 100644 index 0000000..68f8546 --- /dev/null +++ b/client/src/pages/account/favorites/posts.tsx @@ -0,0 +1,185 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { + createAccountFavoritePostsPageURL, + createAccountFavoriteProfilesPageURL, +} from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { HeaderAd, SliderAd } from "#components/advs"; +import { Paginator } from "#components/pagination"; +import { CardList, PostCard } from "#components/cards"; +import { FormRouter } from "#components/forms"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { KemonoLink } from "#components/links"; +import { IFavouritePost, getAllFavouritePosts } from "#entities/account"; + +import * as styles from "./posts.module.scss"; + +type IProps = { + order?: IOrder; + count: number; + offset?: number; + sortBy?: ISortBy; + posts: IFavouritePost[]; +}; + +const sortByValues = ["faved_seq", "published"] as const; +const orderValues = ["asc", "desc"] as const; + +type ISortBy = (typeof sortByValues)[number]; +type IOrder = (typeof orderValues)[number]; + +function validateSortBy(input: unknown): asserts input is ISortBy { + if (!sortByValues.includes(input as ISortBy)) { + throw new Error(`Invalid sort by value "${input}".`); + } +} + +function validateOrder(input: unknown): asserts input is IOrder { + if (!orderValues.includes(input as IOrder)) { + throw new Error(`Invalid order value "${input}".`); + } +} + +/** + * TODO: split into separate pages + */ +export function FavoritePostsPage() { + const { sortBy, order, count, offset, posts } = useLoaderData() as IProps; + const title = "Favorite posts"; + const heading = "Favorite Posts"; + + return ( + + + + + <> + "Filter"} + > +

    + + Favorite Artists + +

    + {/* a filler div until proper form rewrite */} +
    + + + + + +
    + + <> +
    + + String(createAccountFavoritePostsPageURL(offset)) + } + /> +
    + + + {count === 0 ? ( + <> +

    Nobody here but us chickens!

    +

    There are no more posts.

    + + ) : ( + posts.map((post) => ( + + )) + )} +
    + +
    + + String(createAccountFavoritePostsPageURL(offset)) + } + /> +
    + + +
    + ); +} + +export const loader = createAccountPageLoader(async function loader({ + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let sortBy: IProps["sortBy"] = undefined; + { + const inputValue = searchParams.get("sort_by")?.trim(); + + if (inputValue) { + validateSortBy(inputValue); + + sortBy = inputValue; + } + } + + let offset: IProps["offset"] = undefined; + { + const inputValue = searchParams.get("o")?.trim(); + + if (inputValue) { + offset = parseOffset(inputValue); + } + } + + let order: IProps["order"] = undefined; + { + const inputValue = searchParams.get("order")?.trim(); + + if (inputValue) { + validateOrder(inputValue); + + order = inputValue; + } + } + + const { count, posts } = await getAllFavouritePosts(offset, order, sortBy); + + return { + count, + posts, + offset, + order, + sortBy, + }; +}); diff --git a/client/src/pages/account/favorites/profiles.module.scss b/client/src/pages/account/favorites/profiles.module.scss new file mode 100644 index 0000000..df694a8 --- /dev/null +++ b/client/src/pages/account/favorites/profiles.module.scss @@ -0,0 +1,14 @@ +.dropdowns { + display: grid; + grid-template-columns: max-content max-content; + grid-gap: 5px; + justify-content: center; +} + +.label { + text-align: right; + + ::after { + content: ":"; + } +} diff --git a/client/src/pages/account/favorites/profiles.tsx b/client/src/pages/account/favorites/profiles.tsx new file mode 100644 index 0000000..240a328 --- /dev/null +++ b/client/src/pages/account/favorites/profiles.tsx @@ -0,0 +1,190 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { + createAccountFavoritePostsPageURL, + createAccountFavoriteProfilesPageURL, +} from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { HeaderAd, SliderAd } from "#components/advs"; +import { Paginator } from "#components/pagination"; +import { CardList, ArtistCard } from "#components/cards"; +import { FormRouter } from "#components/forms"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { KemonoLink } from "#components/links"; +import { IFavouriteArtist, getAllFavouriteProfiles } from "#entities/account"; + +import * as styles from "./profiles.module.scss"; + +interface IProps { + order?: IOrder; + count: number; + offset?: number; + sortBy?: ISortBy; + profiles: IFavouriteArtist[]; +} + +const sortByValues = ["updated", "faved_seq", "last_imported"] as const; +const orderValues = ["asc", "desc"] as const; +type ISortBy = (typeof sortByValues)[number]; +type IOrder = (typeof orderValues)[number]; + +function validateSortBy(input: unknown): asserts input is ISortBy { + if (!sortByValues.includes(input as ISortBy)) { + throw new Error(`Invalid sort by value "${input}".`); + } +} + +function validateOrder(input: unknown): asserts input is IOrder { + if (!orderValues.includes(input as IOrder)) { + throw new Error(`Invalid order value "${input}".`); + } +} + +export function FavoriteProfilesPage() { + const { sortBy, order, count, offset, profiles } = useLoaderData() as IProps; + const title = "Favorite profiles"; + const heading = "Favorite Profiles"; + + return ( + + + + + "Filter"} + > +

    + + Favorite Posts + +

    + {/* a filler div until proper form rewrite */} +
    + + + + + +
    + +
    + + String(createAccountFavoriteProfilesPageURL(offset, sortBy, order)) + } + /> +
    + + + {count === 0 ? ( + <> +

    Nobody here but us chickens!

    +

    There are no profiles.

    + + ) : ( + profiles.map((profile) => ( + + )) + )} +
    + +
    + + String(createAccountFavoriteProfilesPageURL(offset, sortBy, order)) + } + /> +
    +
    + ); +} + +export const loader = createAccountPageLoader(async function loader({ + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let sortBy: IProps["sortBy"] = undefined; + { + const inputValue = searchParams.get("sort_by")?.trim(); + + if (inputValue) { + validateSortBy(inputValue); + + sortBy = inputValue; + } + } + + let offset: IProps["offset"] = undefined; + { + const inputValue = searchParams.get("o")?.trim(); + + if (inputValue) { + offset = parseOffset(inputValue); + } + } + + let order: IProps["order"] = undefined; + { + const inputValue = searchParams.get("order")?.trim(); + + if (inputValue) { + validateOrder(inputValue); + + order = inputValue; + } + } + + const { count, profiles } = await getAllFavouriteProfiles( + offset, + order, + sortBy + ); + + return { + count, + profiles, + offset, + order, + sortBy, + }; +}); diff --git a/client/src/pages/account/home.html b/client/src/pages/account/home.html deleted file mode 100644 index ab65f36..0000000 --- a/client/src/pages/account/home.html +++ /dev/null @@ -1,80 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/image_link.html' import image_link %} - -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/links.html' import kemono_link %} - -{% set role_links = { - "consumer": "/account", - "moderator": "/account/moderator", - "administrator": "/account/administrator" -} %} - -{% block title %} - {{ props.title }} -{% endblock title %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/account/home.scss b/client/src/pages/account/home.scss index 627badb..b879c68 100644 --- a/client/src/pages/account/home.scss +++ b/client/src/pages/account/home.scss @@ -33,3 +33,8 @@ } } } + +.site-section--home { + display: flex; + flex-direction: column; +} diff --git a/client/src/pages/account/home.tsx b/client/src/pages/account/home.tsx new file mode 100644 index 0000000..1559bf9 --- /dev/null +++ b/client/src/pages/account/home.tsx @@ -0,0 +1,130 @@ +import { useLoaderData } from "react-router"; +import { + createAccountDMsReviewPageURL, + createAccountFavoritePostsPageURL, + createAccountFavoriteProfilesPageURL, + createAccountImportKeysPageURL, + createAccountNotificationsPageURL, + createAccountPageURL, + createAccountPasswordChangePageURL, + createAdministratorPageURL, + createModeratorPageURL, +} from "#lib/urls"; +import { fetchAccount } from "#api/account"; +import { IAccount } from "#entities/account"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { KemonoLink } from "#components/links"; + +interface IProps { + account: IAccount; + notificationsCount: number; +} + +export function AccountPage() { + const { account, notificationsCount } = useLoaderData() as IProps; + const { role, username, created_at } = account; + const title = "Your account details"; + const heading = "Your Account Details"; + const roleURL = String( + role === "administrator" + ? createAdministratorPageURL() + : role == "moderator" + ? createModeratorPageURL() + : createAccountPageURL() + ); + + return ( + +
    +
    + + Hello, + {username} + + +
    + Joined {created_at} | + + + {role} + + +
    +
    + +
    + Favorites: +
      +
    • + + Profiles + +
    • +
    • + + Posts + +
    • +
    +
    + +

    + Notifications: + + + {notificationsCount} + + +

    + +

    + + Keys + +

    + +

    + + Review DMs + +

    + +

    + + Change password + +

    +
    +
    + ); +} + +export const loader = createAccountPageLoader( + async function loader(): Promise { + const { props } = await fetchAccount(); + const { account, currentPage, notifications_count, title } = props; + + return { + account, + notificationsCount: notifications_count, + }; + } +); diff --git a/client/src/pages/account/keys.html b/client/src/pages/account/keys.html deleted file mode 100644 index 8879a90..0000000 --- a/client/src/pages/account/keys.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends 'components/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'components/forms/base.html' import form %} -{% from 'components/forms/submit_button.html' import submit_button %} -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/no_results.html' import no_results %} -{% from 'account/components/service_key.html' import service_key_card %} - -{% set page_title = 'Manage saved keys | ' ~ g.site_name %} - -{% block title %} - - {{ page_title }} - -{% endblock title %} - -{% block content %} -{% call site.section('account-keys', 'Stored service keys') %} - {% call card_list() %} - {% for key in props.service_keys %} -
    - -
    - {{ service_key_card(key, import_ids[loop.index0], class_name="key__card") }} - -
    -
    - {% else %} - {{ no_results() }} - {% endfor %} - {% endcall %} - {% if props.service_keys %} - {% call form( - id= 'revoke-service-keys', - action = '/account/keys', - method= 'POST' - ) %} - {{ submit_button('Revoke selected keys') }} - {% endcall %} - {% endif %} -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/account/keys.scss b/client/src/pages/account/keys.scss index a6c4813..4ecb233 100644 --- a/client/src/pages/account/keys.scss +++ b/client/src/pages/account/keys.scss @@ -1,4 +1,4 @@ -@use "../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .site-section--account-keys { .key { diff --git a/client/src/pages/account/keys.tsx b/client/src/pages/account/keys.tsx new file mode 100644 index 0000000..7ade699 --- /dev/null +++ b/client/src/pages/account/keys.tsx @@ -0,0 +1,113 @@ +import { ActionFunctionArgs, redirect, useLoaderData } from "react-router"; +import { createAccountImportKeysPageURL } from "#lib/urls"; +import { + fetchAccountAutoImportKeys, + fetchRevokeAutoImportKeys, +} from "#api/account/auto-import-keys"; +import { IAutoImportKey } from "#entities/account"; +import { CardList, NoResults } from "#components/cards"; +import { FormRouter } from "#components/forms"; +import { + PageSkeleton, + createAccountPageLoader, + validateAccountPageAction, +} from "#components/pages"; +import { AutoImportKeyCard } from "#entities/account"; + +interface IProps { + autoImportKeys: IAutoImportKey[]; + importIDs: { import_id: string }[]; +} + +export function AccountAutoImportKeysPage() { + const { autoImportKeys, importIDs } = useLoaderData() as IProps; + const title = "Manage saved keys"; + const heading = "Stored service keys"; + const revokeFormID = "revoke-service-keys"; + + return ( + + + {autoImportKeys.length === 0 ? ( + + ) : ( + autoImportKeys.map((autoImportKey, index) => ( +
    + +
    + + +
    +
    + )) + )} +
    + + {autoImportKeys.length !== 0 && ( + <>Revoke selected keys} + /> + )} +
    + ); +} + +export const loader = createAccountPageLoader( + async function loader(): Promise { + const { props, import_ids } = await fetchAccountAutoImportKeys(); + const { service_keys } = props; + + return { + autoImportKeys: service_keys, + importIDs: import_ids, + }; + } +); + +export async function action(args: ActionFunctionArgs) { + try { + await validateAccountPageAction(args); + + const { request } = args; + + if (request.method !== "POST") { + throw new Error(`Unknown method "${request.method}".`); + } + + const data = await request.formData(); + + const idsForRevocation = (data.getAll("revoke") as string[]).map((id) => + parseInt(id, 10) + ); + + if (idsForRevocation.length === 0) { + throw new Error("At least one key must be selected for revocation."); + } + + await fetchRevokeAutoImportKeys(idsForRevocation); + + return redirect(String(createAccountImportKeysPageURL())); + } catch (error) { + return error; + } +} diff --git a/client/src/pages/account/login.html b/client/src/pages/account/login.html deleted file mode 100644 index 7208ba0..0000000 --- a/client/src/pages/account/login.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} - -{% endblock %} diff --git a/client/src/pages/account/login.tsx b/client/src/pages/account/login.tsx new file mode 100644 index 0000000..c532335 --- /dev/null +++ b/client/src/pages/account/login.tsx @@ -0,0 +1,95 @@ +import { + ActionFunctionArgs, + redirect, + useSearchParams, +} from "react-router"; +import { PageSkeleton } from "#components/pages"; +import { KemonoLink } from "#components/links"; +import { createArtistsPageURL, createRegistrationPageURL } from "#lib/urls"; +import { FormRouter, FormSection } from "#components/forms"; +import { loginAccount } from "#entities/account"; + +export function AccountLoginPage() { + const [searchParams] = useSearchParams(); + const title = "Login"; + const heading = "Login"; + const location = + searchParams.get("location")?.trim() ?? String(createArtistsPageURL()); + + return ( + +

    + Don't have an account?{" "} + + Register! + {" "} + Your favorites will automatically be saved. +

    + + "Login"} + > + + + + + + + + + + + + +
    + ); +} + +export async function action({ request }: ActionFunctionArgs) { + try { + if (request.method !== "POST") { + throw new Error(`Unknown method "${request.method}".`); + } + + const data = await request.formData(); + + const location = data.get("location") as string; + const username = data.get("username") as string | null; + { + if (!username) { + throw new Error(`Username is required.`); + } + } + + const password = data.get("password") as string | null; + { + if (!password) { + throw new Error(`Password is required.`); + } + } + + await loginAccount(username, password); + + return redirect(location); + } catch (error) { + return error; + } +} diff --git a/client/src/pages/account/moderator/_index.js b/client/src/pages/account/moderator/_index.js deleted file mode 100644 index 110ce34..0000000 --- a/client/src/pages/account/moderator/_index.js +++ /dev/null @@ -1,4 +0,0 @@ -/** - * @type {Map void} - */ -export const moderatorPageScripts = new Map(); diff --git a/client/src/pages/account/moderator/_index.scss b/client/src/pages/account/moderator/_index.scss index bf6869d..8df7a36 100644 --- a/client/src/pages/account/moderator/_index.scss +++ b/client/src/pages/account/moderator/_index.scss @@ -1,4 +1,4 @@ -@use "../../../css/config/variables" as *; +@use "../../../css/config/variables/sass" as *; .site-section--moderator-dashboard { margin-left: auto; diff --git a/client/src/pages/account/moderator/creator_links.html b/client/src/pages/account/moderator/creator_links.html deleted file mode 100644 index bba7bb0..0000000 --- a/client/src/pages/account/moderator/creator_links.html +++ /dev/null @@ -1,55 +0,0 @@ -{% extends "components/shell.html" %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/account/moderator/creator_links.js b/client/src/pages/account/moderator/creator_links.js deleted file mode 100644 index 9523cc3..0000000 --- a/client/src/pages/account/moderator/creator_links.js +++ /dev/null @@ -1,22 +0,0 @@ -export function creatorLinksPage() { - Array.from(document.querySelectorAll(".link-request-card")).forEach(card => { - card.querySelector(".control > .approve").addEventListener("click", async (_e) => { - await approveLinkRequest(card.dataset["id"]); - card.remove(); - }); - card.querySelector(".control > .reject").addEventListener("click", async (_e) => { - await rejectLinkRequest(card.dataset["id"]); - card.remove(); - }); - }) -} - -async function approveLinkRequest(requestId) { - let resp = await fetch(`/creator_link_requests/${requestId}/approve`, { method: "POST" }); - let json = await resp.json(); -} - -async function rejectLinkRequest(requestId) { - let resp = await fetch(`/creator_link_requests/${requestId}/reject`, { method: "POST" }); - let json = await resp.json(); -} diff --git a/client/src/pages/account/moderator/dashboard.html b/client/src/pages/account/moderator/dashboard.html deleted file mode 100644 index 36a7af4..0000000 --- a/client/src/pages/account/moderator/dashboard.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'components/shell.html' %} - -{% block content %} -
    -
    -

    - Moderator room -

    -
    - -
    -{% endblock content %} diff --git a/client/src/pages/account/moderator/dashboard.tsx b/client/src/pages/account/moderator/dashboard.tsx new file mode 100644 index 0000000..de761d9 --- /dev/null +++ b/client/src/pages/account/moderator/dashboard.tsx @@ -0,0 +1,23 @@ +import { createProfileLinkRequestsPageURL } from "#lib/urls"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; + +export function ModeratorDashboardPage() { + const title = "Moderator overview"; + const heading = "Moderator Overview"; + + return ( + + + + ); +} + +export const loader = createAccountPageLoader(); diff --git a/client/src/pages/account/moderator/files.html b/client/src/pages/account/moderator/files.html deleted file mode 100644 index a9baeea..0000000 --- a/client/src/pages/account/moderator/files.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'components/shell.html' %} - -{% block content %} -
    -
    -

    - Files for review -

    -
    -
    -{% endblock content %} diff --git a/client/src/pages/account/moderator/profile_links.tsx b/client/src/pages/account/moderator/profile_links.tsx new file mode 100644 index 0000000..2771128 --- /dev/null +++ b/client/src/pages/account/moderator/profile_links.tsx @@ -0,0 +1,140 @@ +import { + LoaderFunctionArgs, + useLoaderData, + useNavigate, +} from "react-router"; +import { + createProfileLinkRequestsPageURL, + createProfilePageURL, +} from "#lib/urls"; +import { + fetchApproveLinkRequest, + fetchProfileLinkRequests, + fetchRejectLinkRequest, +} from "#api/account/moderator"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { IProfileLinkRequest } from "#entities/account"; +import { paysites } from "#entities/paysites"; + +interface IProps { + linkRequests: IProfileLinkRequest[]; +} + +export function ProfileLinkRequestsPage() { + const { linkRequests } = useLoaderData() as IProps; + const title = "Profile links requests"; + const heading = "Profile Links Requests"; + + return ( + +
    + {linkRequests.length === 0 ? ( +

    No pending requests. Yay!

    + ) : ( + linkRequests.map((linkRequest) => ( + + )) + )} +
    +
    + ); +} + +interface IProfileLinkRequestProps { + linkRequest: IProfileLinkRequest; +} + +function ProfileLinkRequest({ linkRequest }: IProfileLinkRequestProps) { + const navigate = useNavigate(); + const { id, requester, from_creator, to_creator, reason } = linkRequest; + const fromSite = paysites[from_creator.service]; + const toSite = paysites[to_creator.service]; + const fromURL = String( + createProfilePageURL({ + service: from_creator.service, + profileID: from_creator.id, + }) + ); + const toURL = String( + createProfilePageURL({ + service: to_creator.service, + profileID: to_creator.id, + }) + ); + + return ( + + ); +} + +export const loader = createAccountPageLoader( + async function loader({}: LoaderFunctionArgs): Promise { + const linkRequests = await fetchProfileLinkRequests(); + + return { linkRequests }; + } +); diff --git a/client/src/pages/account/notifications.html b/client/src/pages/account/notifications.html deleted file mode 100644 index bdab237..0000000 --- a/client/src/pages/account/notifications.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/timestamp.html' import timestamp %} -{% from 'account/components/notification.html' import notification_item %} - -{% block title %} - Account notificatons -{% endblock title %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/account/notifications.scss b/client/src/pages/account/notifications.scss index 6207a3f..31174e6 100644 --- a/client/src/pages/account/notifications.scss +++ b/client/src/pages/account/notifications.scss @@ -1,4 +1,4 @@ -@use "../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .site-section--account-notifications { .notifications { diff --git a/client/src/pages/account/notifications.tsx b/client/src/pages/account/notifications.tsx new file mode 100644 index 0000000..41697b2 --- /dev/null +++ b/client/src/pages/account/notifications.tsx @@ -0,0 +1,40 @@ +import { useLoaderData } from "react-router"; +import { fetchAccountNotifications } from "#api/account"; +import { INotification } from "#entities/account"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { NotificationItem } from "#entities/account"; + +interface IProps { + notifications: INotification[]; +} + +export function AccountNotificationsPage() { + const { notifications } = useLoaderData() as IProps; + const title = "Account notificatons"; + const heading = "Notificatons"; + + return ( + +
      + {notifications.length === 0 ? ( +
    • There are no notifications.
    • + ) : ( + notifications.map((notification) => ( + + )) + )} +
    +
    + ); +} + +export const loader = createAccountPageLoader( + async function loader(): Promise { + const { notifications } = await fetchAccountNotifications(); + + return { notifications }; + } +); diff --git a/client/src/pages/account/register.html b/client/src/pages/account/register.html deleted file mode 100644 index 6b276ad..0000000 --- a/client/src/pages/account/register.html +++ /dev/null @@ -1,73 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} - - -{% endblock %} diff --git a/client/src/pages/account/register.js b/client/src/pages/account/register.js deleted file mode 100644 index 0ee8c7c..0000000 --- a/client/src/pages/account/register.js +++ /dev/null @@ -1,96 +0,0 @@ -import "./register.scss"; - -/** - * @param {HTMLElement} section - */ -export function registerPage(section) { - populate_favorites(); - input_validation(); -} - -function populate_favorites() { - var input = document.getElementById("serialized-favorites"); - var favorites = localStorage.getItem("favorites"); - var to_serialize = []; - if (input && favorites) { - var artists = favorites.split(","); - artists.forEach(function (artist) { - var split = artist.split(":"); - if (split.length != 2) { - return; - } - var elem = { - service: split[0], - artist_id: split[1], - }; - to_serialize.push(elem); - }); - var serialized = JSON.stringify(to_serialize); - input.value = serialized; - } -} - - -function input_validation() { - const USERNAME_INPUT = () => document.getElementById("new-username"); - const PASSWORD_INPUT = () => document.getElementById("new-password"); - const PASSWORD_CONFIRM_INPUT = () => document.getElementById("password-confirm"); - const SUBMIT_BUTTON = () => document.getElementById("submit"); - - const USER_CHAR_COUNT = () => document.getElementById("username-char-count"); - const ALLOWED_CHARS = () => document.getElementById("username-allowed-characters"); - const PASS_CHAR_COUNT = () => document.getElementById("password-char-count"); - const PASSWORD_CONFIRM = () => document.getElementById("password-confirm-matches"); - - const USERNAME_PAT = new RegExp("^" + document.getElementById("register_form").dataset["pattern"] + "$"); - const NOT_ALLOWED_CHARS_PAT = new RegExp(`[^a-z0-9_@+.\-]`, 'g'); - - function validateInputs(e) { - let errors = false; - let username = USERNAME_INPUT().value; - let password = PASSWORD_INPUT().value; - let passwordConfirmation = PASSWORD_CONFIRM_INPUT().value; - - if (username.length < 3 || username.length > 15) { - errors = true; - USER_CHAR_COUNT().classList.add("invalid"); - } else { - USER_CHAR_COUNT().classList.remove("invalid"); - } - - if (!username.match(USERNAME_PAT)) { - errors = true; - ALLOWED_CHARS().classList.add("invalid"); - } else { - ALLOWED_CHARS().classList.remove("invalid"); - } - - if (password.length < 5) { - errors = true; - PASS_CHAR_COUNT().classList.add("invalid"); - } else { - PASS_CHAR_COUNT().classList.remove("invalid"); - } - - if (passwordConfirmation !== password || passwordConfirmation === "") { - errors = true; - PASSWORD_CONFIRM().classList.add("invalid"); - } else { - PASSWORD_CONFIRM().classList.remove("invalid"); - } - - SUBMIT_BUTTON().disabled = errors; - } - - window.addEventListener("load", (_event) => { - USERNAME_INPUT().textContent = USERNAME_INPUT().textContent.toLowerCase().replace(NOT_ALLOWED_CHARS_PAT, ""); - validateInputs(); - USERNAME_INPUT().addEventListener("input", validateInputs); - USERNAME_INPUT().addEventListener("input", (input) => { - input.target.value = input.target.value.toLowerCase().replace(NOT_ALLOWED_CHARS_PAT, ""); - }); - PASSWORD_INPUT().addEventListener("input", validateInputs); - PASSWORD_CONFIRM_INPUT().addEventListener("input", validateInputs); - }); - -} diff --git a/client/src/pages/account/register.tsx b/client/src/pages/account/register.tsx new file mode 100644 index 0000000..86ee5f4 --- /dev/null +++ b/client/src/pages/account/register.tsx @@ -0,0 +1,185 @@ +import { + ActionFunctionArgs, + redirect, + useSearchParams, +} from "react-router"; +import { createArtistsPageURL, createLoginPageURL } from "#lib/urls"; +import { getLocalStorageItem } from "#storage/local"; +import { KemonoLink } from "#components/links"; +import { PageSkeleton } from "#components/pages"; +import { FormRouter, FormSection } from "#components/forms"; +import { registerAccount } from "#entities/account"; + +const USERNAME_REGEX = /^[a-z0-9_@+.-]{3,15}$/; +const NOT_ALLOWED_CHARS_REGEX = /[^a-z0-9_@+.\-]/g; + +export function RegisterPage() { + const [searchParams] = useSearchParams(); + const title = "Register account"; + const heading = "Register Account"; + const location = + searchParams.get("location") ?? String(createArtistsPageURL()); + + return ( + +
    + Already have an account?{" "} + + Log in! + +
    + + "Register"} + > + + + + + + + + + + + + + + + + + + + + + + +
    + ); +} + +export async function action({ request }: ActionFunctionArgs) { + try { + if (request.method !== "POST") { + throw new Error(`Unknown method "${request.method}".`); + } + + const data = await request.formData(); + + const location = data.get("location") as string; + const favorites = getLegacyFavoriteProfiles(); + + let userName: string | undefined = undefined; + { + const inputValue = (data.get("username") as string | null) + ?.toLowerCase() + .replace(NOT_ALLOWED_CHARS_REGEX, "") + .trim(); + + if (!inputValue) { + throw new Error("Username is required."); + } + + if (inputValue.length < 3 || inputValue.length > 15) { + throw new Error( + "Username must be at least 3 characters and no more than 15 characters long." + ); + } + + if (!inputValue.match(USERNAME_REGEX)) { + throw new Error(`Username doesn't match pattern "${USERNAME_REGEX}".`); + } + + userName = inputValue; + } + + let password: string | undefined; + { + const inputValue = (data.get("password") as string | null)?.trim(); + + if (!inputValue) { + throw new Error("Password is required."); + } + + if (inputValue.length < 5) { + throw new Error("Password must have at least 5 characters."); + } + + password = inputValue; + } + + const confirmPassword = ( + data.get("confirm_password") as string | null + )?.trim(); + { + if (confirmPassword !== password) { + throw new Error("Passwords don't match."); + } + } + + await registerAccount(userName, password, confirmPassword, favorites); + + return redirect(location); + } catch (error) { + return error; + } +} + +function getLegacyFavoriteProfiles(): string | undefined { + const value = getLocalStorageItem("favorites"); + + if (!value) { + return; + } + + const favorites: { service: string; artist_id: string }[] = []; + const artists = value.split(","); + + for (const artist of artists) { + const split = artist.split(":"); + + if (split.length != 2) { + continue; + } + + const fav = { + service: split[0], + artist_id: split[1], + }; + + favorites.push(fav); + } + + if (favorites.length === 0) { + return; + } + + return JSON.stringify(favorites); +} diff --git a/client/src/pages/all_dms.html b/client/src/pages/all_dms.html deleted file mode 100644 index a5ca44b..0000000 --- a/client/src/pages/all_dms.html +++ /dev/null @@ -1,50 +0,0 @@ -{% extends 'components/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/dm.html' import dm_card %} -{% from 'components/ads.html' import slider_ad, header_ad %} - -{% block content %} -{% call site.section("all-dms", title="DMs") %} - {{ slider_ad() }} - {{ header_ad() }} -
    - {% include 'components/paginator.html' %} -
    - - -
    -
    - - {% call card_list("phone") %} - {% for dm in props.dms %} - {{ dm_card(dm, artist=dm|attr("artist") or {}, is_global=True) }} - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no DMs. -

    -
    - {% endfor %} - {% endcall %} - -
    - {% include 'components/paginator.html' %} -
    -{% endcall %} -{% endblock %} diff --git a/client/src/pages/all_dms.scss b/client/src/pages/all_dms.scss index 2ef8f97..f18a71c 100644 --- a/client/src/pages/all_dms.scss +++ b/client/src/pages/all_dms.scss @@ -1,4 +1,4 @@ -@use "../css/config/variables" as *; +@use "../css/config/variables/sass" as *; .site-section--all-dms { .no-results { diff --git a/client/src/pages/all_dms.tsx b/client/src/pages/all_dms.tsx new file mode 100644 index 0000000..0e31044 --- /dev/null +++ b/client/src/pages/all_dms.tsx @@ -0,0 +1,154 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createDMsPageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { fetchDMs } from "#api/dms"; +import { PageSkeleton } from "#components/pages"; +import { HeaderAd, SliderAd } from "#components/advs"; +import { Paginator } from "#components/pagination"; +import { CardList, DMCard } from "#components/cards"; +import { ButtonSubmit, FormRouter } from "#components/forms"; +import { IApprovedDM } from "#entities/dms"; +import { useRef, useState } from "react"; + +interface IProps { + query?: string; + count: number; + offset?: number; + dms: IApprovedDM[]; +} + +export function DMsPage() { + const { query, count, dms, offset } = useLoaderData() as IProps; + const [isLoading, setIsLoading] = useState(false); + const title = "DMs"; + const heading = "DMs"; + + return ( + + + + +
    + setIsLoading(loading)} + /> + String(createDMsPageURL(offset, query))} + /> +
    + + + {count === 0 ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no DMs.

    +
    + ) : ( + dms.map((dm) => ( + + )) + )} +
    + +
    + String(createDMsPageURL(offset, query))} + /> +
    +
    + ); +} + +interface ISearchFormProps + extends Pick { + onLoadingChange: (loading: boolean) => void; +} + +function SearchForm({ query, onLoadingChange }: ISearchFormProps) { + const timeoutRef = useRef(null); + + const onInputChange = (e: React.ChangeEvent) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + + const target = e.currentTarget as HTMLInputElement; + + onLoadingChange(true); + + timeoutRef.current = setTimeout(() => { + if (target.form) target.form.requestSubmit(); + onLoadingChange(false); + }, 1000); + }; + + return ( + + {(state) => ( + <> +
    + + { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + onLoadingChange(false); + }} + > + + +
    + + )} +
    + ); +} + +export async function loader({ request }: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let offset: number | undefined = undefined; + { + const inputOffset = searchParams.get("o")?.trim(); + if (inputOffset) { + offset = parseOffset(inputOffset); + } + } + + const query = searchParams.get("q")?.trim(); + + const { props } = await fetchDMs(offset, query); + const { count, dms } = props; + + return { + count, + offset, + query, + dms, + }; +} diff --git a/client/src/pages/artist/announcements.html b/client/src/pages/artist/announcements.html deleted file mode 100644 index 29a976f..0000000 --- a/client/src/pages/artist/announcements.html +++ /dev/null @@ -1,77 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/headers.html" import user_header %} -{% from "components/card_list.html" import card_list %} -{% from "components/cards/dm.html" import dm_card %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = "Announcements of " ~ props.artist.name ~ " from " ~ paysite.title ~ " | " ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -
    - {{ user_header(request, props) }} -
    - {% include "components/tabs.html" %} -
    -{#
    #} -{# {% include "components/tabs.html" %}#} -{# {% include "components/paginator.html" %}#} -{##} -{#
    #} -{# #} -{# #} -{#
    #} - - {% call card_list("phone") %} - {% for announcement in props.announcements %} - {{ dm_card(announcement) }} - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no Announcements for your query. -

    -
    - {% endfor %} - {% endcall %} - -{#
    #} -{# {% include "components/paginator.html" %}#} -{#
    #} -
    -{% endblock content %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} \ No newline at end of file diff --git a/client/src/pages/artist/dms.html b/client/src/pages/artist/dms.html deleted file mode 100644 index f247812..0000000 --- a/client/src/pages/artist/dms.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/cards/dm.html' import dm_card %} -{% from 'components/headers.html' import user_header %} -{% from 'components/card_list.html' import card_list %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = 'DMs of ' ~ props.artist.name ~ ' from ' ~ paysite.title ~ ' | ' ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock title %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -
    - {{ user_header(request, props) }} -
    - {% include "components/tabs.html" %} -
    - {% call card_list("phone") %} - {% for dm in props.dms %} - {{ dm_card(dm, artist=dm|attr("artist") or {}) }} - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no DMs for your query. -

    -
    - {% endfor %} - {% endcall %} -
    -{% endblock content %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} diff --git a/client/src/pages/artist/fancards.html b/client/src/pages/artist/fancards.html deleted file mode 100644 index cd5f5fe..0000000 --- a/client/src/pages/artist/fancards.html +++ /dev/null @@ -1,64 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/headers.html' import user_header %} - -{% set page_title = 'Fancards of ' ~ artist.name ~ ' | ' ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock title %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -
    - {{ user_header(request, props) }} -
    - {% include 'components/tabs.html' %} -
    -
    - {% for fancard in fancards %} -
    - Added {{ (fancard.added|simple_date)[:7] }} - - - -
    - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no uploads for your query. -

    -
    - {% endfor %} -
    -
    -{% endblock content %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} \ No newline at end of file diff --git a/client/src/pages/artist/linked_accounts.html b/client/src/pages/artist/linked_accounts.html deleted file mode 100644 index 08e5aae..0000000 --- a/client/src/pages/artist/linked_accounts.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/card_list.html" import card_list %} -{% from "components/headers.html" import user_header %} -{% from "components/cards/user.html" import user_card %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = "Linked accounts for " ~ props.artist.name ~ " on " ~ paysite.title ~ " | " ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} - -{% endblock %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} \ No newline at end of file diff --git a/client/src/pages/artist/linked_accounts.js b/client/src/pages/artist/linked_accounts.js deleted file mode 100644 index 7cc1f31..0000000 --- a/client/src/pages/artist/linked_accounts.js +++ /dev/null @@ -1,32 +0,0 @@ -export function viewLinkedAccountsPage() { - if (localStorage.getItem("role") !== "administrator") return; - - window.addEventListener("DOMContentLoaded", (_e) => { - Array.from(document.querySelectorAll(".user-card")).forEach(card => { - let btn = document.createElement("button"); - btn.textContent = "✗"; - btn.classList = "remove-link"; - btn.addEventListener("click", async (e) => { - // apparently it only actually stops if you use both: - e.preventDefault(); - e.stopPropagation(); - - let id = card.dataset["id"]; - let service = card.dataset["service"]; - - if (confirm(`Delete the connection for user #${id} on ${service}?`)) { - if (await deleteLinkedAccount(service, id)) { - card.remove(); - } else { - alert("Error"); - } - } - }); - card.appendChild(btn); - }); - }); -} - -async function deleteLinkedAccount(service, id) { - return (await fetch(`/${service}/user/${id}/links`, { method: "DELETE" })).status == 204; -} diff --git a/client/src/pages/artist/new_linked_account.html b/client/src/pages/artist/new_linked_account.html deleted file mode 100644 index 6b1dfa0..0000000 --- a/client/src/pages/artist/new_linked_account.html +++ /dev/null @@ -1,84 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/headers.html" import user_header %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = "Link a new account to " ~ props.artist.name ~ " on " ~ paysite.title ~ " | " ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} - -{% endblock %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} diff --git a/client/src/pages/artist/new_linked_account.js b/client/src/pages/artist/new_linked_account.js deleted file mode 100644 index e7b1830..0000000 --- a/client/src/pages/artist/new_linked_account.js +++ /dev/null @@ -1,111 +0,0 @@ -import { debounce } from "../artists.js"; -import { kemonoAPI } from "@wp/api"; -import { freesites, paysites } from "@wp/utils"; -import { BANNERS_PREPEND, ICONS_PREPEND } from "@wp/env/env-vars"; - -/** - * @type {Array} - */ -let ALL_CREATORS; - -export function newLinkedAccountPage() { - document.getElementById("service").addEventListener("input", debounce(testUser, 300)) - document.getElementById("creator_name").addEventListener("input", debounce(testUser, 300)) - document.getElementById("reason").addEventListener("input", debounce(validateReason, 300)) - testUser(); -} - -function validateReason() { - let reason = document.getElementById("reason"); - - if (reason.value.length > 140) { - reason.title = "Too long (140 max)"; - reason.classList.add("invalid"); - } else { - reason.title = ""; - reason.classList.remove("invalid"); - } -} - -async function testUser() { - if (ALL_CREATORS === undefined) { - ALL_CREATORS = await kemonoAPI.api.creators(); - } - - const CURRENT_ARTIST_ID = document.querySelector("meta[name='id']").content; - const CURRENT_ARTIST_SERVICE = document.querySelector("meta[name='service']").content; - let service = document.getElementById("service").value; - let creatorName = document.getElementById("creator_name").value.toLowerCase(); - let button = document.getElementById("submit"); - let resultBox = document.getElementById("lookup-result"); - - button.disabled = true; - - let items = ALL_CREATORS.filter(item => { - console.log(item); - return !(item.service === CURRENT_ARTIST_SERVICE && item.id === CURRENT_ARTIST_ID) - && (creatorName? item.name.toLowerCase().includes(creatorName) : true) - && (service !== "all"? item.service === service : true); - }).slice(0, 20).map(createCard); - console.log(items); - resultBox.replaceChildren.apply(resultBox, items); -} - -function createCard({id, name, service, indexed, updated, favorited}) { - // would like to use initUserCardFromScratch here, but it doesn't work. - // why would components be reuseable, after all? - - // so I'll just recreate the entire function here! - let updatedDate; - - if (updated || indexed) { - updatedDate = new Date((updated || indexed) * 1000).toISOString(); - } - - let profileIcon = freesites.kemono.user.icon(service, id); - let profileBanner = freesites.kemono.user.banner(service, id); - - profileIcon = ICONS_PREPEND + profileIcon; - profileBanner = BANNERS_PREPEND + profileBanner; - - let div = document.createElement("div"); - div.innerHTML = ` - -
    -
    - - - - - -
    -
    - -
    -
    - ${paysites[service].title} -
    - -
    - ${name} -
    - -
    - -
    -
    -
    - `; - div.addEventListener("click", (e) => { - e.preventDefault(); - - Array.from(document.querySelectorAll(".user-card.selected")).forEach(item => item.classList.remove("selected")); - - div.children[0].classList.add("selected"); - document.querySelector("#new_link_form #creator").value = `${service}/${id}`; - document.querySelector("#new_link_form #submit").disabled = false; - }); - return div; -} diff --git a/client/src/pages/artist/shares.html b/client/src/pages/artist/shares.html deleted file mode 100644 index 8079e93..0000000 --- a/client/src/pages/artist/shares.html +++ /dev/null @@ -1,56 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/cards/share.html' import share_card %} -{% from 'components/headers.html' import user_header %} -{% from 'components/card_list.html' import card_list %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = 'DMs of ' ~ props.artist.name ~ ' from ' ~ paysite.title ~ ' | ' ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock title %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -
    - {{ user_header(request, props) }} -
    - {% include 'components/tabs.html' %} -
    - {% call card_list() %} - {% for dm in results %} - {{ share_card(dm) }} - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no uploads for your query. -

    -
    - {% endfor %} - {% endcall %} -
    -{% endblock content %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} diff --git a/client/src/pages/artist/tags.html b/client/src/pages/artist/tags.html deleted file mode 100644 index 0244815..0000000 --- a/client/src/pages/artist/tags.html +++ /dev/null @@ -1,66 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/headers.html" import user_header %} -{% from "components/card_list.html" import card_list %} -{% from "components/cards/dm.html" import dm_card %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = "Announcements of " ~ props.artist.name ~ " from " ~ paysite.title ~ " | " ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -
    - {{ user_header(request, props) }} -
    - {% include "components/tabs.html" %} -
    -
    - {% for tag in tags %} - - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no Announcements for your query. -

    -
    - {% endfor %} -
    -
    -{% endblock %} - - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} diff --git a/client/src/pages/artists.html b/client/src/pages/artists.html deleted file mode 100644 index bac121f..0000000 --- a/client/src/pages/artists.html +++ /dev/null @@ -1,101 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/image_link.html' import image_link %} -{% from 'components/fancy_image.html' import fancy_image %} -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/user.html' import user_card, user_card_header, user_card_skeleton %} -{% from 'components/ads.html' import slider_ad, header_ad, footer_ad %} - -{% block content %} - {{ slider_ad() }} -
    -
    - - Loading creators... please wait! - -
    -
    -
    - - - Leave blank to list all -
    -
    - - -
    -
    - - - -
    -
    - {% if props.display %} -
    -

    - Displaying {{ props.display }} -

    -
    - {% endif %} -
    - {% include 'components/paginator.html' %} -
    - {{ header_ad() }} - {% call card_list('phone') %} - {% for user in results %} - {{ user_card( - user, - is_updated=base.get('sort_by') == 'updated', - is_indexed=base.get('sort_by') == 'indexed', - is_count=base.get('sort_by') == 'favorited', - single_of='favorite', - plural_of='favorites' - ) }} - {% else %} -

    - No {{ g.artists_or_creators|lower }} found for your query. -

    - {% endfor %} - {% endcall %} -
    - {% include 'components/paginator.html' %} -
    - {{ footer_ad() }} -
    -{% endblock %} - -{% block components %} - {{ image_link("") }} - {{ fancy_image("") }} - {{ user_card_skeleton() }} -{% endblock components %} diff --git a/client/src/pages/artists.js b/client/src/pages/artists.js deleted file mode 100644 index c5c4a5e..0000000 --- a/client/src/pages/artists.js +++ /dev/null @@ -1,400 +0,0 @@ -import { kemonoAPI } from "@wp/api"; -import { CardList, registerPaginatorKeybinds, UserCard } from "@wp/components"; -import { isLoggedIn } from "@wp/js/account"; -import { findFavouriteArtist } from "@wp/js/favorites"; - -/** - * @type {KemonoAPI.User[]} - */ -let creators; -/** - * @type {KemonoAPI.User[]} - */ -let filteredCreators; -let skip = - parseInt( - window.location.hash - .substring(1) - .split("&") - .find((e) => e.split("=")[0] === "o") - ?.split("=")[1], - ) || 0; -let limit = 50; -const TOTAL_BUTTONS = 5; -const OPTIONAL_BUTTONS = TOTAL_BUTTONS - 2; -const MANDATORY_BUTTONS = TOTAL_BUTTONS - OPTIONAL_BUTTONS; - -// generic debounce function, idk jsdoc, figure it out :) -export function debounce(func, timeout = 300) { - let timer; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => { - func.apply(this, args); - }, timeout); - }; -} - -/** - * @param {HTMLElement} section - */ -export async function artistsPage(section) { - /** - * @type {HTMLHeadingElement} - */ - const displayStatus = document.getElementById("display-status"); - /** - * @type {HTMLDivElement} - */ - const loadingStatus = document.getElementById("loading"); - /** - * @type {HTMLFormElement} - */ - const searchForm = document.forms["search-form"]; - /** - * @type {HTMLSelectElement} - */ - const orderSelect = searchForm.elements["order"]; - /** - * @type {HTMLSelectElement} - */ - const serviceSelect = searchForm.elements["service"]; - /** - * @type {HTMLSelectElement} - */ - const sortSelect = searchForm.elements["sort_by"]; - /** - * @type {HTMLInputElement} - */ - const queryInput = searchForm.elements["q"]; - /** - * @type {HTMLDivElement} - */ - const cardListElement = section.querySelector(".card-list"); - const { cardList, cardContainer } = CardList(cardListElement); - const pagination = { - top: document.getElementById("paginator-top"), - bottom: document.getElementById("paginator-bottom"), - }; - - Array.from(cardContainer.children).forEach(async (userCard) => { - const { id, service } = userCard.dataset; - const isFav = isLoggedIn && (await findFavouriteArtist(id, service)); - - if (isFav) { - userCard.classList.add("user-card--fav"); - } - }); - section.addEventListener("click", async (event) => { - /** - * @type {HTMLAnchorElement} - */ - const button = event.target; - const isB = button.parentElement.classList.contains("paginator-button-ident"); - if ( - (button.classList.contains("paginator-button-ident") && button.dataset && button.dataset.value) || - (isB && button.parentElement.dataset && button.parentElement.dataset.value) - ) { - event.preventDefault(); - skip = Number(isB ? button.parentElement.dataset.value : button.dataset.value); - window.location.hash = "o=" + skip; - filterCards(orderSelect.value, serviceSelect.value, sortSelect.value, queryInput.value); - await loadCards(displayStatus, cardContainer, pagination, sortSelect.value); - } - }); - - searchForm.addEventListener("submit", (event) => event.preventDefault()); - queryInput.addEventListener( - "change", - handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination), - ); - // 300 ms delay between each keystroke, trigger a new search on each new letter added or removed - // debounce lets you do this by waiting for the user to stop typing first - queryInput.addEventListener( - "keydown", - debounce( - handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination), - 300, - ), - ); - serviceSelect.addEventListener( - "change", - handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination), - ); - sortSelect.addEventListener( - "change", - handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination), - ); - orderSelect.addEventListener( - "change", - handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination), - ); - - await retrieveArtists(loadingStatus); - handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination)(null); - registerPaginatorKeybinds(); -} - -/** - * @param {HTMLSelectElement} orderSelect - * @param {HTMLSelectElement} serviceSelect - * @param {HTMLSelectElement} sortSelect - * @param {HTMLInputElement} queryInput - * @param {HTMLDivElement} displayStatus - * @param {HTMLDivElement} cardContainer - * @param {{ top: HTMLElement, bottom: HTMLElement }} pagination - * @return {(event: Event) => void} - */ -function handleSearch(orderSelect, serviceSelect, sortSelect, queryInput, displayStatus, cardContainer, pagination) { - return async (event) => { - filterCards(orderSelect.value, serviceSelect.value, sortSelect.value, queryInput.value); - await loadCards(displayStatus, cardContainer, pagination, sortSelect.value); - }; -} - -// localeCompare isn't slow itself, but this is still faster and we're processing a LOT of data here! -// better get any speed gains we can -function fastCompare(a, b) { - return a < b ? -1 : a > b ? 1 : 0; -} - -/** - * @param {string} order - * @param {string} service - * @param {string} sortBy - * @param {string} query - */ -function filterCards(order, service, sortBy, query) { - // fixme: creators is null/undefined sometimes - filteredCreators = creators.slice(0); - - if (order === "desc") { - filteredCreators.reverse(); - } - - filteredCreators = filteredCreators - .filter((creator) => creator.service === (service || creator.service)) - .sort((a, b) => { - if (order === "asc") { - return sortBy === "indexed" - ? a.parsedIndexed - b.parsedIndexed - : sortBy === "updated" - ? a.parsedUpdated - b.parsedUpdated - : fastCompare(a[sortBy], b[sortBy]); - } else { - return sortBy === "indexed" - ? b.parsedIndexed - a.parsedIndexed - : sortBy === "updated" - ? b.parsedUpdated - a.parsedUpdated - : fastCompare(b[sortBy], a[sortBy]); - } - }) - .filter((creator) => { - return creator.name.match(new RegExp(query.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "i")); - }); -} - -function _paginatorButton(content, skip, className = "") { - if (typeof skip === "string") { - className = skip; - skip = null; - } - if (typeof skip === "number") - return `${content}`; - return `
  • ${content}
  • `; -} - -function createPaginator() { - const count = filteredCreators.length; - - const currentCeilingOfRange = skip + limit < count ? skip + limit : count; - - const currPageNum = Math.ceil((skip + limit) / limit); - const totalPages = Math.ceil(count / limit); - const numBeforeCurrPage = - totalPages < TOTAL_BUTTONS || currPageNum < TOTAL_BUTTONS - ? currPageNum - 1 - : totalPages - currPageNum < TOTAL_BUTTONS - ? TOTAL_BUTTONS - 1 + (TOTAL_BUTTONS - (totalPages - currPageNum)) - : TOTAL_BUTTONS - 1; - const basePageNum = Math.max(currPageNum - numBeforeCurrPage - 1, 1); - const showFirstPostsButton = basePageNum > 1; - const showLastPostsButton = - totalPages - currPageNum > - TOTAL_BUTTONS + (currPageNum - basePageNum < TOTAL_BUTTONS ? TOTAL_BUTTONS - (currPageNum - basePageNum) : 0); - const optionalBeforeButtons = - currPageNum - - MANDATORY_BUTTONS - - (totalPages - currPageNum < MANDATORY_BUTTONS ? MANDATORY_BUTTONS - (totalPages - currPageNum) : 0); - const optionalAfterButtons = - currPageNum + - MANDATORY_BUTTONS + - (currPageNum - basePageNum < MANDATORY_BUTTONS ? MANDATORY_BUTTONS - (currPageNum - basePageNum) : 0); - - const range = createRange(0, TOTAL_BUTTONS * 2 + 1); - - const paginator = - count > limit - ? ` - Showing ${skip + 1} - ${currentCeilingOfRange} of ${count} - - - ${ - showFirstPostsButton || showLastPostsButton - ? showFirstPostsButton - ? _paginatorButton("<<", 0) - : _paginatorButton( - "<<", - `pagination-button-disabled${currPageNum - MANDATORY_BUTTONS - 1 ? " pagination-desktop" : ""}`, - ) - : `` - } - ${ - showFirstPostsButton - ? "" - : currPageNum - MANDATORY_BUTTONS - 1 - ? _paginatorButton("<<", 0, "pagination-mobile") - : totalPages - currPageNum > MANDATORY_BUTTONS && !showLastPostsButton - ? _paginatorButton("<<", "pagination-button-disabled pagination-mobile") - : "" - } - ${ - currPageNum > 1 - ? _paginatorButton("<", (currPageNum - 2) * limit, "prev") - : _paginatorButton("<", "pagination-button-disabled") - } - ${range - .map((page) => - page + basePageNum && page + basePageNum <= totalPages - ? _paginatorButton( - page + basePageNum, - page + basePageNum != currPageNum ? (page + basePageNum - 1) * limit : null, - (page + basePageNum < optionalBeforeButtons || page + basePageNum > optionalAfterButtons) && - page + basePageNum != currPageNum - ? "pagination-button-optional" - : page + basePageNum == currPageNum - ? "pagination-button-disabled pagination-button-current" - : page + basePageNum == currPageNum + 1 - ? "pagination-button-after-current" - : "", - ) - : "", - ) - .join("\n")} - ${ - currPageNum < totalPages - ? _paginatorButton(">", currPageNum * limit, "next") - : _paginatorButton(">", `pagination-button-disabled${totalPages ? " pagination-button-after-current" : ""}`) - } - ${ - showFirstPostsButton || showLastPostsButton - ? showLastPostsButton - ? _paginatorButton(">>", (totalPages - 1) * limit) - : _paginatorButton( - ">>", - `pagination-button-disabled${totalPages - currPageNum > MANDATORY_BUTTONS ? " pagination-desktop" : ""}`, - ) - : "" - } - ${ - showLastPostsButton - ? "" - : totalPages - currPageNum > MANDATORY_BUTTONS - ? _paginatorButton(">>", (totalPages - 1) * limit, "pagination-mobile") - : currPageNum > OPTIONAL_BUTTONS && !showFirstPostsButton - ? _paginatorButton(">>", "pagination-button-disabled pagination-mobile") - : "" - } - - ` - : ""; - - return paginator; -} - -/** - * @param {HTMLDivElement} displayStatus - * @param {HTMLDivElement} cardContainer - * @param {{ top: HTMLElement, bottom: HTMLElement }} pagination - * @param {String} sortBy - */ -async function loadCards(displayStatus, cardContainer, pagination, sortBy) { - displayStatus.textContent = "Displaying search results"; - pagination.top.innerHTML = createPaginator(); - pagination.bottom.innerHTML = createPaginator(); - /** - * @type {[ HTMLDivElement, HTMLElement ]} - */ - const [...cards] = cardContainer.children; - cards.forEach((card) => { - card.remove(); - }); - - if (filteredCreators.length === 0) { - const paragraph = document.createElement("p"); - - paragraph.classList.add("subtitle", "card-list__item--no-results"); - paragraph.textContent = "No artists found for your query."; - cardContainer.appendChild(paragraph); - return; - } else { - const fragment = document.createDocumentFragment(); - - for await (const user of filteredCreators.slice(skip, skip + limit)) { - const userIsCount = sortBy === "favorited"; - const userIsIndexed = sortBy === "indexed"; - const userIsUpdated = sortBy === "updated"; - const userCard = UserCard(null, user, userIsCount, userIsUpdated, userIsIndexed); - const isFaved = isLoggedIn && (await findFavouriteArtist(user.id, user.service)); - - if (isFaved) { - userCard.classList.add("user-card--fav"); - } - - fragment.appendChild(userCard); - } - - cardContainer.appendChild(fragment); - } -} - -/** - * @param {HTMLDivElement} loadingStatus - */ -async function retrieveArtists(loadingStatus) { - try { - const artists = await kemonoAPI.api.creators(); - - if (!artists) { - return null; - } - - for (const artist of artists) { - // preemptively do it here, it's taxing to parse a date string then convert it to a unix timestamp in milliseconds - // this way we only have to do it once after fetching and none for sorting - artist.parsedIndexed = artist.indexed * 1000; - artist.parsedUpdated = artist.updated * 1000; - artist.indexed = new Date(artist.parsedIndexed).toISOString(); - artist.updated = new Date(artist.parsedUpdated).toISOString(); - } - - loadingStatus.innerHTML = ""; - creators = artists; - filteredCreators = artists; - } catch (error) { - console.error(error); - } -} - -/** - * @param {number} start - * @param {number} end - */ -function createRange(start, end) { - const length = end - start; - const range = Array.from({ length }, (_, index) => start + index); - - return range; -} diff --git a/client/src/pages/authentication/logout.tsx b/client/src/pages/authentication/logout.tsx new file mode 100644 index 0000000..cc32aa2 --- /dev/null +++ b/client/src/pages/authentication/logout.tsx @@ -0,0 +1,9 @@ +import { redirect } from "react-router"; +import { createArtistsPageURL } from "#lib/urls"; +import { logoutAccount } from "#entities/account"; + +export async function loader() { + await logoutAccount(); + + return redirect(String(createArtistsPageURL())); +} diff --git a/client/src/pages/components/_index.js b/client/src/pages/components/_index.js deleted file mode 100644 index 5414576..0000000 --- a/client/src/pages/components/_index.js +++ /dev/null @@ -1,10 +0,0 @@ -export { LoadingIcon } from "./loading_icon"; -export { CardList } from "./card_list"; -export { PostCard, UserCard } from "./cards/_index.js"; -export { FancyImage } from "./fancy_image"; -export { FancyLink } from "./links"; -export { ImageLink } from "./image_link"; -export { showTooltip, registerMessage } from "./tooltip"; -export { initShell } from "./shell"; -export { Timestamp } from "./timestamp"; -export { registerPaginatorKeybinds } from "./paginator"; diff --git a/client/src/pages/components/_index.scss b/client/src/pages/components/_index.scss deleted file mode 100644 index 67a5821..0000000 --- a/client/src/pages/components/_index.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "site"; -@use "fancy_image"; -@use "links"; -@use "timestamp"; -@use "card_list"; -@use "cards"; -@use "loading_icon"; -@use "buttons"; -@use "image_link"; -@use "shell"; -@use "tooltip"; -@use "paginator_new"; -@use "navigation"; -@use "lists"; -@use "importer_states"; -@use "file_hash_search"; diff --git a/client/src/pages/components/ads.html b/client/src/pages/components/ads.html deleted file mode 100644 index 50bfd5a..0000000 --- a/client/src/pages/components/ads.html +++ /dev/null @@ -1,29 +0,0 @@ -{% macro header_ad() %} - {% if g.header_ad %} -
    - {{ g.header_ad|safe }} -
    - {% endif %} -{% endmacro %} - -{% macro middle_ad() %} - {% if g.middle_ad %} -
    - {{ g.middle_ad|safe }} -
    - {% endif %} -{% endmacro %} - -{% macro footer_ad() %} - {% if g.footer_ad %} -
    - {{ g.footer_ad|safe }} -
    - {% endif %} -{% endmacro %} - -{% macro slider_ad() %} - {% if g.slider_ad %} - {{ g.slider_ad|safe }} - {% endif %} -{% endmacro %} diff --git a/client/src/pages/components/buttons.html b/client/src/pages/components/buttons.html deleted file mode 100644 index 38b758c..0000000 --- a/client/src/pages/components/buttons.html +++ /dev/null @@ -1,7 +0,0 @@ -{%- macro button(text, class_name=none, is_focusable=true) -%} - -{%- endmacro -%} diff --git a/client/src/pages/components/card_list.html b/client/src/pages/components/card_list.html deleted file mode 100644 index 1bc991c..0000000 --- a/client/src/pages/components/card_list.html +++ /dev/null @@ -1,11 +0,0 @@ -{% macro card_list(layout='legacy', class_name=none) %} -
    -
    -
    -
    - {{ caller() }} -
    -
    -{% endmacro %} diff --git a/client/src/pages/components/card_list.js b/client/src/pages/components/card_list.js deleted file mode 100644 index 10c2adb..0000000 --- a/client/src/pages/components/card_list.js +++ /dev/null @@ -1,116 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -/** - * TODO: layout switch button. - * @param {HTMLElement} element - * @param {string} layout - */ -export function CardList(element = null, layout = "feature") { - const cardList = element ? initFromElement(element) : initFromScratch(); - let currentLayout = layout; - - - let thumbSizeSetting = undefined; - try { - let cookies = getCookies(); - thumbSizeSetting = parseInt(cookies?.thumbSize); - thumbSizeSetting = isNaN(thumbSizeSetting) ? undefined : thumbSizeSetting; - } catch (e) { - return cardList; - } - if (!thumbSizeSetting){ - addCookie("thumbSize","180", 399) - } - - let defaultThumbSize = 180; - let thumbSize = parseInt(thumbSizeSetting) === parseInt(defaultThumbSize) ? undefined: thumbSizeSetting; - - let cardListEl = document.querySelector('.card-list__items'); - - if (cardListEl.parentNode.classList.contains("card-list--phone")){ - return cardList; - } - - window.addEventListener('resize', () => updateThumbsizes(cardListEl, defaultThumbSize, thumbSize)); - updateThumbsizes(cardListEl, defaultThumbSize, thumbSize) - - return cardList; -} - -/** - * @param {HTMLElement} element - */ -function initFromElement(element) { - /** - * @type {HTMLDivElement} - */ - const cardContainer = element.querySelector(".card-list__items"); - /** - * @type {NodeListOf} - */ - const itemListElements = element.querySelectorAll(".card-list__items > *"); - - return { - cardList: element, - cardContainer, - cardItems: Array.from(itemListElements), - }; -} - -function initFromScratch() { - /** - * @type {HTMLElement} - */ - const cardList = createComponent("card-list"); - /** - * @type {HTMLDivElement} - */ - const cardContainer = cardList?.querySelector(".card-list__items"); - /** - * @type {HTMLElement[]} - */ - const cardItems = []; - - return { - cardList, - cardContainer, - cardItems, - }; -} - - -function getCookies(){ - return document.cookie.split(';').reduce((cookies, cookie) => (cookies[cookie.split('=')[0].trim()] = decodeURIComponent(cookie.split('=')[1]), cookies), {}); -} - -function setCookie(name, value, daysToExpire) { - const date = new Date(); - date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000)); - const expires = "expires=" + date.toUTCString(); - document.cookie = name + "=" + value + "; " + expires + ";path=/"; -} -function addCookie(name, newValue, daysToExpire) { - const existingCookie = document.cookie - .split(';') - .find(cookie => cookie.trim().startsWith(name + '=')); - - if (!existingCookie) { - setCookie(name, newValue, daysToExpire); - } -} -function updateThumbsizes(element, defaultSize, thumbSizeSetting){ - let thumbSize = thumbSizeSetting? thumbSizeSetting : defaultSize; - if (!thumbSizeSetting){ - let viewportWidth = window.innerWidth; - let offset = 24; - let viewportWidthExcludingMargin = viewportWidth - offset; - let howManyFit = viewportWidthExcludingMargin/thumbSize; - - if ( howManyFit < 2.0 && 1.5 < howManyFit) { - thumbSize = viewportWidthExcludingMargin / 2; - } else if( howManyFit > 12 ){ - thumbSize = defaultSize*1.5; - } - } - element.style.setProperty('--card-size', `${thumbSize}px`); -} \ No newline at end of file diff --git a/client/src/pages/components/cards/_index.js b/client/src/pages/components/cards/_index.js deleted file mode 100644 index 49f2aa1..0000000 --- a/client/src/pages/components/cards/_index.js +++ /dev/null @@ -1,151 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; -import { FancyImage, Timestamp } from "@wp/components"; -import { freesites, paysites } from "@wp/utils"; -import { BANNERS_PREPEND, ICONS_PREPEND } from "@wp/env/env-vars"; - -/** - * @param {HTMLElement} element - * @param {KemonoAPI.Post} post - */ -export function PostCard(element = null, post = {}) { - const postCard = element ? initFromElement(element) : initFromScratch(post); - - const view = postCard.postCardElement.querySelector(".post-card__view"); - - if (view) { - /** - * @type {HTMLButtonElement} - */ - const button = view.querySelector(".post-card__button"); - /** - * @type {HTMLAnchorElement} - */ - const link = postCard.postCardElement.querySelector(".post-card__link"); - - button.addEventListener("click", handlePostView(link)); - } - - return postCard; -} - -/** - * @param {HTMLElement} element - */ -function initFromElement(element) { - const { id, service, user } = element.dataset; - return { - postCardElement: element, - postID: id, - service, - userID: user, - }; -} - -/** - * @param {KemonoAPI.Post} post - */ -function initFromScratch(post) { - /** - * @type {HTMLElement} - */ - const postCardElement = createComponent("post-card"); - - return { - postCardElement, - postID: post.id, - service: post.service, - userID: post.user, - }; -} - -/** - * @param {HTMLAnchorElement} link - * @returns {(event: MouseEvent) => void} - */ -function handlePostView(link) { - return (event) => { - link.focus(); - }; -} - -/** - * @param {HTMLElement} element - * @param {KemonoAPI.User} user - * @param {boolean} isCount - * @param {boolean} isDate - * @param {string} className - */ -export function UserCard(element, user = {}, isCount = false, isUpdated = false, isIndexed = false, className = null) { - const userCard = element - ? initUserCardFromElement(element) - : initUserCardFromScratch(user, isCount, isUpdated, isIndexed, className); - - return userCard; -} - -/** - * @param {HTMLElement} element - */ -function initUserCardFromElement(element) { - const userCard = element; - - return userCard; -} - -/** - * @param {KemonoAPI.User} user - * @param {boolean} isCount - * @param {boolean} isDate - * @param {string} className - */ -function initUserCardFromScratch(user, isCount, isUpdated, isIndexed, className) { - let profileIcon = freesites.kemono.user.icon(user.service, user.id); - let profileBanner = freesites.kemono.user.banner(user.service, user.id); - const profileLink = freesites.kemono.user.profile(user.service, user.id); - /** - * @type {HTMLElement} - */ - - profileIcon = ICONS_PREPEND + profileIcon; - profileBanner = BANNERS_PREPEND + profileBanner; - - const userCard = createComponent("user-card"); - userCard.href = profileLink; - userCard.style.backgroundImage = `linear-gradient(rgb(0 0 0 / 50%), rgb(0 0 0 / 80%)), url(${profileBanner})`; - - const imageLink = FancyImage(null, profileIcon, profileIcon, true, "", "user-card__user-icon"); - - const userIcon = userCard.querySelector(".user-card__icon"); - const userName = userCard.querySelector(".user-card__name"); - const userService = userCard.querySelector(".user-card__service"); - const userCount = userCard.querySelector(".user-card__count"); - const userUpdated = userCard.querySelector(".user-card__updated"); - - userIcon.appendChild(imageLink); - userName.textContent = user.name; - if (user.name.length >= 24) { - userName.title = user.name; - } - - userService.textContent = paysites[user.service].title; - userService.style.backgroundColor = paysites[user.service].color; - - if (className) { - userCard.classList.add(className); - } - - if (isCount) { - userCount.innerHTML = `${user.favorited} favorites`; - } else { - userCount.remove(); - } - - if (isUpdated || isIndexed) { - const timestamp = Timestamp(null, isUpdated ? user.updated : user.indexed); - userUpdated.appendChild(timestamp); - } else { - userUpdated.remove(); - } - - return userCard; -} diff --git a/client/src/pages/components/cards/account.html b/client/src/pages/components/cards/account.html deleted file mode 100644 index ec3b891..0000000 --- a/client/src/pages/components/cards/account.html +++ /dev/null @@ -1,29 +0,0 @@ -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/links.html' import kemono_link %} - -{% macro account_card(account) %} - {% set account_url = 'account/administrator/accounts/' ~ account.id %} - - -{% endmacro %} diff --git a/client/src/pages/components/cards/account.scss b/client/src/pages/components/cards/account.scss deleted file mode 100644 index 7c003a1..0000000 --- a/client/src/pages/components/cards/account.scss +++ /dev/null @@ -1,9 +0,0 @@ -@use "../../../css/sass-mixins" as mixins; - -.account-card { - @include mixins.article-card(); - - &__body { - flex: 1 1 auto; - } -} diff --git a/client/src/pages/components/cards/base.html b/client/src/pages/components/cards/base.html deleted file mode 100644 index ef39246..0000000 --- a/client/src/pages/components/cards/base.html +++ /dev/null @@ -1,26 +0,0 @@ -{# base parts of the card #} -{# these macros can only be called #} - -{% macro card(class_name=none) %} -
    - {{ caller() }} -
    -{% endmacro %} - -{% macro card_header(class_name=none) %} -
    - {{ caller() }} -
    -{% endmacro %} - -{% macro card_body(class_name=none) %} -
    - {{ caller() }} -
    -{% endmacro %} - -{% macro card_footer(class_name=none) %} -
    - {{ caller() }} -
    -{% endmacro %} diff --git a/client/src/pages/components/cards/dm.html b/client/src/pages/components/cards/dm.html deleted file mode 100644 index 276fe65..0000000 --- a/client/src/pages/components/cards/dm.html +++ /dev/null @@ -1,70 +0,0 @@ -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/links.html' import fancy_link %} -{% from 'components/fancy_image.html' import fancy_image, background_image %} - -{% macro dm_card( - dm, - is_private=false, - is_global=false, - artist={}, - class_name=none -) %} - {% set service = g.paysites[dm.service] %} - {% set creator_page_url = '/' ~ dm.service ~ '/user/' ~ dm.user %} - {% set remote_creator_page_url = service.user.profile(artist or { "id" : dm.user}) %} - -
    - {% if is_global %} -
    -{# {% call fancy_link(url=creator_page_url, class_name="dm-card__icon") %}#} -{# {{ fancy_image( g.icons_prepend ~ '/icons/' ~ artist.service ~ '/' ~ artist.id) }}#} -{# {% endcall %}#} - - {% call fancy_link(url=creator_page_url, class_name='dms__user-link') %} - {{ artist.name or dm.user }} - {% endcall %} - {% call fancy_link(url=remote_creator_page_url, class_name='dms__remote-user-link') %} - ({{ service.title }}) - {% endcall %} -
    - {% endif %} - - {% if is_private %} -
    - {% call fancy_link(url=creator_page_url, class_name='dms__user-link') %} - {{ artist.name or dm.user }} - {% endcall %} - - {% call fancy_link(url=remote_creator_page_url, class_name='dms__remote-user-link') %} - ({{ service.title }}) - {% endcall %} -
    - {% endif %} - -
    - {# writing it like this so there wouldn't be whitespaces/newlines in the output #} -
    {{ dm.content|sanitize_html|safe }}
    -
    - -
    - {% if dm.published %} -
    - Published: {{ ( dm.published|simple_datetime|string)[:7] }} -
    - {% elif dm.user_id %} {# this is to detect if its not DM#} -
    - Added: {{ ( dm.added|simple_datetime|string)[:7] }} -
    - {% else %} -
    - Added: {{ dm.added|simple_datetime }} -
    - {% endif %} -
    -
    -{% endmacro %} diff --git a/client/src/pages/components/cards/no_results.html b/client/src/pages/components/cards/no_results.html deleted file mode 100644 index cf871c7..0000000 --- a/client/src/pages/components/cards/no_results.html +++ /dev/null @@ -1,18 +0,0 @@ -{% from 'components/cards/base.html' import card, card_header, card_body %} - -{% macro no_results( - title = 'Nobody here but us chickens!', - message = 'There are no items found.' -) %} - {% call card(class_name='card--no-results') %} - {% call card_header() %} -

    - {{ title }} -

    - {% endcall %} - - {% call card_body() %} - {{ message }} - {% endcall %} - {% endcall %} -{% endmacro %} diff --git a/client/src/pages/components/cards/post.html b/client/src/pages/components/cards/post.html deleted file mode 100644 index ad05d01..0000000 --- a/client/src/pages/components/cards/post.html +++ /dev/null @@ -1,117 +0,0 @@ -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/buttons.html' import button %} -{% from 'components/links.html' import fancy_link %} - -{% macro post_card(post) %} - {% set src_ns = namespace(found=false) %} - {% set src_ns.src = post.file.path if post.file.path and post.file.path|lower|regex_match("\.(gif|jpe?g|jpe|png|webp)$") %} - {% if post.service == "fansly" or post.service == "candfans" or post.service == "boosty" or post.service == "gumroad"%} - {% for file in post.attachments %} - {% if not src_ns.src and file.path and file.path|lower|regex_match("\.(gif|jpe?g|jpe|png|webp)$") %} - {% set src_ns.src = file.path %} - {% endif %} - {% endfor %} - {% endif %} - {% set post_link = g.freesites.kemono.post.link(post.service, post.user, post.id) %} -
    - -
    - {% if post.title and post.title != "DM" %} - {{ post.title }} - {% elif post.content|length < 50 %} - {{ post.content }} - {% else %} - {{ post.content[:50] + "..." }} - {% endif %} -
    - {% if src_ns.src %} -
    - -
    - {% endif %} -
    -
    -
    - {% if post.published %} - {{ timestamp(post.published) }} - {% endif %} -
    - {% if post.attachments|length %} - {{ post.attachments|length }} {{ 'attachment' if post.attachments|length == 1 else 'attachments' }} - {% else %} - No attachments - {% endif %} -
    -
    - -
    -
    -
    -
    -{% endmacro %} - -{% macro post_fav_card(post) %} - {% set src_ns = namespace(found=false) %} - {% set src_ns.src = post.file.path if post.file.path and post.file.path|lower|regex_match("\.(gif|jpe?g|jpe|png|webp)$") %} - {% if post.service == "fansly" or post.service == "candfans" or post.service == "boosty" or post.service == "gumroad"%} - {% for file in post.attachments %} - {% if not src_ns.src and file.path and file.path|lower|regex_match("\.(gif|jpe?g|jpe|png|webp)$") %} - {% set src_ns.src = file.path %} - {% endif %} - {% endfor %} - {% endif %} - {% set post_link = g.freesites.kemono.post.link(post.service, post.user, post.id) %} -
    - -
    - {% if post.title and post.title != "DM" %} - {{ post.title }} - {% elif post.content|length < 50 %} - {{ post.content }} - {% else %} - {{ post.content[:50] + "..." }} - {% endif %} -
    - {% if src_ns.src %} -
    - -
    - {% endif %} -
    -
    -
    - {% if post.published %} - {{ timestamp(post.published) }} - {% endif %} -
    - {% if post.attachments|length %} - {{ post.attachments|length }} {{ 'attachment' if post.attachments|length == 1 else 'attachments' }} - {% else %} - No attachments - {% endif %} -
    - {{ post.fav_count| int }} {{ "favorites" if post.fav_count > 1 else "favorite" }} -
    -
    - -
    -
    -
    -
    -{% endmacro %} diff --git a/client/src/pages/components/cards/share.html b/client/src/pages/components/cards/share.html deleted file mode 100644 index d9939a3..0000000 --- a/client/src/pages/components/cards/share.html +++ /dev/null @@ -1,66 +0,0 @@ -{% from 'components/fancy_image.html' import fancy_image, background_image %} -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/links.html' import fancy_link %} -{% from 'components/buttons.html' import button %} - -{% macro share_card(share) %} - -{% endmacro %} - diff --git a/client/src/pages/components/cards/user.html b/client/src/pages/components/cards/user.html deleted file mode 100644 index 2ca7eb8..0000000 --- a/client/src/pages/components/cards/user.html +++ /dev/null @@ -1,93 +0,0 @@ -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/image_link.html' import image_link %} -{% from 'components/fancy_image.html' import fancy_image %} -{% from 'components/links.html' import fancy_link %} - -{% macro user_card_header(is_count=false, is_date=false) %} -
    -
    Icon
    -
    Name
    -
    Service
    - {% if is_count %} -
    Times favorited
    - {% endif %} - {% if is_date %} -
    Updated
    - {% else %} - {% endif %} -
    -{% endmacro %} - -{% macro user_card( - user, - is_updated=false, - is_indexed=false, - is_count=false, - single_of='', - plural_of='', - is_date=false, - class_name=none -) %} - {% set user_link = g.freesites.kemono.user.profile(user.service, user.id) %} - {% set user_icon = g.freesites.kemono.user.icon(user.service, user.id) %} - {% set user_banner = g.freesites.kemono.user.banner(user.service, user.id) %} - - - {# Icon. #} -
    -
    - {{ fancy_image(src=user_icon) }} -
    -
    - - {# Secondary identifiers and elements. #} -
    - - {{ g.paysites[user.service].title }} - - -
    {{ user.name }}
    - - {% if is_updated %} -
    - {{ timestamp(user.updated) }} -
    - {% endif %} - {% if is_indexed %} -
    - {{ timestamp(user.indexed) }} -
    - {% endif %} - {% if is_count %} -
    - {% if user.count %} - {{ user.count }} {{ plural_of if user.count > 1 else single_of }} - {% else %} - {{ 'No ' ~ plural_of if plural_of else 'None' }} - {% endif %} -
    - {% endif %} -
    -
    -{% endmacro %} - -{% macro user_card_skeleton() %} - -
    -
    - -
    -
    -
    -
    -
    -{% endmacro %} diff --git a/client/src/pages/components/fancy_image.html b/client/src/pages/components/fancy_image.html deleted file mode 100644 index 324a12b..0000000 --- a/client/src/pages/components/fancy_image.html +++ /dev/null @@ -1,23 +0,0 @@ -{% macro fancy_image(src, srcset=src, is_lazy=true, alt="", class_name=none) %} - - {{ base_image(src, srcset, is_lazy, alt) }} - -{% endmacro %} - -{% macro background_image(src, srcset=src, is_lazy=true, class_name=none) %} -
    - {{ base_image(src, srcset, is_lazy, alt="") }} -
    -{% endmacro %} - -{% macro base_image(src, srcset=src, is_lazy=true, alt="") %} - - {{ alt }} - -{% endmacro %} diff --git a/client/src/pages/components/fancy_image.js b/client/src/pages/components/fancy_image.js deleted file mode 100644 index cb9373b..0000000 --- a/client/src/pages/components/fancy_image.js +++ /dev/null @@ -1,59 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -/** - * @param {HTMLSpanElement} element - * @param {string} src - * @param {string} srcset - * @param {boolean} isLazy - * @param {string} alt - * @param {string} className - */ -export function FancyImage(element = null, src, srcset = src, isLazy = true, alt = "", className = null) { - /** - * @type {HTMLSpanElement} - */ - const fancyImage = element ? initFromElement(element) : initFromScratch(src, srcset, isLazy, alt, className); - - return fancyImage; -} - -/** - * @param {HTMLSpanElement} element - */ -function initFromElement(element) { - return element; -} - -/** - * @param {string} src - * @param {string} srcset - * @param {boolean} isLazy - * @param {string} alt - * @param {string} className - */ -function initFromScratch(src, srcset, isLazy, alt, className) { - /** - * @type {HTMLSpanElement} - */ - const fancyImage = createComponent("fancy-image"); - /** - * @type {HTMLImageElement} - */ - const img = fancyImage.querySelector(".fancy-image__image"); - - img.src = src; - img.srcset = srcset; - img.alt = alt; - - if (className) { - fancyImage.classList.add(className); - } - - if (isLazy) { - img.loading = "lazy"; - } else { - img.loading = "eager"; - } - - return fancyImage; -} diff --git a/client/src/pages/components/file_hash_search.html b/client/src/pages/components/file_hash_search.html deleted file mode 100644 index 84cbcc4..0000000 --- a/client/src/pages/components/file_hash_search.html +++ /dev/null @@ -1,20 +0,0 @@ -{% macro search_form() %} - -{% endmacro %} diff --git a/client/src/pages/components/file_hash_search.scss b/client/src/pages/components/file_hash_search.scss deleted file mode 100644 index 1af7d1e..0000000 --- a/client/src/pages/components/file_hash_search.scss +++ /dev/null @@ -1,5 +0,0 @@ -#file-hash-search-form { - text-align: center; - padding-top: 1em; - padding-bottom: 0.5em; -} diff --git a/client/src/pages/components/flash_messages.html b/client/src/pages/components/flash_messages.html deleted file mode 100644 index e1edc3d..0000000 --- a/client/src/pages/components/flash_messages.html +++ /dev/null @@ -1,9 +0,0 @@ -{% with messages = get_flashed_messages() %} - {% if messages %} -
    - {% for message in messages %} - {{ message }}
    - {% endfor %} -
    - {% endif %} -{% endwith %} diff --git a/client/src/pages/components/footer.html b/client/src/pages/components/footer.html deleted file mode 100644 index 1d26570..0000000 --- a/client/src/pages/components/footer.html +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/client/src/pages/components/forms/base.html b/client/src/pages/components/forms/base.html deleted file mode 100644 index 2086c8b..0000000 --- a/client/src/pages/components/forms/base.html +++ /dev/null @@ -1,8 +0,0 @@ -{% macro form() %} -
    {{ caller() if caller }}
    -{% endmacro %} diff --git a/client/src/pages/components/forms/submit_button.html b/client/src/pages/components/forms/submit_button.html deleted file mode 100644 index ff68fe8..0000000 --- a/client/src/pages/components/forms/submit_button.html +++ /dev/null @@ -1,9 +0,0 @@ -{%- macro submit_button(text) -%} - -{%- endmacro -%} diff --git a/client/src/pages/components/headers.html b/client/src/pages/components/headers.html deleted file mode 100644 index da744be..0000000 --- a/client/src/pages/components/headers.html +++ /dev/null @@ -1,76 +0,0 @@ -{% from 'components/links.html' import fancy_link %} -{% from 'components/fancy_image.html' import background_image %} -{% from 'components/image_link.html' import image_link %} - -{% macro user_header(request, props) %} - {% set artist_icon = g.freesites.kemono.user.icon(props.service, props.id) %} - {% set artist_banner = g.freesites.kemono.user.banner(props.service, props.id) %} - {% set paysite_icons = { - 'patreon': '/static/patreon.svg', - 'fanbox': '/static/fanbox.svg', - 'gumroad': '/static/gumroad.svg', - 'subscribestar': '/static/subscribestar.png', - 'dlsite': '/static/dlsite.png', - 'fantia': '/static/fantia.png', - 'onlyfans': '/static/onlyfans.svg', - 'fansly': '/static/fansly.svg', - 'candfans': '/static/candfans.png', - } %} - -
    - {{ background_image( - artist_banner, - is_lazy=false, - class_name='user-header__background' - ) }} - - {{ image_link( - url=request.path, - src=artist_icon, - is_lazy=false, - is_noop=false, - class_name='user-header__avatar' - ) }} - - -
    -{% endmacro %} diff --git a/client/src/pages/components/image_link.html b/client/src/pages/components/image_link.html deleted file mode 100644 index 07ea5fa..0000000 --- a/client/src/pages/components/image_link.html +++ /dev/null @@ -1,25 +0,0 @@ -{% from 'components/fancy_image.html' import base_image %} -{% from 'components/links.html' import fancy_link %} - -{% macro image_link( - url, - src=url, - alt="", - srcset=src, - is_lazy=true, - is_noop=true, - class_name=none -) %} - {% call fancy_link( - url, - '', - is_noop, - 'image-link ' ~ (class_name if class_name) - ) %} - {% if not caller %} - {{ base_image(src, srcset, is_lazy, alt) }} - {% else %} - {{ caller() }} - {% endif %} - {% endcall %} -{% endmacro %} diff --git a/client/src/pages/components/image_link.js b/client/src/pages/components/image_link.js deleted file mode 100644 index 0f65902..0000000 --- a/client/src/pages/components/image_link.js +++ /dev/null @@ -1,78 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -/** - * TODO: Restructure arguments. - * @param {HTMLAnchorElement} element - * @param {string} url - * @param {string} src - * @param {string} alt - * @param {string} srcset - * @param {boolean} isLazy - * @param {boolean} isNoop - * @param {string} className - */ -export function ImageLink( - element = null, - url, - src = url, - alt = "", - srcset = src, - isLazy = true, - isNoop = true, - className = null, -) { - const imageLink = element - ? initFromElement(element) - : initFromScratch(url, src, alt, srcset, isLazy, isNoop, className); - - return imageLink; -} - -/** - * @param {HTMLAnchorElement} element - */ -function initFromElement(element) { - return element; -} - -/** - * @param {string} url - * @param {string} src - * @param {string} alt - * @param {string} srcset - * @param {boolean} isLazy - * @param {boolean} isNoop - * @param {string} className - */ -function initFromScratch(url, src, alt, srcset, isLazy, isNoop, className) { - /** - * @type {HTMLAnchorElement} - */ - const imageLink = createComponent("fancy-link image-link"); - /** - * @type {HTMLImageElement} - */ - const image = imageLink.querySelector(".fancy-image__image"); - - imageLink.href = url; - image.src = src; - image.srcset = srcset; - image.alt = alt; - - if (isNoop) { - imageLink.target = "_blank"; - imageLink.rel = "noopener noreferrer"; - } - - if (isLazy) { - image.loading = "lazy"; - } else { - image.loading = "eager"; - } - - if (className) { - imageLink.classList.add(className); - } - - return imageLink; -} diff --git a/client/src/pages/components/import_sidebar.html b/client/src/pages/components/import_sidebar.html deleted file mode 100644 index 712c852..0000000 --- a/client/src/pages/components/import_sidebar.html +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/client/src/pages/components/importer_states.html b/client/src/pages/components/importer_states.html deleted file mode 100644 index 659318d..0000000 --- a/client/src/pages/components/importer_states.html +++ /dev/null @@ -1,4 +0,0 @@ -{#
    - - -
    #} \ No newline at end of file diff --git a/client/src/pages/components/links.html b/client/src/pages/components/links.html deleted file mode 100644 index 87c05d6..0000000 --- a/client/src/pages/components/links.html +++ /dev/null @@ -1,56 +0,0 @@ -{# not splitting on several lines because it adds whitespaces in the output #} -{% macro fancy_link(url, text=url, is_noop=true, class_name=none ) %} - {{ text if not caller else caller() }} -{%- endmacro -%} - -{% macro download_link(url, text=url, file_name=text, class_name=none) %} - {{ text if not caller else caller() }} -{%- endmacro -%} - -{% macro kemono_link(url, text=url, is_noop=true,class_name=none) %} - {{ text if not caller else caller() }} -{%- endmacro -%} - -{% macro local_link(id, text=id, class_name=none) %} - {{ text if not caller else caller() }} -{%- endmacro -%} - -{% macro email_link(email, text=email, class_name=none) %} - -{%- endmacro -%} - -{% macro link_button(url, text=url, is_noop=true, class_name=none) %} - {{ text if not caller else caller() }} -{% endmacro %} diff --git a/client/src/pages/components/links.js b/client/src/pages/components/links.js deleted file mode 100644 index 6ab26bd..0000000 --- a/client/src/pages/components/links.js +++ /dev/null @@ -1,53 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -/** - * @param {HTMLElement} element - * @param {string} url - * @param {string} text - * @param {boolean} isNoop - * @param {string} className - * @returns - */ -export function FancyLink(element = null, url, text = url, isNoop = true, className = undefined) { - /** - * @type {HTMLAnchorElement} - */ - const fancyLink = element ? initFromElement(element) : initFromScratch(url, text, isNoop, className); - - return fancyLink; -} - -/** - * @param {HTMLAnchorElement} - */ -function initFromElement(element) { - return element; -} - -/** - * @param {string} url - * @param {string} text - * @param {boolean} isNoop - * @param {string} className - * @returns - */ -function initFromScratch(url, text, isNoop, className) { - /** - * @type {HTMLAnchorElement} - */ - const fancyLink = createComponent("fancy-link"); - - fancyLink.href = url; - fancyLink.textContent = text; - - if (className) { - fancyLink.classList.add(className); - } - - if (isNoop) { - fancyLink.target = "_blank"; - fancyLink.rel = "noopener noreferrer"; - } - - return fancyLink; -} diff --git a/client/src/pages/components/lists/_index.scss b/client/src/pages/components/lists/_index.scss deleted file mode 100644 index 914c413..0000000 --- a/client/src/pages/components/lists/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use "base"; -@use "faq"; diff --git a/client/src/pages/components/lists/base.html b/client/src/pages/components/lists/base.html deleted file mode 100644 index 52b498b..0000000 --- a/client/src/pages/components/lists/base.html +++ /dev/null @@ -1,18 +0,0 @@ -{% from 'components/meta/attributes.html' import attributes %} - -{# Call-only macros #} -{% macro desc_list() %} -
    {{ caller() }}
    -{% endmacro %} - -{% macro desc_section() %} -
    {{ caller() }}
    -{% endmacro %} - -{% macro desc_term() %} -
    {{ caller() }}
    -{% endmacro %} - -{% macro desc_details() %} -
    {{ caller() }}
    -{% endmacro %} diff --git a/client/src/pages/components/lists/base.scss b/client/src/pages/components/lists/base.scss deleted file mode 100644 index ef27f9d..0000000 --- a/client/src/pages/components/lists/base.scss +++ /dev/null @@ -1,23 +0,0 @@ -@use "../../../css/config/variables" as *; - -.desc-list { - background-color: var(--colour1-tertiary); - border-radius: 10px; - - &__section { - display: inline-block; - border-radius: 10px; - padding: $size-small; - - &:target { - outline-color: var(--anchour-local-colour1-primary); - outline-width: $size-thin; - outline-style: dashed; - } - } - &__term { - font-weight: bold; - } - &__details { - } -} diff --git a/client/src/pages/components/lists/faq.html b/client/src/pages/components/lists/faq.html deleted file mode 100644 index 80ebe71..0000000 --- a/client/src/pages/components/lists/faq.html +++ /dev/null @@ -1,18 +0,0 @@ -{% from 'components/meta/attributes.html' import attributes %} - -{# Call-only macros #} -{% macro faq_list() %} -
    {{ caller() }}
    -{% endmacro %} - -{% macro faq_section() %} -
    {{ caller() }}
    -{% endmacro %} - -{% macro faq_question() %} -
    {{ caller() }}
    -{% endmacro %} - -{% macro faq_answer() %} -
    {{ caller() }}
    -{% endmacro %} diff --git a/client/src/pages/components/lists/faq.scss b/client/src/pages/components/lists/faq.scss deleted file mode 100644 index 6d4f63b..0000000 --- a/client/src/pages/components/lists/faq.scss +++ /dev/null @@ -1,10 +0,0 @@ -.desc-list { - &--faq { - } - &__section--faq { - } - &__term--question { - } - &__details--answer { - } -} diff --git a/client/src/pages/components/loading_icon.html b/client/src/pages/components/loading_icon.html deleted file mode 100644 index 0bb1a2a..0000000 --- a/client/src/pages/components/loading_icon.html +++ /dev/null @@ -1,9 +0,0 @@ -{% from "components/fancy_image.html" import fancy_image %} - -{% set url = url_for('static', filename='loading.gif') %} - -{% macro loading_icon() -%} - - {{ fancy_image(url, alt="loading progress spinner") }} - -{%- endmacro %} diff --git a/client/src/pages/components/loading_icon.js b/client/src/pages/components/loading_icon.js deleted file mode 100644 index f86fc89..0000000 --- a/client/src/pages/components/loading_icon.js +++ /dev/null @@ -1,9 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -export function LoadingIcon() { - /** - * @type {HTMLSpanElement} - */ - const icon = createComponent("loading-icon"); - return icon; -} diff --git a/client/src/pages/components/meta/attributes.html b/client/src/pages/components/meta/attributes.html deleted file mode 100644 index e08080e..0000000 --- a/client/src/pages/components/meta/attributes.html +++ /dev/null @@ -1,7 +0,0 @@ -{# Put html attributes into kwargs argument #} -{% macro attributes(class_name) %} - class="{{ class_name ~ ' ' ~ kwargs.pop('class') if kwargs.class else class_name }}" - {% for attribute in kwargs %} - {{ attribute }}="{{ kwargs[attribute] }}" - {% endfor %} -{% endmacro %} diff --git a/client/src/pages/components/navigation/_index.scss b/client/src/pages/components/navigation/_index.scss deleted file mode 100644 index 31e0e9a..0000000 --- a/client/src/pages/components/navigation/_index.scss +++ /dev/null @@ -1,5 +0,0 @@ -@use "base"; -@use "global"; -@use "local"; -@use "account"; -@use "sidebar"; diff --git a/client/src/pages/components/navigation/account.html b/client/src/pages/components/navigation/account.html deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/pages/components/navigation/account.scss b/client/src/pages/components/navigation/account.scss deleted file mode 100644 index e7c51f6..0000000 --- a/client/src/pages/components/navigation/account.scss +++ /dev/null @@ -1 +0,0 @@ -@use "../../../css/config/variables" as *; diff --git a/client/src/pages/components/navigation/base.html b/client/src/pages/components/navigation/base.html deleted file mode 100644 index 61f14d1..0000000 --- a/client/src/pages/components/navigation/base.html +++ /dev/null @@ -1,23 +0,0 @@ -{# Call-only macros #} -{% macro navigation(id=none, class_name=none) %} - -{% endmacro %} - -{% macro nav_list(class_name=none) %} - -{% endmacro %} - -{% macro nav_item(class_name=none) %} - -{% endmacro %} diff --git a/client/src/pages/components/navigation/base.scss b/client/src/pages/components/navigation/base.scss deleted file mode 100644 index 8e830c1..0000000 --- a/client/src/pages/components/navigation/base.scss +++ /dev/null @@ -1,24 +0,0 @@ -@use "../../../css/config/variables" as *; - -.navigation { - &__list { - display: flex; - flex-flow: column nowrap; - align-items: flex-start; - gap: $size-small; - list-style: none; - padding: $size-small; - margin: 0; - - &--ordered { - list-style-type: decimal-leading-zero; - padding-left: $size-normal; - } - } - - &__item { - } - - &__link { - } -} diff --git a/client/src/pages/components/navigation/global.html b/client/src/pages/components/navigation/global.html deleted file mode 100644 index 9a1dbc4..0000000 --- a/client/src/pages/components/navigation/global.html +++ /dev/null @@ -1,35 +0,0 @@ -{% from 'components/navigation/base.html' import navigation, nav_list, nav_item %} -{% from 'components/links.html' import fancy_link, kemono_link %} -{% from 'components/buttons.html' import button as base_button %} - -{% macro nav(id) %} - {% call navigation(id, class_name='global-nav') %} - {{ caller }} - {% endcall %} -{% endmacro %} - -{% macro list() %} - {% call nav_list(class_name='global-nav__list') %} - {{ caller }} - {% endcall %} -{% endmacro %} - -{% macro item() %} - {% call nav_item(class_name='global-nav__item') %} - {{ caller }} - {% endcall %} -{% endmacro %} - -{% macro button() %} - {% call base_button() %} - {{ caller }} - {% endcall %} -{% endmacro %} - -{% macro link(url, text) %} - {{ kemono_link(url, text, class_name='global-nav__link') }} -{% endmacro %} - -{% macro link_external(url, text) %} - {{ fancy_link(url, text, class_name='global-nav__link') }} -{% endmacro %} diff --git a/client/src/pages/components/navigation/global.scss b/client/src/pages/components/navigation/global.scss deleted file mode 100644 index 08865b4..0000000 --- a/client/src/pages/components/navigation/global.scss +++ /dev/null @@ -1,110 +0,0 @@ -@use "../../../css/config/variables" as *; - -.global-nav { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - align-items: center; - gap: $size-normal; - - &__list { - display: flex; - flex-flow: column nowrap; - - transition-property: visibility, opacity; - transition-duration: var(--duration-global); - } - - &__item { - position: relative; - - & > .global-nav__list { - position: absolute; - top: 100%; - z-index: 1; - visibility: hidden; - opacity: 0; - display: flex; - flex-flow: column nowrap; - align-items: flex-start; - min-width: 150px; - background-color: var(--colour1-tertiary); - border-radius: 10px; - padding: $size-small; - } - - &--open { - & > .global-nav__button { - background-color: var(--local-colour2-secondary); - border-radius: 5px 5px 0 0; - border-bottom-color: transparent; - } - - & > .global-nav__list { - visibility: visible; - opacity: 1; - border-radius: 0 10px 10px 10px; - box-shadow: 0 0 5px var(--colour1-primary-transparent); - } - } - - &--account { - margin-left: auto; - - & > .global-nav__list { - right: 0; - border-radius: 10px 0 10px 10px; - } - } - - // quick hack until I figure out anchour selector specificities - & .global-nav__link { - --local-colour1-primary: var(--colour0-primary); - --local-colour1-secondary: var(--colour0-primary); - --local-colour2-primary: var(--colour1-tertiary); - --local-colour2-secondary: var(--colour1-secondary); - } - } - - &__button { - --local-colour1-primary: var(--colour0-primary); - --local-colour1-secondary: var(--colour0-tertirary); - --local-colour2-primary: var(--colour1-primary); - --local-colour2-secondary: var(--colour1-tertiary); - - // temp until header rework - min-height: 34px; - color: var(--local-colour1-primary); - background-image: none; - background-color: var(--local-colour2-primary); - border: $size-nano solid var(--local-colour1-secondary); - box-shadow: - inset 2px 2px 3px hsla(0, 0%, 40%, 0.5), - inset -2px -2px 3px hsla(0, 0%, 0%, 0.5); - - transition-property: color, background-color, shadow, outline; - transition-duration: var(--duration-global); - - &:focus { - background-color: var(--local-colour2-secondary); - // outline-offset: 3px; - // outline-width: $size-thin; - // outline-style: dashed; - // outline-color: var(--colour0-secondary); - } - - &:hover { - background-color: var(--local-colour2-secondary); - } - - &:active { - box-shadow: - inset -2px -2px 3px hsla(0, 0%, 40%, 0.5), - inset 2px 2px 3px hsla(0, 0%, 0%, 0.5); - } - - &--notifs { - --local-colour1-primary: var(--submit-colour1-primary); - } - } -} diff --git a/client/src/pages/components/navigation/local.html b/client/src/pages/components/navigation/local.html deleted file mode 100644 index 9fb70f5..0000000 --- a/client/src/pages/components/navigation/local.html +++ /dev/null @@ -1,18 +0,0 @@ -{% from 'components/meta/attributes.html' import attributes %} -{% from 'components/links.html' import local_link %} - -{% macro local_nav() %} - -{% endmacro %} - -{% macro local_list() %} -
      {{ caller() }}
    -{% endmacro %} - -{% macro local_list_ordered() %} -
      {{ caller() }}
    -{% endmacro %} - -{% macro local_item(id, text) %} -
  • {{ local_link(id, text) }}
  • -{% endmacro %} diff --git a/client/src/pages/components/navigation/local.scss b/client/src/pages/components/navigation/local.scss deleted file mode 100644 index a5bf4c8..0000000 --- a/client/src/pages/components/navigation/local.scss +++ /dev/null @@ -1,16 +0,0 @@ -@use "../../../css/config/variables" as *; - -.navigation { - &--local { - display: inline-block; - } - - &__list--local { - list-style-type: disc; - background-color: var(--colour1-tertiary); - border-radius: 10px; - padding-left: calc(#{$size-normal} + #{$size-small}); - } - &__item--local { - } -} diff --git a/client/src/pages/components/navigation/sidebar.html b/client/src/pages/components/navigation/sidebar.html deleted file mode 100644 index 9096558..0000000 --- a/client/src/pages/components/navigation/sidebar.html +++ /dev/null @@ -1,75 +0,0 @@ -{% macro nav_list(items, class_name=none) %} - {% for item in items %} - {{ nav_entry(item, class_name) }} - {% endfor %} -{% endmacro %} - -{% macro nav_entry(items, class_name=none) %} -
    - {% if not caller %} - {% for item in items %} - {% if not item.disable %} - {% if item.header %} - {% if item.link %} - {{ nav_item( - item.link, - item.text, - "clickable-header " ~ (item.class_name if item.class_name), - icon=item.icon - ) - }} - {% else %} - {{ nav_header(item.text, item.class_name, item.icon) }} - {% endif %} - {% else %} - {{ nav_item( - item.link, - item.text, - item.class_name, - item.is_external, - item.color, - item.icon - ) - }} - {% endif %} - {% endif %} - {% endfor %} - {% else %} - {{ caller() }} - {% endif %} -
    -{% endmacro %} - -{% macro nav_header(text, class_name=none, icon=none) %} -
    - {% if icon %} - - {% endif %} - {% if not caller %} - {{ text }} - {% else %} - {{ caller() }} - {% endif %} -
    -{% endmacro %} - -{% macro nav_item(link, text=link, class_name=none, is_external=false, color=none, icon=none) %} - {% if icon %} - - {% endif %} - {% if not caller %} - {{ text }} - {% else %} - {{ caller() }} - {% endif %} - -{% endmacro %} diff --git a/client/src/pages/components/paginator.html b/client/src/pages/components/paginator.html deleted file mode 100644 index d925709..0000000 --- a/client/src/pages/components/paginator.html +++ /dev/null @@ -1,72 +0,0 @@ -{% set skip = request.args.get('o')|parse_int if request.args.get('o') else 0 %} -{% set currentCeilingOfRange = skip + props.limit if (skip + props.limit) < props.count else props.count %} - -{% set TOTAL_BUTTONS = 5 %} -{% set OPTIONAL_BUTTONS = TOTAL_BUTTONS - 2 %} -{% set MANDATORY_BUTTONS = TOTAL_BUTTONS - OPTIONAL_BUTTONS %} -{% set currPageNum = ((skip + props.limit) / props.limit)|round(0, 'ceil')|int %} -{% set totalPages = (props.count / props.limit)|round(0, 'ceil')|int %} -{% set numBeforeCurrPage = currPageNum - 1 if ((totalPages < TOTAL_BUTTONS) or (currPageNum < TOTAL_BUTTONS)) else ((TOTAL_BUTTONS - 1) + ((TOTAL_BUTTONS) - (totalPages - currPageNum)) if (totalPages - currPageNum) < TOTAL_BUTTONS else (TOTAL_BUTTONS - 1)) %} -{% set basePageNum = [currPageNum - numBeforeCurrPage - 1, 1]|max %} -{% set showFirstPostsButton = basePageNum > 1 %} -{% set showLastPostsButton = totalPages - currPageNum > (TOTAL_BUTTONS + ((TOTAL_BUTTONS - (currPageNum - basePageNum)) if currPageNum - basePageNum < TOTAL_BUTTONS else 0)) %} -{% set optionalBeforeButtons = currPageNum - MANDATORY_BUTTONS - ((MANDATORY_BUTTONS - (totalPages - currPageNum)) if totalPages - currPageNum < MANDATORY_BUTTONS else 0) %} -{% set optionalAfterButtons = currPageNum + MANDATORY_BUTTONS + ((MANDATORY_BUTTONS - (currPageNum - basePageNum)) if currPageNum - basePageNum < MANDATORY_BUTTONS else 0) %} - -{% macro paginator_button(content, href=none, class_name=none) %} - {%if href %} - {{ content }} - {%else%} -
  • {{ content }}
  • - {%endif%} -{% endmacro %} - -{% if props.count > props.limit %} - - Showing {{ skip + 1 }} - {{ currentCeilingOfRange }} of {{ props.true_count or props.count }} - - {% set rng = range(0, (TOTAL_BUTTONS * 2) + 1) %} - - {%if showFirstPostsButton or showLastPostsButton %} - {%if showFirstPostsButton %} - {{ paginator_button('<<', href=url_for(request.endpoint, o = 0, **base)) }} - {%else%} - {{ paginator_button('<<', class_name='pagination-button-disabled' ~ (' pagination-desktop' if currPageNum - MANDATORY_BUTTONS - 1 else '')) }} - {%endif%} - {%endif%} - {%if not showFirstPostsButton %} - {%if currPageNum - MANDATORY_BUTTONS - 1 %} - {{ paginator_button('<<', href=url_for(request.endpoint, o = 0, **base), class_name='pagination-mobile') }} - {%elif (totalPages - currPageNum > MANDATORY_BUTTONS) and not showLastPostsButton %} - {{ paginator_button('<<', class_name='pagination-button-disabled pagination-mobile') }} - {%endif%} - {%endif%} - {%if currPageNum > 1 %} - {{ paginator_button('<', href=url_for(request.endpoint, o = (currPageNum - 2) * props.limit, **base), class_name='prev') }} - {%else%} - {{ paginator_button('<', class_name='pagination-button-disabled')}} - {%endif%} - {% for page in rng if (page + basePageNum) and ((page + basePageNum) <= totalPages) %} - {{ paginator_button((page + basePageNum), href=url_for(request.endpoint, o =((page + basePageNum - 1) * props.limit) if not (page + basePageNum) == 1 else none, **base) if (page + basePageNum) != currPageNum else none, class_name='pagination-button-optional' if ((page + basePageNum) < optionalBeforeButtons or (page + basePageNum) > optionalAfterButtons) and (page + basePageNum) != currPageNum else ('pagination-button-disabled pagination-button-current' if (page + basePageNum) == currPageNum else ('pagination-button-after-current' if (page + basePageNum) == (currPageNum + 1) else ''))) }} - {% endfor %} - {%if currPageNum < totalPages %} - {{ paginator_button('>', href=url_for(request.endpoint, o = currPageNum * props.limit, **base), class_name='next') }} - {%else%} - {{ paginator_button('>', class_name='pagination-button-disabled' ~(' pagination-button-after-current' if totalPages else '')) }} - {%endif%} - {%if showFirstPostsButton or showLastPostsButton %} - {%if showLastPostsButton %} - {{ paginator_button('>>', href=url_for(request.endpoint, o = (totalPages - 1) * props.limit, **base)) }} - {%else%} - {{ paginator_button('>>', class_name='pagination-button-disabled' ~ (' pagination-desktop' if totalPages - currPageNum > MANDATORY_BUTTONS else '')) }} - {%endif%} - {%endif%} - {%if not showLastPostsButton %} - {%if totalPages - currPageNum > MANDATORY_BUTTONS%} - {{ paginator_button('>>', href=url_for(request.endpoint, o = (totalPages - 1) * props.limit, **base), class_name='pagination-mobile') }} - {%elif (currPageNum > OPTIONAL_BUTTONS) and not showFirstPostsButton %} - {{ paginator_button('>>', class_name='pagination-button-disabled pagination-mobile') }} - {%endif%} - {%endif%} - -{% endif %} diff --git a/client/src/pages/components/paginator.js b/client/src/pages/components/paginator.js deleted file mode 100644 index 76e4451..0000000 --- a/client/src/pages/components/paginator.js +++ /dev/null @@ -1,12 +0,0 @@ -export function registerPaginatorKeybinds() { - document.addEventListener("keydown", (e) => { - switch (e.key) { - case "ArrowLeft": - document.querySelector(".paginator .prev")?.click(); - break; - case "ArrowRight": - document.querySelector(".paginator .next")?.click(); - break; - } - }); -} diff --git a/client/src/pages/components/paginator_new.html b/client/src/pages/components/paginator_new.html deleted file mode 100644 index 64b05a3..0000000 --- a/client/src/pages/components/paginator_new.html +++ /dev/null @@ -1,116 +0,0 @@ -{% from 'components/links.html' import link_button %} - -{# `id` is the id of related `form_controller()` #} -{% macro paginator(id, request, pagination, class_name= none) %} - {% set current_page = pagination.current_page %} - {% set total_pages = pagination.total_pages %} - {% set base_url = pagination.base_url %} - -
    - - Showing {{ pagination.offset + 1 }} - {{ pagination.current_count }} of {{ pagination.count }} - -
      -
    • - {% if current_page != 1 %} - {{ link_button( - pagination.create_paged_url(request, 1), - 1, - is_noop=false, - class_name= 'paginator__link' - ) }} - {% else %} - - ... - - {% endif %} - -
    • -
    • - {% if current_page > 2 %} - {{ link_button( - pagination.create_paged_url(request, current_page - 1), - current_page - 1, - is_noop=false, - class_name= 'paginator__link' - ) }} - {% else %} - - ... - - {% endif %} -
    • - -
    • - - -
    • - -
    • - {% if current_page < total_pages - 1 %} - {{ link_button( - pagination.create_paged_url(request, current_page + 1), - current_page + 1, - is_noop=false, - class_name= 'paginator__link' - ) }} - {% else %} - - ... - - {% endif %} -
    • - -
    • - {% if current_page != total_pages %} - {{ link_button( - pagination.create_paged_url(request, total_pages), - total_pages, - is_noop=false, - class_name= 'paginator__link' - ) }} - {% else %} - - ... - - {% endif %} -
    • -
    -
    -{% endmacro %} - -{# `**kwargs` is `
    ` attributes #} -{% macro paginator_controller(id, request, pagination) %} - - {% for param in pagination.base %} - - {% endfor %} -
    -{% endmacro %} diff --git a/client/src/pages/components/shell.html b/client/src/pages/components/shell.html deleted file mode 100644 index 88e4c33..0000000 --- a/client/src/pages/components/shell.html +++ /dev/null @@ -1,221 +0,0 @@ -{# TODO: figure out nested macro calls #} -{% import 'components/navigation/global.html' as global %} -{% from 'components/navigation/sidebar.html' import nav_list, nav_item, nav_header, nav_entry %} -{% from 'components/loading_icon.html' import loading_icon %} -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/tooltip.html' import tooltip %} -{% from 'components/links.html' import fancy_link, kemono_link, link_button %} -{% from 'components/tooltip.html' import register_message %} -{% from 'components/buttons.html' import button %} - -{% macro header_link(url, text, class_name=none) %} - - {{ text }} - -{% endmacro %} - - - - - - - {% if g.matomo_enabled and g.matomo_plain_code %} - {{ g.matomo_plain_code|safe }} - {% elif g.matomo_enabled %} - - - {% endif %} - - - - {% block title %} - - {{ (props.name ~ " | " ~ g.site_name) if props.name else g.site_name }} - - {% endblock title %} - - - {% block meta %} - {% if props.service %} - - {% endif %} - {% if props.id %} - - {% endif %} - {% if props.importId %} - - {% endif %} - {% if props.count %} - - {% endif %} - {% if props.posts|length %} - {% if props.posts[0].published %} - - {% endif %} - - - {% endif %} - {% endblock meta %} - - {% block opengraph %} - - - - - - - - {% endblock opengraph %} - - {% block styles %} - {% endblock styles %} - - {% block scripts %} - {% if request.args.logged_in %} - - {% endif %} - {% if request.args.role %} - - {% endif %} - {# TODO remove this shit #} - {% endblock scripts %} - - {% block bundler_output %} - {# quick hack until writing proper loader #} - <% for (const css in htmlWebpackPlugin.files.css) { %> - <% if (htmlWebpackPlugin.files.css[css].startsWith("/static/bundle/css/global")) { %> - - <% } %> - <% } %> - <% for (const chunk in htmlWebpackPlugin.files.chunks) { %> - - <% } %> - <% for (const scriptPath in htmlWebpackPlugin.files.js) { %> - <% if (htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/global") || htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/runtime") || htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/vendors")) { %> - - <% } %> - <% } %> - {% endblock bundler_output %} - - {% block scripts_extra %} - {% endblock scripts_extra %} - - - - -
    -
    -
    -
    -
    - -
    - {{ header_link('/', 'Home', 'home') }} - {{ header_link('/artists', g.artists_or_creators) }} - {{ header_link('/posts', 'Posts') }} - {{ header_link('/importer', 'Import', 'import') }} - {{ header_link('/account/register?location=' + request.args.get("location", request.path), 'Register', 'register') }} - {{ header_link('/account/login?location=' + request.args.get("location", request.path), 'Login', 'login') }} -
    - {% include 'components/flash_messages.html' %} - {% if g.banner_global %} - {{ g.banner_global|safe }} - {% endif %} - -
    - {% block content %} - {% endblock content %} -
    - -
    - {% include 'components/footer.html' %} - -
    -
    - {#
    -
    #} - {% call tooltip() %} -

    -

    - {% endcall %} - - diff --git a/client/src/pages/components/shell.js b/client/src/pages/components/shell.js deleted file mode 100644 index 0db0df7..0000000 --- a/client/src/pages/components/shell.js +++ /dev/null @@ -1,106 +0,0 @@ -import { isLoggedIn } from "@wp/js/account"; - -window.addEventListener("load", () => { - document.body.classList.remove("transition-preload"); -}); - -/** - * @param {HTMLElement} sidebar - */ -export function initShell(sidebar) { - const burgor = document.getElementById("burgor"); - const header = burgor.parentElement; - const backdrop = document.querySelector(".backdrop"); - const contentWrapper = document.querySelector(".content-wrapper"); - const closeButton = sidebar.querySelector(".close-sidebar"); - const closeSidebar = (_, setState = true) => { - sidebar.classList.toggle("expanded"); - sidebar.classList.toggle("retracted"); - backdrop.classList.toggle("backdrop-hidden"); - contentWrapper.classList.toggle("shifted"); - const retracted = header.classList.toggle("sidebar-retracted"); - if (setState && window.innerWidth > 1020) localStorage.setItem("sidebar_state", retracted); - }; - if (typeof localStorage.getItem("sidebar_state") === "string") { - const sidebarState = localStorage.getItem("sidebar_state") === "true"; - if (window.innerWidth > 1020 && sidebarState) closeSidebar(); - } - window.addEventListener("resize", () => { - if (typeof localStorage.getItem("sidebar_state") !== "string") return; - const sidebarState = localStorage.getItem("sidebar_state") === "true"; - const realState = header.classList.contains("sidebar-retracted"); - const killAnimations = () => { - document.body.classList.add("transition-preload"); - requestAnimationFrame(() => setInterval(() => document.body.classList.remove("transition-preload"))); - }; - if (window.innerWidth <= 1020) { - if (sidebarState && realState) { - killAnimations(); - closeSidebar(null, false); - } - } else if (sidebarState && !realState) { - killAnimations(); - closeSidebar(); - } - }); - burgor.addEventListener("click", closeSidebar); - backdrop.addEventListener("click", closeSidebar); - closeButton.addEventListener("click", closeSidebar); - if (isLoggedIn) { - const accountList = sidebar.querySelector(".account"); - const login = accountList.querySelector(".login"); - const loginHeader = header.querySelector(".login"); - const register = accountList.querySelector(".register"); - const registerHeader = header.querySelector(".register"); - const favorites = accountList.querySelector(".favorites"); - const reviewDms = accountList.querySelector(".review_dms"); - login.classList.remove("login"); - loginHeader.classList.remove("login"); - loginHeader.classList.add("logout"); - register.classList.remove("register"); - registerHeader.classList.remove("register"); - favorites.classList.remove("hidden"); - reviewDms.classList.remove("hidden"); - login.lastChild.textContent = "Logout"; - login.firstElementChild.src = "/static/menu/logout.svg"; - login.href = "/account/logout"; - loginHeader.innerText = "Logout"; - loginHeader.href = "/account/logout"; - register.lastChild.textContent = "Keys"; - register.firstElementChild.src = "/static/menu/keys.svg"; - register.href = "/account/keys"; - registerHeader.innerText = "Favorites"; - registerHeader.href = "/favorites"; - const onLogout = (e) => { - e.preventDefault(); - localStorage.removeItem("logged_in"); - localStorage.removeItem("role"); - localStorage.removeItem("favs"); - localStorage.removeItem("post_favs"); - location.href = "/account/logout"; - }; - login.addEventListener("click", onLogout); - loginHeader.addEventListener("click", onLogout); - } else { - const accountHeader = sidebar.querySelector(".account-header"); - const newHeader = document.createElement("div"); - newHeader.className = "global-sidebar-entry-item header"; - newHeader.innerText = "Account"; - newHeader.prepend(accountHeader.firstElementChild); - accountHeader.parentElement.replaceChild(newHeader, accountHeader); - } - // questionable? close sidebar on tap of an item, - // delay loading of page until animation is done - // uncomment to close on tap - // uncomment the items commented with // to add a delay so it finishes animating - /* sidebar.querySelectorAll('.global-sidebar-entry-item').forEach(e => { - e.addEventListener('click', ev => { - //ev.preventDefault(); - sidebar.classList.remove('expanded'); - backdrop.classList.add('backdrop-hidden'); - // setTimeout(() => { - // location.href = e.href; - // }, 250); - }) - }) */ -} diff --git a/client/src/pages/components/site.html b/client/src/pages/components/site.html deleted file mode 100644 index 6811eb8..0000000 --- a/client/src/pages/components/site.html +++ /dev/null @@ -1,27 +0,0 @@ -{# call-only #} -{% macro section(name, title=none, class_name=none) %} -
    - {% if title %} - {% call header() %} - {{ heading(title) }} - {% endcall %} - {% endif %} - {{ caller() }} -
    -{% endmacro %} - -{% macro header(class_name=none) %} -
    - {{ caller() }} -
    -{% endmacro %} - -{% macro heading(title, class_name=none) %} -

    - {% if not caller %} - {{ title }} - {% else %} - {{ caller() }} - {% endif %} -

    -{% endmacro %} diff --git a/client/src/pages/components/site_section.html b/client/src/pages/components/site_section.html deleted file mode 100644 index de100d0..0000000 --- a/client/src/pages/components/site_section.html +++ /dev/null @@ -1,13 +0,0 @@ -{% macro site_section(name) %} -
    - {{ caller() }} -
    -{% endmacro %} - -{% macro site_section_header(heading) %} -
    -

    - {{ heading }} -

    -
    -{% endmacro %} diff --git a/client/src/pages/components/support_sidebar.html b/client/src/pages/components/support_sidebar.html deleted file mode 100644 index 701ac56..0000000 --- a/client/src/pages/components/support_sidebar.html +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/client/src/pages/components/tabs.html b/client/src/pages/components/tabs.html deleted file mode 100644 index ab51ab9..0000000 --- a/client/src/pages/components/tabs.html +++ /dev/null @@ -1,68 +0,0 @@ - diff --git a/client/src/pages/components/timestamp.html b/client/src/pages/components/timestamp.html deleted file mode 100644 index 79457be..0000000 --- a/client/src/pages/components/timestamp.html +++ /dev/null @@ -1,14 +0,0 @@ -{% macro timestamp(time, is_relative=false, class_name=none) %} - {# `datetime` value should be an ISO string #} - -{% endmacro %} diff --git a/client/src/pages/components/timestamp.js b/client/src/pages/components/timestamp.js deleted file mode 100644 index 85f6825..0000000 --- a/client/src/pages/components/timestamp.js +++ /dev/null @@ -1,39 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -/** - * TODO: make it work with `Date` objects. - * @param {HTMLTimeElement} element - * @param {string} date - * @param {string} className - */ -export function Timestamp(element, date, isRelative = false, className = null) { - const timestamp = element ? element : initFromScratch(date, isRelative, className); - - return timestamp; -} - -/** - * @param {string} date - * @param {boolean} isRelative - * @param {string} className - */ -function initFromScratch(date, isRelative, className) { - /** - * @type {HTMLTimeElement} - */ - const timestamp = createComponent("timestamp"); - - timestamp.dateTime = date; - - if (className) { - timestamp.classList.add(className); - } - - if (isRelative) { - timestamp.textContent = date; - } else { - timestamp.textContent = date; - } - - return timestamp; -} diff --git a/client/src/pages/components/tooltip.html b/client/src/pages/components/tooltip.html deleted file mode 100644 index 9bcc918..0000000 --- a/client/src/pages/components/tooltip.html +++ /dev/null @@ -1,22 +0,0 @@ -{% from 'components/buttons.html' import button %} -{% from 'components/links.html' import kemono_link %} - -{% macro tooltip() %} -
    - {{ button('Close', 'tooltip__close') }} - {{ caller() }} -
    -{% endmacro %} - -{% macro register_message(action_name) %} -

    - {{ action_name }} is only available to registered users. -
    - Visit the {{ kemono_link('/account/login?location=' + request.path, 'login page', is_noop=false) }} if you have an account. -
    - Otherwise visit the {{ kemono_link('/account/register?location=' + request.path, 'registration page', is_noop=false) }} to create one. -

    -{% endmacro %} diff --git a/client/src/pages/components/tooltip.js b/client/src/pages/components/tooltip.js deleted file mode 100644 index dd2eec2..0000000 --- a/client/src/pages/components/tooltip.js +++ /dev/null @@ -1,65 +0,0 @@ -import { createComponent } from "@wp/js/component-factory"; - -/** - * @type {HTMLDivElement} - */ -const tooltip = document.getElementById("flying-tooltip"); -/** - * @type {[HTMLButtonElement, HTMLSpanElement]} - */ -const [closeButton, messageContainer] = tooltip.children; - -closeButton.addEventListener("click", (event) => { - tooltip.classList.remove("tooltip--shown"); -}); - -/** - * @param {HTMLElement} element - * @param {HTMLParagraphElement} messageElement - */ -export function showTooltip(element, messageElement) { - const { left, bottom } = element.getBoundingClientRect(); - - tooltip.classList.remove("tooltip--shown"); - messageContainer.replaceWith(messageElement); - tooltip.style.setProperty("--local-x", `${left}px`); - tooltip.style.setProperty("--local-y", `${bottom}px`); - tooltip.classList.add("tooltip--shown"); -} - -/** - * TODO: init from `action_name` - * @param {HTMLElement} element - * @param {string} actionName - */ -export function registerMessage(element, actionName = "") { - /** - * @type {HTMLParagraphElement} - */ - const messageElement = element ? element : initFromScratch(actionName); - - return messageElement; -} - -/** - * @param {HTMLElement} element - */ -function initFromElement(element) {} - -/** - * @param {string} actionName - */ -function initFromScratch(actionName) { - /** - * @type {HTMLParagraphElement} - */ - const message = createComponent("tooltip__message tooltip__message--register"); - /** - * @type {HTMLSpanElement} - */ - const action = message.querySelector(".tooltip__action"); - - action.textContent = actionName; - - return message; -} diff --git a/client/src/pages/contact.tsx b/client/src/pages/contact.tsx new file mode 100644 index 0000000..99cf163 --- /dev/null +++ b/client/src/pages/contact.tsx @@ -0,0 +1,17 @@ +import { PageSkeleton } from "#components/pages"; + +export function ContactPage() { + const title = "Contact Us"; + const heading = "Contact Us"; + + return ( + +

    + Contact email: contact@kemono.ru +

    +

    + Please allow up to 3-5 working days for your request to be processed. You may not receive a response if we are not interested. +

    +
    + ); +} diff --git a/client/src/pages/development/_index.scss b/client/src/pages/development/_index.scss deleted file mode 100644 index a3235dd..0000000 --- a/client/src/pages/development/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use "components"; -@use "design"; diff --git a/client/src/pages/development/closure.html b/client/src/pages/development/closure.html deleted file mode 100644 index cf7b73e..0000000 --- a/client/src/pages/development/closure.html +++ /dev/null @@ -1,26 +0,0 @@ -{# taken from https://gist.github.com/dah33/e18e71a81d1a0aaf59658269ada963b3 #} - -{% macro enclose(fn, env) %} - {% set closure = namespace(fn=fn, env=env) %} - {% do return(closure) %} -{% endmacro %} - -{% macro call1(closure, x1) %} - {% do return(closure.fn(x1, closure.env)) %} -{% endmacro %} - -{% macro call2(closure, x1, x2) %} - {% do return(closure.fn(x1, x2, closure.env)) %} -{% endmacro %} - -{# Example: #} - -{% macro power(x, kwargs) %} - {% do return(x**kwargs.exponent) %} -{% endmacro %} - -{# -{% set square = enclose(power, dict(exponent=2)) %} - -{{ call1(square, 8) }}{# = 8**2 = 64 #} -#} diff --git a/client/src/pages/development/components/_index.scss b/client/src/pages/development/components/_index.scss deleted file mode 100644 index af1b252..0000000 --- a/client/src/pages/development/components/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use "forms"; -@use "inputs"; diff --git a/client/src/pages/development/components/forms.html b/client/src/pages/development/components/forms.html deleted file mode 100644 index bd34b3f..0000000 --- a/client/src/pages/development/components/forms.html +++ /dev/null @@ -1,17 +0,0 @@ -{% from 'components/meta/attributes.html' import attributes %} - -{% macro form() %} -
    {{ caller() if caller }}
    -{% endmacro %} - -{% macro section() %} -
    {{ caller() if caller }}
    -{% endmacro %} - -{% macro label(text=none) %} - -{% endmacro %} - -{% macro input() %} - -{% endmacro %} diff --git a/client/src/pages/development/components/forms.scss b/client/src/pages/development/components/forms.scss deleted file mode 100644 index 501af59..0000000 --- a/client/src/pages/development/components/forms.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use "../../../css/config/variables" as *; - -.dev-form { - display: grid; - grid-template-columns: 1fr; - grid-auto-rows: auto; - gap: $size-normal; - max-width: $width-mobile; - margin: 0 auto; - - &__section { - border: none; - padding: 0; - margin: 0; - - &--submit { - text-align: center; - } - } - - &__label { - display: inline-block; - } - - &__input { - min-width: 44px; - min-height: 44px; - width: 100%; - padding: $size-small; - } -} diff --git a/client/src/pages/development/components/inputs.html b/client/src/pages/development/components/inputs.html deleted file mode 100644 index 9935918..0000000 --- a/client/src/pages/development/components/inputs.html +++ /dev/null @@ -1,25 +0,0 @@ -{% from 'components/meta/attributes.html' import attributes %} - -{% import 'development/components/forms.html' as forms %} - -{% macro text(id, text) %} - {% call forms.section() %} - {{ forms.label(text, for=id) }} - {{ forms.input(id=id, - type="text", - **kwargs - ) }} - {% endcall %} -{% endmacro %} - -{% macro submit_button(text=none) %} - {% call forms.section(class='dev-form__section--submit') %} - - {% endcall %} -{% endmacro %} diff --git a/client/src/pages/development/components/inputs.scss b/client/src/pages/development/components/inputs.scss deleted file mode 100644 index 24d2370..0000000 --- a/client/src/pages/development/components/inputs.scss +++ /dev/null @@ -1,11 +0,0 @@ -@use "../../../css/config/variables" as *; - -.dev-form__input { -} - -.dev-form__submit { - min-width: 44px; - min-height: 44px; - width: auto; - padding: $size-small; -} diff --git a/client/src/pages/development/components/nav.html b/client/src/pages/development/components/nav.html deleted file mode 100644 index 524c5cd..0000000 --- a/client/src/pages/development/components/nav.html +++ /dev/null @@ -1,15 +0,0 @@ -{% from 'components/navigation/base.html' import navigation, nav_list, nav_item %} -{% from 'components/links.html' import kemono_link %} - -{# `nav_items` is a list of tuples `(url, title)` #} -{% macro dev_nav(nav_items, id=none) %} - {% call navigation(id) %} - {% call nav_list() %} - {% for url, title in nav_items %} - {% call nav_item() %} - {{ kemono_link(url, title, is_noop= false) }} - {% endcall %} - {% endfor %} - {% endcall %} - {% endcall %} -{% endmacro %} diff --git a/client/src/pages/development/config.html b/client/src/pages/development/config.html deleted file mode 100644 index 803abe3..0000000 --- a/client/src/pages/development/config.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} - -{% block content %} -{% call site.section('test-entries', 'Test Database') %} -
    -
    -

    Press "Activate" to:

    -
      -
    • Add test service keys
    • -
    -
    -
    - -
    -
    - -
    -
    -

    Press "Activate" to:

    -
      -
    • Add test accounts
    • -
    -
    -
    - -
    -
    -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/development/design/_index.scss b/client/src/pages/development/design/_index.scss deleted file mode 100644 index a232bd3..0000000 --- a/client/src/pages/development/design/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@use "wip"; diff --git a/client/src/pages/development/design/current/home.html b/client/src/pages/development/design/current/home.html deleted file mode 100644 index b24447b..0000000 --- a/client/src/pages/development/design/current/home.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'development/components/nav.html' import dev_nav %} - -{% set nav_items = [ - ('/development/design', 'Home'), -] %} - -{% block content %} -{% call site.section('design-current', 'Current') %} - {{ dev_nav(nav_items) }} -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/development/design/home.html b/client/src/pages/development/design/home.html deleted file mode 100644 index 774c544..0000000 --- a/client/src/pages/development/design/home.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'development/components/nav.html' import dev_nav %} - -{% set nav_items = [ - ('/development/design/current', 'Current'), - ('/development/design/upcoming', 'Upcoming'), - ('/development/design/wip', 'Work In Progress'), -] %} - -{% block content %} -{% call site.section('design', 'Design Overview') %} - {{ dev_nav(nav_items) }} -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/development/design/upcoming/home.html b/client/src/pages/development/design/upcoming/home.html deleted file mode 100644 index f71614c..0000000 --- a/client/src/pages/development/design/upcoming/home.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'development/components/nav.html' import dev_nav %} - -{% set nav_items = [ - ('/development/design', 'Home'), -] %} - -{% block content %} -{% call site.section('design-upcoming', 'Upcoming') %} - {{ dev_nav(nav_items) }} -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/development/design/wip/_index.scss b/client/src/pages/development/design/wip/_index.scss deleted file mode 100644 index 495fed2..0000000 --- a/client/src/pages/development/design/wip/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@use "home"; diff --git a/client/src/pages/development/design/wip/home.html b/client/src/pages/development/design/wip/home.html deleted file mode 100644 index fbd9e59..0000000 --- a/client/src/pages/development/design/wip/home.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} -{% import 'development/components/forms.html' as forms %} -{% import 'development/components/inputs.html' as inputs %} -{% from 'components/navigation/local.html' import local_nav %} -{% from 'development/components/nav.html' import dev_nav %} - -{% set page_title = 'Work In Progress Designs | ' ~ g.site_name %} - -{% block title %} - - {{ page_title }} - -{% endblock title %} - -{% block content %} -{% call site.section('development-design-wip', 'Work In Progress Designs') %} - {{ dev_nav([ - ('/development/design', 'Home'), - ]) }} - {{ local_nav([ - ('forms', 'Forms') - ]) }} - {% call site.article(id='forms') %} -

    Forms

    - {% call forms.form() %} -

    Form example

    - {{ inputs.text('form-text', 'Text Input:') }} - {{ inputs.submit_button('Submit Button') }} - {% endcall %} - {% endcall %} -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/development/design/wip/home.scss b/client/src/pages/development/design/wip/home.scss deleted file mode 100644 index 5f8e844..0000000 --- a/client/src/pages/development/design/wip/home.scss +++ /dev/null @@ -1,2 +0,0 @@ -.site-section--development-design-wip { -} diff --git a/client/src/pages/development/home.html b/client/src/pages/development/home.html deleted file mode 100644 index 15de9fd..0000000 --- a/client/src/pages/development/home.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'development/components/nav.html' import dev_nav %} - -{# ('/development/test-entries', 'Test entries'), #} -{% set nav_items = [ - ('/development/design', 'Design'), -] %} - -{% block content %} -{% call site.section('dev-only', g.site_name ~' dev') %} - {{ dev_nav(nav_items) }} -
    -
    -

    - Press "Activate" to create a seeded database. -

    -
    -
    - -
    -
    - -
    -
    -

    - Press "Activate" to create a random database. -

    -
    -
    - -
    -
    -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/development/shell.html b/client/src/pages/development/shell.html deleted file mode 100644 index b00fc94..0000000 --- a/client/src/pages/development/shell.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends 'components/shell.html' %} - - {% block bundler_output %} - {# quick hack until writing proper loader #} - <% for (const css in htmlWebpackPlugin.files.css) { %> - <% if (htmlWebpackPlugin.files.css[css].startsWith("/static/bundle/css/development")) { %> - - <% } %> - <% } %> - <% for (const chunk in htmlWebpackPlugin.files.chunks) { %> - - <% } %> - <% for (const scriptPath in htmlWebpackPlugin.files.js) { %> - <% if (htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/development") | htmlWebpackPlugin.files.js[scriptPath].startsWith("/static/bundle/js/runtime")) { %> - - <% } %> - <% } %> - {% endblock bundler_output %} diff --git a/client/src/pages/development/test_entries.html b/client/src/pages/development/test_entries.html deleted file mode 100644 index 73910b5..0000000 --- a/client/src/pages/development/test_entries.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends 'development/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'development/components/nav.html' import dev_nav %} - -{% set nav_items = [ - ('/development', 'Home') -] %} - -{% block content %} -{% call site.section('test-entries', 'Test entries') %} - {{ dev_nav(nav_items) }} -
    -
    -

    - Press "Activate" to create a seeded database. -

    -
    -
    - -
    -
    - -
    -
    -

    - Press "Activate" to create a random database. -

    -
    -
    - -
    -
    -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/discord-channel.module.scss b/client/src/pages/discord-channel.module.scss new file mode 100644 index 0000000..13cf119 --- /dev/null +++ b/client/src/pages/discord-channel.module.scss @@ -0,0 +1,5 @@ +.main { + display: flex; + flex-direction: row; + gap: 1em; +} diff --git a/client/src/pages/discord-channel.tsx b/client/src/pages/discord-channel.tsx new file mode 100644 index 0000000..18161ce --- /dev/null +++ b/client/src/pages/discord-channel.tsx @@ -0,0 +1,77 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { parseOffset } from "#lib/pagination"; +import { fetchDiscordChannel, fetchDiscordServer } from "#api/profiles/discord"; +import { PageSkeleton } from "#components/pages"; +import { + DiscordMessages, + DiscordServer, + IDiscordChannelMessage, +} from "#entities/posts"; + +import * as styles from "./discord-channel.module.scss"; + +interface IProps { + serverID: string; + channelID: string; + channels: { id: string; name: string }[]; + messages: IDiscordChannelMessage[]; + offset?: number; +} + +export function DiscordChannelPage() { + const { serverID, channelID, channels, messages, offset } = + useLoaderData() as IProps; + const title = "Discord channel"; + const heading = "Discord Channel"; + + return ( + +
    + + +
    +
    + ); +} + +export async function loader({ + params, + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + const serverID = params.server_id?.trim(); + if (!serverID) { + throw new Error("Server ID is required."); + } + + const channelID = params.channel_id?.trim(); + if (!channelID) { + throw new Error("Channel ID is required."); + } + + let offset: number | undefined; + { + const inputOffset = searchParams.get("o"); + + if (inputOffset) { + offset = parseOffset(inputOffset, 150); + } + } + + const channels = await fetchDiscordServer(serverID); + const messages = await fetchDiscordChannel(channelID, offset); + + return { + serverID, + channelID, + channels, + messages, + offset, + }; +} diff --git a/client/src/pages/discord.html b/client/src/pages/discord.html deleted file mode 100644 index 328a52e..0000000 --- a/client/src/pages/discord.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - {{ g.site_name }} - - - - - - - -
    -
    -
    - -
    -
    - - - - \ No newline at end of file diff --git a/client/src/pages/discord.module.scss b/client/src/pages/discord.module.scss new file mode 100644 index 0000000..13cf119 --- /dev/null +++ b/client/src/pages/discord.module.scss @@ -0,0 +1,5 @@ +.main { + display: flex; + flex-direction: row; + gap: 1em; +} diff --git a/client/src/pages/discord.tsx b/client/src/pages/discord.tsx new file mode 100644 index 0000000..5ef9a8c --- /dev/null +++ b/client/src/pages/discord.tsx @@ -0,0 +1,39 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { fetchDiscordServer } from "#api/profiles/discord"; +import { PageSkeleton } from "#components/pages"; +import { DiscordServer } from "#entities/posts"; + +import * as styles from "./discord.module.scss"; + +interface IProps { + serverID: string; + channels: { id: string; name: string }[]; +} + +export function DiscordServerPage() { + const { serverID, channels } = useLoaderData() as IProps; + const title = "Discord server"; + const heading = "Discord Server"; + + return ( + +
    + +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const serverID = params.server_id?.trim(); + if (!serverID) { + throw new Error("Server ID is required."); + } + + const channels = await fetchDiscordServer(serverID); + + return { + serverID, + channels, + }; +} diff --git a/client/src/pages/dmca.tsx b/client/src/pages/dmca.tsx new file mode 100644 index 0000000..821f25a --- /dev/null +++ b/client/src/pages/dmca.tsx @@ -0,0 +1,144 @@ +import { PageSkeleton } from "#components/pages"; + +export function DMCAPage() { + const title = "DMCA Notice"; + const heading = "DMCA Notice"; + + return ( + +

    + Please allow up to 3-5 working days for your request to be processed. + You may not receive a response unless further information is needed from + you. +

    + +

    + This information is required by the DMCA process. If any required + information is falsified or omitted, your request may be not be + processed. +

    + +

    + Notices may be shared to third parties for due diligence and transparency + purposes. +

    + +

    + Please be aware that information that is not protected by copyright may + not be removed from the site. This includes AI generated content, + creator tags, creator profiles, wiki pages, links to social media + profiles or other websites, links to works on other websites, and other + factual information not protected by copyright law. +

    + +

    + Please note that we will only honor content removal requests from the + following parties: +

    + +
      +
    • Legal copyright holder of the content
    • +
    • Legal copyright holder of the character(s)
    • +
    • The commissioner(s) of the content
    • +
    + +

    + Please consult the guide below for further information regarding when + it's acceptable to submit a takedown request and when it's not. +

    + +

    + You SHOULD submit a takedown request if you are one of + the following: +

    + +
      +
    • Creator wanting their own content removed.
    • +
    • + Character owner wanting content featuring their character removed. +
    • +
    • A commissioner who has paid for the content in question.
    • +
    • + Publisher wanting a publication or excerpts of a publication removed. +
    • +
    + +

    + You SHOULD NOT submit a takedown request if you are one + of the following: +

    + +
      +
    • + User wanting content removed because they believe it breaks a site + rule. You should instead use the site's Flag For Deletion tool (found + on every post's page) for each post that you wish to dispute. +
    • +
    • + Friend, relative, or fan of an creator, character owner, or + commissioner who wishes to act as a middlemen for the creator, + character owner, or commissioner. +
    • +
    • + Commissioner or character owner wanting to remove art that the creator + themselves has posted. In those cases please ask the creator to file a + takedown request for you. +
    • +
    + +

    Your email must include the following:

    + +
      +
    • + Your contact information, including your name, physical address, phone + number, and email address. +
    • +
    • + Identification of the material you wish to have removed, with enough + information to locate the material. For example, a list of links to + each post on Kemono you wish to have removed. Screenshots or simply + listing your creator tag, creator name, or social media accounts are + not sufficient to locate the exact material you wish to have removed. +
    • +
    • + Identification of the copyrighted work you claim is being infringed. + For example, for each Kemono post you claim infringes on your + copyright, a link to where the original work was posted on your Pixiv, + Twitter, or other social media accounts or personal websites. +
    • +
    • + A statement that you have a good faith belief that the use of the work + you believe is being infringed was not authorized by the copyright + owner, an agent of the owner, or the law. +
    • +
    • + A statement that everything contained in the takedown notice is + accurate and that, under penalty of perjury, you are the copyright + owner or have permission to act on the copyright owner’s behalf. +
    • +
    • + Your signature. This must be your full legal name, not a pseudonym or + creator handle. +
    • +
    + +

    + This notice can be sent via email to us on our Legal Inquiries address + found on the contact page. +

    + +

    + + Failure to follow these instructions, or emailing it to the wrong + department, or emailing all three departments at once will get your + notice discarded. + +

    + +

    + For further assistance, or for any questions regarding takedowns, please{" "} + contact us. +

    +
    + ); +} diff --git a/client/src/pages/documentation/api.module.scss b/client/src/pages/documentation/api.module.scss new file mode 100644 index 0000000..6abe3a2 --- /dev/null +++ b/client/src/pages/documentation/api.module.scss @@ -0,0 +1,4 @@ +.block { + background: #ffffff; + width: 100%; +} diff --git a/client/src/pages/documentation/api.tsx b/client/src/pages/documentation/api.tsx new file mode 100644 index 0000000..6cda990 --- /dev/null +++ b/client/src/pages/documentation/api.tsx @@ -0,0 +1,27 @@ +import { Helmet } from "@dr.pogodin/react-helmet"; +import SwaggerUI from "swagger-ui-react"; +import { PageSkeleton } from "#components/pages"; + +// TODO: path alias it after moving out of server folder +import schema from "../../../../schema/public/api.yaml"; + +import "swagger-ui-react/swagger-ui.css"; +import * as styles from "./api.module.scss"; + +export function Component() { + const title = "API documenation"; + const heading = "API Documenation"; + + return ( + + + + +
    + +
    +
    + ); +} + +Component.displayName = "APIDocumentationPage"; diff --git a/client/src/pages/error.html b/client/src/pages/error.html deleted file mode 100644 index d6a82e7..0000000 --- a/client/src/pages/error.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} -

    Error

    -

    {{ props.get('message') }}

    - {% if props.get('redirect') %} - -

    Redirecting you back...

    - {% endif %} -{% endblock %} \ No newline at end of file diff --git a/client/src/pages/errors/404.tsx b/client/src/pages/errors/404.tsx new file mode 100644 index 0000000..b1fa0b3 --- /dev/null +++ b/client/src/pages/errors/404.tsx @@ -0,0 +1,22 @@ +import { KemonoLink } from "#components/links"; +import { useNavigate } from "react-router"; +import * as styles from "./errors.module.scss"; + +export function Error404() { + const navigate = useNavigate(); + + function goBack() { + navigate(-1); + } + + return ( +
    +

    404

    +
    The page you are looking for does not exist.
    +
    + Home{" | "} + Go Back +
    +
    + ) +} diff --git a/client/src/pages/errors/errors.module.scss b/client/src/pages/errors/errors.module.scss new file mode 100644 index 0000000..37722e0 --- /dev/null +++ b/client/src/pages/errors/errors.module.scss @@ -0,0 +1,13 @@ +.errorPage { + width: fit-content; + margin-left: auto; + margin-right: auto; + margin-top: 10vh; + text-align: center; + + h1 { + margin-bottom: 5vh; + font-size: 500%; + user-select: none; + } +} diff --git a/client/src/pages/fanboximports.tsx b/client/src/pages/fanboximports.tsx new file mode 100644 index 0000000..0c0ab57 --- /dev/null +++ b/client/src/pages/fanboximports.tsx @@ -0,0 +1,38 @@ +import { PageSkeleton } from "#components/pages"; + +export function FanboxImportsPage() { + const title = "Fanbox Importer"; + const heading = "Fanbox Importer"; + + return ( + +

    + Auto-Import is disabled, please import the keys at your own discretion. +

    +

    + While we made sure to take steps to prevent detection and association of + the accounts with our importing, we can not make any guarantees. +

    +

    + This is a test run that is not optimized for performance but rather for + consistency and human emulation. +

    +

    + The system is accessible in this testing phase, if you submit your + fanbox key it should start importing most of your data with the improved + importer logic. +

    +

    + Once the testing is finished, we will scale up the services required for + more performant importing. +

    +

    + In case of you getting a notification from fanbox, do contact us via + Telegram and Chan that can be found in the bottom of the menu to the + left, or send an email to{" "} + contact@kemono.su +

    +

    Do include the content/screenshot of the notification from fanbox.

    +
    + ); +} diff --git a/client/src/pages/favorites.html b/client/src/pages/favorites.html deleted file mode 100644 index f76a296..0000000 --- a/client/src/pages/favorites.html +++ /dev/null @@ -1,111 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/user.html' import user_card, user_card_header %} -{% from 'components/cards/post.html' import post_card %} -{% from 'components/ads.html' import slider_ad, header_ad, footer_ad %} - -{% block scripts_extra %} - -{% endblock scripts_extra %} - -{% block content %} -{{ slider_ad() }} -
    - {{ header_ad() }} -
    -

    Favorite {{ g.artists_or_creators if props.fave_type == 'artist' else 'Posts' }}

    -
    - - {% if source == 'session' %} -
    - - - - - - - - - - - - - - -
    NameService
    This feature requires Javascript.
    - -
    - {% else %} - - {% if props.fave_type == "artist" %} -
    - {% include 'components/paginator.html' %} -
    - - {% call card_list('phone') %} - {% for user in results %} - {{ user_card(user, is_updated=true) }} - {% else %} -

    Nobody here but us chickens!

    -

    - There are no {{ g.artists_or_creators|lower }}. -

    - {% endfor %} - {% endcall %} - -
    - {% include 'components/paginator.html' %} -
    - {% else %} -
    - {% include 'components/paginator.html' %} -
    - - {% call card_list() %} - {% for post in results %} - {{ post_card(post) }} - {% else %} -

    Nobody here but us chickens!

    -

    - There are no more posts. -

    - {% endfor %} - {% endcall %} - -
    - {% include 'components/paginator.html' %} -
    - {% endif %} - {% endif %} - {{ footer_ad() }} -
    -{% endblock content %} diff --git a/client/src/pages/favorites.scss b/client/src/pages/favorites.scss deleted file mode 100644 index 8b279b6..0000000 --- a/client/src/pages/favorites.scss +++ /dev/null @@ -1,16 +0,0 @@ -.site-section--favorites { - div.dropdowns { - display: grid; - grid-template-columns: max-content max-content; - grid-gap: 5px; - justify-content: center; - } - - div.dropdowns > label { - text-align: right; - } - - div.dropdowns > label:after { - content: ":"; - } -} diff --git a/client/src/pages/favorites.tsx b/client/src/pages/favorites.tsx new file mode 100644 index 0000000..dbb7231 --- /dev/null +++ b/client/src/pages/favorites.tsx @@ -0,0 +1,6 @@ +import { redirect } from "react-router"; +import { createAccountFavoriteProfilesPageURL } from "#lib/urls"; + +export async function loader() { + return redirect(String(createAccountFavoriteProfilesPageURL())); +} diff --git a/client/src/pages/file/archive.module.scss b/client/src/pages/file/archive.module.scss new file mode 100644 index 0000000..ce9fe0a --- /dev/null +++ b/client/src/pages/file/archive.module.scss @@ -0,0 +1,35 @@ +@use "../../css/config/variables/sass.scss" as *; + +.article { + h3 { + margin-bottom: 0.25em; + } + + .error { + color: var(--negative-colour1-primary); + } + + .code { + height: 30px; + user-select: all; + background-color: var(--colour1-secondary); + border: 1px solid var(--colour1-tertiary); + border-radius: 5px; + padding: 2px; + top: 1px; + } + + section { + margin-top: 1em; + + ul { + margin-top: 4px; + } + } + + input[type="submit"]:disabled { + color: hsl(0deg 0% 40%); + background-image: linear-gradient(#4a5059, #4a5059); + cursor: progress; + } +} diff --git a/client/src/pages/file/archive.tsx b/client/src/pages/file/archive.tsx new file mode 100644 index 0000000..1c5e72c --- /dev/null +++ b/client/src/pages/file/archive.tsx @@ -0,0 +1,176 @@ +import { + LoaderFunctionArgs, + useLoaderData, + useNavigate, +} from "react-router"; +import { apiFetchArchiveFile, apiSetArchiveFilePassword } from "#api/files"; +import { PageSkeleton } from "#components/pages"; +import { IS_FILE_SERVING_ENABLED } from "#env/env-vars"; +import { createArchiveFileURL } from "#lib/urls"; +import { KemonoLink } from "#components/links"; +import { IArchiveFile } from "#api/files"; +import { FormEvent, useState } from "react"; + +import * as styles from "./archive.module.scss"; + +interface IProps { + archive: IArchiveFile; +} + +interface IFileItemProps { + name: string; + archiveHash: string; + archiveExtension: string; + password?: string; +} + +function FileItem({ + name, + archiveHash, + archiveExtension, + password, +}: IFileItemProps) { + return ( +
  • + {!IS_FILE_SERVING_ENABLED ? ( + name + ) : ( + + {name} + + )} +
  • + ); +} + +export function ArchiveFilePage() { + const { archive: { file: { hash, ext }, file_list, password: loaderPassword } } = useLoaderData() as IProps; + const title = `Archive file "${hash}" details`; + const heading = "Archive File Details"; + const [error, setError] = useState(""); + const [password, setPassword] = useState(loaderPassword); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + async function submitPassword(event: FormEvent) { + event.preventDefault(); + let form = event.target as HTMLFormElement; + let password = (form.elements.namedItem("password-input") as HTMLInputElement).value; + if (!password) { + setError("Please enter a password."); + } else { + setLoading(true); + try { + await apiSetArchiveFilePassword(hash, password); + setPassword(password); + } catch { + setError("Invalid password"); + } finally { + setLoading(false); + } + } + } + + return ( + + + navigate(-1)}>« Go Back{" | "} + Find Posts + +
    +
    +

    Hash: {hash}

    + {password ? ( +
    Password: {password}
    + ) : (password === "") ? ( + <> + Password required but not found. +
    + + + +
    + + ) : ( + <>Password not required. + )} +
    + +
    +
    Files
    + {file_list.length === 0 ? ( + <>Archive is empty or missing password. + ) : ( +
      + {file_list.map((filename, index) => ( + + ))} +
    + )} +
    +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const fileHash = params.file_hash?.trim(); + + if (!fileHash) { + throw new Error("File hash is required."); + } + + const archive = await apiFetchArchiveFile(fileHash); + + if (!archive) { + throw new Error("Archive file not found."); + } + + return { + archive, + }; +} +/* +export async function action({ params, request }: ActionFunctionArgs) { + try { + const method = request.method; + + switch (method) { + case "PATCH": { + const fileHash = params.file_hash?.trim(); + + if (!fileHash) { + throw new Error("File hash is required."); + } + + const data = await request.formData(); + const password = data.get("password") as string | null; + + if (!password) { + throw new Error("Password is required."); + } + + await apiSetArchiveFilePassword(fileHash, password); + const url = String(createFilePageURL(fileHash)); + + return redirect(url); + } + + default: { + throw new Error(`Unknown method "${method}".`); + } + } + } catch (error) { + return error; + } +} +*/ diff --git a/client/src/pages/file/legacy.tsx b/client/src/pages/file/legacy.tsx new file mode 100644 index 0000000..6e046a0 --- /dev/null +++ b/client/src/pages/file/legacy.tsx @@ -0,0 +1,40 @@ +import { + LoaderFunctionArgs, + useLoaderData, + useNavigate, +} from "react-router"; +import { createFilePageURL } from "#lib/urls"; +import { useEffect } from "react"; + +interface IProps { + hash: string; +} +// doing this roundabout way because `redirect` returned from loaders +// doesn't overwrite history stack, even if redirect is "permanent" + +export function LegacyFilePage() { + const { hash } = useLoaderData() as IProps; + const navigate = useNavigate(); + + const url = String(createFilePageURL(hash)); + + useEffect(() => { + navigate(url, { replace: true }); + }, []); + + return <>; +} + +export async function loader({ params }: LoaderFunctionArgs) { + const fileHash = params.file_hash?.trim(); + + if (!fileHash) { + throw new Error("File hash is required."); + } + + const props: IProps = { + hash: fileHash, + }; + + return props; +} diff --git a/client/src/pages/gumroad-and-co.tsx b/client/src/pages/gumroad-and-co.tsx new file mode 100644 index 0000000..780b755 --- /dev/null +++ b/client/src/pages/gumroad-and-co.tsx @@ -0,0 +1,54 @@ +import { PageSkeleton } from "#components/pages"; + +export function GumroadAndCoPage() { + const title = "Gumroad and Co"; + const heading = "Gumroad and Co"; + + return ( + +

    TL;DR (browse ») == archive password sharing and validation

    + +

    + To make a long story short, you are now able to list (not view) the + contents of the archives and submit passwords for archives that are + password protected. +
    + Upon submitting the correct password, the page will re-load and display + the valid password. +

    + +

    + + As for gumroad, they sure are deep-throating the payment processors, + shaft and balls. + {" "} + We'll see how it goes, but we do know where it'll end. +
    + Either way, we didn't expect this to happen this fast. The importer is + being worked on every day to scrape whatever is missing. +
    + And to you, who are submitting keys, please check if your key contains + "...", browsers are kind of shit and will abbreviate anything that is + "too long". +

    +

    + If you see something missing from your imports (contents, posts, reward + text, etc.), do contact us via Telegram and Chan, which can be found at + the bottom of the menu to the left, or send an email to{" "} + contact@kemono.su +

    + +

    + In regard to Account Linking. You do not need to cross-link (A{">"}B,B + {">"}A) the profiles, a single direction is more than enough: (A{">"}B,A + {">"}C,A{">"}D) or (A{">"}B,B{">"}C,C{">"}D). +

    + +

    + Also, I think it was never mentioned, but there exists a cookie + "thumbSize" for this site, which controls the size of the displayed + tiles. +

    +
    + ); +} diff --git a/client/src/pages/help/faq.html b/client/src/pages/help/faq.html deleted file mode 100644 index c7788c1..0000000 --- a/client/src/pages/help/faq.html +++ /dev/null @@ -1,48 +0,0 @@ -{% extends 'components/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'components/navigation/local.html' import local_nav, local_list, local_item %} -{% from 'components/lists/faq.html' import faq_list, faq_section, faq_question, faq_answer %} - -{% set page_title = 'Frequently Asked Questions | ' ~ g.site_name %} - -{% block title %} - - {{ page_title }} - -{% endblock title %} - -{% block content %} -{% call site.section('help-faq', 'Frequently Asked Questions') %} - {% call local_nav(id="faq-nav") %} -

    Table of contents

    - {% call local_list() %} - {{ local_item("id1", "question1") }} - {{ local_item("id2", "question2") }} - {% endcall %} - {% endcall %} - -

    FAQ

    - {% call faq_list() %} - {% call faq_section(id="id1") %} - {% call faq_question() %} - question1 - {% endcall %} - - {% call faq_answer() %} - answer1 - {% endcall %} - {% endcall %} - - {% call faq_section(id="id2") %} - {% call faq_question() %} - question2 - {% endcall %} - - {% call faq_answer() %} - answer2 - {% endcall %} - {% endcall %} - {% endcall %} -{% endcall %} -{% endblock content %} diff --git a/client/src/pages/help/license.html b/client/src/pages/help/license.html deleted file mode 100644 index dff0ee5..0000000 --- a/client/src/pages/help/license.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends 'components/shell.html' %} - -{% block content %} -
    -
    -

    Open Source

    -

    - This website is running Kemono 2, which is provided for free under the BSD-3 License.
    -

    -          
    -Copyright 2020 kemono.party
    -
    -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
    -
    -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    -
    -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    -
    -3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    -
    -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
    -          
    -      
    -
    -
    -{% endblock %} diff --git a/client/src/pages/help/posts.html b/client/src/pages/help/posts.html deleted file mode 100644 index 87a825d..0000000 --- a/client/src/pages/help/posts.html +++ /dev/null @@ -1,25 +0,0 @@ -{% extends 'components/shell.html' %} - -{% block content %} -
    -
    -

    Posts

    -

    - A green border means the post is the parent of one or more "child" images.
    - A yellow border means the image has a parent.
    - An orange border means the post is user-shared.
    -
    - Multiple edits of the same post can appear on one page. -

    -

    Searching Posts

    -

    - Searching for posts is straightforward. Enter the terms you want to search for, and both titles and descriptions will be scanned for your query. For example, searching for mio yuuko will return every post that has both mio and yuuko in it. You can also exclude a term by putting a hyphen (-) in front of it, and search for a phrase by putting quotation marks around it. They work about how you would expect.
    - Please note that {{ g.site_name }} has limited support for non-English search terms due to database limitations. Most notably, Japanese characters cannot be searched. -

    -

    Flagging

    -

    - If there's something wrong with a post (like damaged/corrupted files) you can click Flag for reimport to have it purged and redownloaded the next time the importer encounters its ID. After that, simply import as usual. -

    -
    -
    -{% endblock %} diff --git a/client/src/pages/home.html b/client/src/pages/home.html deleted file mode 100644 index 396510c..0000000 --- a/client/src/pages/home.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends 'components/shell.html' %} - -{% import 'components/site.html' as site %} -{% from "components/links.html" import kemono_link %} - -{% block content %} - {% call site.section("home") %} - {% if g.banner_welcome %} - {{ g.banner_welcome|safe }} - {% endif %} -
    -
    - {% if g.mascot_path %} -
    -
    - -
    -
    - {% endif %} -
    - {% if g.logo_path %} -
    - -
    - {% endif %} -

    - {{ g.site_name }} is a public archiver for: -

    -
      - {% for paysite in g.paysite_list %} -
    • - {{ g.paysites[paysite].title }} -
    • - {% endfor %} -
    -

    - Contributors here upload content and share it here for easy searching and organization. To get started viewing content, either search for creators on the {{ kemono_link("/artists", g.artists_or_creators|lower ~ " page")}}, or search for content on the {{ kemono_link("/posts", "posts page") }}. If you want to contribute content, head over to the {{ kemono_link("/importer", "import page") }}. -

    - {% if g.welcome_credits %} -
    - {{ g.welcome_credits|safe }} -
    - {% endif %} -
    -
    - {% for announcement in g.announcements %} -
    -
    -

    {{ announcement.title }}

    -
    {{ announcement.date }}
    -
    -

    - {{ announcement.content|safe }} -

    -
    - {% endfor %} - {% endcall %} -{% endblock %} diff --git a/client/src/pages/home.scss b/client/src/pages/home.scss index 448eb1c..64b4d60 100644 --- a/client/src/pages/home.scss +++ b/client/src/pages/home.scss @@ -1,34 +1,36 @@ -@use "../css/config/variables" as *; +@use "../css/config/variables/sass" as *; -.site-section--home { -} // minheights +div.content-wrapper { + display: flex; + flex-direction: column; +} + .jumbo-welcome { overflow-y: hidden; position: relative; display: flex; - flex-direction: row; + flex-flow: row nowrap; box-shadow: 0 1px 3px rgb(0 0 0 / 25%); - align-items: center; - justify-content: flex-end; min-height: 450px; background-color: rgba(0, 0, 0, 0.7); - margin: 0.5rem; - @media (max-width: $width-tablet) { - background-color: #3b3e44; - } + padding: $size-small; + height: 100%; } .jumbo-welcome-mascot { transform: translateZ(0); display: flex; - max-height: 450px; width: 100%; height: 100%; @media (max-width: $width-tablet) { display: none; } + + div { + max-height: 70vh; + } } .jumbo-welcome-description { diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx new file mode 100644 index 0000000..2d62361 --- /dev/null +++ b/client/src/pages/home.tsx @@ -0,0 +1,102 @@ +import { + ARTISTS_OR_CREATORS, + BANNER_WELCOME, + HOME_BACKGROUND_IMAGE, + HOME_LOGO_PATH, + HOME_MASCOT_PATH, + HOME_WELCOME_CREDITS, + SITE_NAME, +} from "#env/env-vars"; +import { AVAILABLE_PAYSITE_LIST } from "#env/derived-vars"; +import { PageSkeleton } from "#components/pages"; +import { KemonoLink } from "#components/links"; + +export function HomePage() { + const title = "Welcome"; + + return ( + + {!BANNER_WELCOME ? undefined : ( +
    + )} + +
    +
    + + {!HOME_MASCOT_PATH ? undefined : ( +
    +
    + +
    +
    + )} + + +
    + + {BUNDLER_ENV_HOME_ANNOUNCEMENTS?.map((announcement) => ( +
    +
    +

    {announcement.title}

    +
    {announcement.date}
    +
    +

    +

    + ))} + + ); +} + +function Description() { + return ( +
    + {!HOME_LOGO_PATH ? undefined : ( +
    + +
    + )} + +

    + {SITE_NAME} is a public archiver for: +

    + +
      + {AVAILABLE_PAYSITE_LIST.map((paysite, index) => ( +
    • {paysite.title}
    • + ))} +
    + +

    + Contributors here upload content and share it here for easy searching + and organization. To get started viewing content, either search for + creators on the{" "} + + {ARTISTS_OR_CREATORS.toLowerCase()} page + + , or search for content on the{" "} + posts page. If you want to + contribute content, head over to the{" "} + import page. +

    + + {!HOME_WELCOME_CREDITS ? undefined : ( +
    + )} +
    + ); +} diff --git a/client/src/pages/importer/importer_list.module.scss b/client/src/pages/importer/importer_list.module.scss new file mode 100644 index 0000000..d886519 --- /dev/null +++ b/client/src/pages/importer/importer_list.module.scss @@ -0,0 +1,5 @@ +.error { + color: var(--negative-colour1-primary); + text-align: center; + margin-bottom: 1em; +} diff --git a/client/src/pages/importer/importer_list.tsx b/client/src/pages/importer/importer_list.tsx new file mode 100644 index 0000000..baeba0b --- /dev/null +++ b/client/src/pages/importer/importer_list.tsx @@ -0,0 +1,438 @@ +import clsx from "clsx"; +import { FormEvent, useState } from "react"; +import { redirect, useNavigate } from "react-router"; +import { PAYSITE_LIST, SITE_NAME } from "#env/env-vars"; +import { fetchCreateImport } from "#api/imports"; +import { PageSkeleton } from "#components/pages"; + +import * as styles from "./importer_list.module.scss"; + +const MAX_LENGTH = 1024; + +function titlize(s: string) { + return s.replace("_", " ").replace(/\b\w+/g, text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase()); +} + +interface Input { + name: string; + label?: string; + hint?: string; + default?: () => string; +} + +interface PaysiteForm { + name?: string; + inputs: Array; + // Returns an error message, or undefined if no errors. + validate: (...args: Array) => string | undefined; + includeDMs?: boolean; +} + +const PAYSITES: { [name: string]: PaysiteForm } = { + patreon: { + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + if (session_key.length != 43) + return `Invalid key: Expected 43 characters, got ${session_key.length}`; + }, + includeDMs: true, + }, + fanbox: { + name: "Pixiv Fanbox", + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + if (!/^\d+_\w+$/.test(session_key) || session_key.length > MAX_LENGTH) { + return "Invalid key."; + } + }, + }, + afdian: { + inputs: [ + { name: "session_key", label: "Auth Token", hint: "Can be found in cookies -> auth_token." }, + ], + validate({ session_key }) { + if (session_key.length > MAX_LENGTH) { + return "Key is too long."; + } + if (!/^[a-f0-9]+_\d+$/.test(session_key)) { + return "Invalid key."; + } + }, + }, + boosty: { + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + try { + JSON.parse(decodeURIComponent(session_key)); + } catch { + return "Invalid key: Expected valid JSON."; + } + }, + }, + discord: { + inputs: [ + { name: "session_key", label: "Token" }, + { name: "channel_ids", label: "Channel IDs", hint: "Separate with commas." }, + ], + validate({ session_key, channel_ids }) { + if (!/^(mfa.[a-z0-9_-]{20,})|([a-z0-9_-]{23,28}.[a-z0-9_-]{6,7}.[a-z0-9_-]{27,})/i.test(session_key)) { + return "Invalid token format."; + } + for (const id of channel_ids.split(/\s*,\s*/)) { + if (id && !parseInt(id)) { // + return `${id} is not a valid channel ID.`; + } + } + }, + }, + dlsite: { + name: "DLsite", + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + if (session_key.length > MAX_LENGTH) { + return "Key is too long."; + } + }, + }, + fantia: { + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + if (![32, 64].includes(session_key.length)) { + return "Invalid key: Expected 32 or 64 characters."; + } + }, + }, + gumroad: { + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + if (session_key.length < 200 || session_key.length > MAX_LENGTH) { + return `Invalid key: Expected 200 to ${MAX_LENGTH} characters.`; + } + }, + }, + subscribestar: { + name: "SubscribeStar", + inputs: [ + { name: "session_key" }, + ], + validate({ session_key }) { + if (session_key.length > MAX_LENGTH) { + return "Key is too long."; + } + }, + }, + onlyfans: { + name: "OnlyFans", + inputs: [ + { name: "session_key", hint: "Can be found in cookies -> sess." }, + { name: "auth_id", label: "User ID", hint: "Can be found in cookies -> auth_id." }, + { name: "x-bc", label: "BC Token", hint: "Can be found in local storage -> bcTokenSha. Paste localStorage.bcTokenSha into the console for easy access." }, + { + name: "user_agent", + label: "User-Agent", + hint: "This needs to be set to the User-Agent of the last device that logged into your OnlyFans account; leave it as the default value if you are on it right now.", + default: () => navigator.userAgent, + }, + ], + validate({ session_key, auth_id: user_id, "x-bc": bc_token, user_agent }) { + if (session_key.length > MAX_LENGTH) { + return "Key is too long."; + } + if (!parseInt(user_id)) { + return "User ID must consist of only digits."; + } + if (!bc_token.match(/^[a-f0-9]{40}$/)) { + return "Invalid BC token"; + } + }, + }, + fansly: { + inputs: [ + { + name: "session_key", + hint: ` + Copy the following string and enter it into the browser Console, + accessible by pressing F12. + btoa(JSON.stringify({...JSON.parse(localStorage?.session_active_session||'{}'),device:localStorage?.device_device_id})) + ` + }, + ], + validate({ session_key }) { + if (session_key.length == 71 && !/^[A-Za-z0-9]{71}$/.test(session_key)) { + return "The key doesn't match the required pattern."; + } + try { + if (!JSON.parse(atob(session_key))?.token) { + return "Token not found in JSON."; + } + } catch { + return "Key is not valid JSON." + } + }, + includeDMs: true, + }, + candfans: { + name: "CandFans", + inputs: [ + { name: "session_key", hint: "On CandFans page, Press F12 -> \"Application\" tab (check >> if its hidden) -> Storage: Cookies -> candfans.jp -> secure_candfans_session value." }, + ], + validate({ session_key }) { + try { + let keys = Object.keys(JSON.parse(atob(decodeURIComponent(session_key)))); + if (!["mac", "iv", "tag", "value"].every(key => keys.includes(key))) { + return "The key does not contain the appropriate values."; + } + } catch { + return "The key was not decodable."; + } + }, + } +} + +/** + * TODO: split into separate pages per service + */ +export function ImporterPage() { + const [selectedService, changeSelectedService] = useState(PAYSITE_LIST[0]); + const [error, setError] = useState(undefined); + const navigate = useNavigate(); + const title = "Import paywall posts/comments/DMs"; + const heading = "Import from Paysite"; + + async function onSubmit(event: FormEvent) { + event.preventDefault(); + setError(undefined); + let form = event.target as HTMLFormElement; + let inputs = form.querySelectorAll("input"); + let args: {[key: string]: string} = { service: selectedService }; + inputs.forEach(el => { + if (el.type == "checkbox") { + args[el.name] = el.checked ? "1" : "0"; + } else { + args[el.name] = el.value.trim(); + } + }); + let error = PAYSITES[selectedService].validate(args); + if (error) { + setError(error); + } else { + try { + let { import_id } = await fetchCreateImport(args as any); + await navigate(`/importer/status/${import_id}`); + } catch (resp: any) { + setError(resp.message) + } + } + } + + return ( + +
    +
    + + +
    + + {PAYSITES[selectedService].inputs.map((input, index) => { + return ( +
    + + + {index === 0 && ( + + Learn how to get your session key. + + )} + {input.hint && ( + + )} +
    + ) + })} + + {PAYSITES[selectedService].includeDMs && ( + + )} + + + + + + {selectedService == "fanbox" && ( + + )} + + + + + + +

    Important information

    +

    + Your session key is used to scrape paid posts from your feed. After + downloading missing posts, the key is immediately discarded and never + stored without permission. +

    + + {!PAYSITE_LIST.includes("fantia") ? undefined : ( + <> +

    Fantia

    +
      +
    • + At least one paid content must be unlocked for the post to be + imported. Free posts cannot be archived at this time. +
    • +
    • + In order to download post contents accurately, the importer will + automatically enable adult-viewing mode for duration of the import + if you have it turned off.{" "} + Do not change back to general-viewing during imports. +
    • +
    + + )} + +

    Auto-import

    +

    + The auto-import feature allows users to give {SITE_NAME} permission to + automatically detect and retrieve new posts and creators by storing + session keys long-term, without need for manual key submission. All keys + are encrypted using a strong RSA 4096 key. When the administrators start + a new autoimport round, a computer outside of {SITE_NAME}'s + infrastucture sends the private key to the backend, allowing it to + decrypt all working keys and start import tasks. Even if {SITE_NAME}'s + private database were to somehow be compromised, your tokens would + remain anonymous and secure. +
    + If you are logged into {SITE_NAME}, any key you submit with autoimport + enabled can be managed under the Keys section of your{" "} + [Account] tab in the header. There, you will be able to view + import logs or revoke access.{" "} + Please note that anonymously-submitted keys cannot be managed. +

    +
    + ); +} diff --git a/client/src/pages/importer/importer_ok.tsx b/client/src/pages/importer/importer_ok.tsx new file mode 100644 index 0000000..dae601b --- /dev/null +++ b/client/src/pages/importer/importer_ok.tsx @@ -0,0 +1,14 @@ +import { PageSkeleton } from "#components/pages"; + +export function ImporterOKPage() { + return ( + +

    + Your session key has been submitted to the server. Posts will be added + soon. Thank you for contributing! +
    + If you're having trouble with the importer, contact admin. +

    +
    + ); +} diff --git a/client/src/pages/importer/importer_status.module.scss b/client/src/pages/importer/importer_status.module.scss new file mode 100644 index 0000000..ff82227 --- /dev/null +++ b/client/src/pages/importer/importer_status.module.scss @@ -0,0 +1,5 @@ +@use "../../css/config/variables/sass" as *; + +.importList { + padding-left: 48px; +} diff --git a/client/src/pages/importer_status.scss b/client/src/pages/importer/importer_status.scss similarity index 97% rename from client/src/pages/importer_status.scss rename to client/src/pages/importer/importer_status.scss index e016f62..573af18 100644 --- a/client/src/pages/importer_status.scss +++ b/client/src/pages/importer/importer_status.scss @@ -1,4 +1,4 @@ -@use "../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .site-section--importer-status { .import { diff --git a/client/src/pages/importer/importer_status.tsx b/client/src/pages/importer/importer_status.tsx new file mode 100644 index 0000000..c401a5b --- /dev/null +++ b/client/src/pages/importer/importer_status.tsx @@ -0,0 +1,138 @@ +import { + LoaderFunctionArgs, + useLoaderData, +} from "react-router"; +import { createAccountDMsReviewPageURL } from "#lib/urls"; +import { fetchHasPendingDMs } from "#api/dms"; +import { fetchImportLogs } from "#api/imports"; +import { getLocalStorageItem, setLocalStorageItem } from "#storage/local"; +import { PageSkeleton } from "#components/pages"; +import { LoadingIcon } from "#components/loading"; + +import * as styles from "./importer_status.module.scss"; +import { KemonoLink } from "#components/links"; +import { useEffect, useState } from "react"; + +interface IProps { + importId: string; + isDMS?: boolean; +} + +export function ImporterStatusPage() { + const { importId, isDMS } = useLoaderData() as IProps; + const title = `Import "${importId}" logs`; + const heading = `Importer Logs for ${importId}`; + const cooldown = 15_000; + const [reverse, setReverse] = useState(false); + const [logs, setLogs] = useState | null>(null); + const [finished, setFinished] = useState(false); + + useEffect(() => { + if (finished) return; + async function fetchLogs() { + try { + let resp = await fetchImportLogs(importId); + setLogs(reverse ? [...resp].reverse() : [...resp]); + if (resp?.[resp?.length - 1]?.match(/Finished/i)) { + setFinished(true); + } else { + setFinished(false); + } + } catch (error) { + console.log(error) + } + } + + fetchLogs(); + + const id = setInterval(fetchLogs, cooldown); + + return () => clearInterval(id); + }, [reverse, finished]); + + function toggleReverse() { + setReverse(!reverse); + setLogs(logs?.reverse() ?? []); + } + + return ( + + {!logs ? ( +

    + Wait until logs load... +

    + ) : logs.length ? ( + <> + {isDMS && ( +
    + Hey! +

    + You gave the importer permission to access your messages. To protect your anonymity, you must manually approve each one. Wait until after the importer says Done importing DMs, then go go{" "} + + here + {" "} + to choose which ones you wish to import. to choose which ones you wish to import. +

    +
    + )} +
    +
    + Status: {finished ? "Completed" : "In Progress"} + Total: {logs?.length} +
    + +
    +
      + {logs?.map((message, index) => ( +
    1. {message}
    2. + ))} +
    + + ) : ( +

    No logs found!

    + )} +
    + ); +} + +async function initPendingReviewDms( + forceReload = false, + minutesForRecheck = 30 +) { + let hasPendingReviewDms = getLocalStorageItem("has_pending_review_dms") === "true"; + const lastCheckedHasPendingReviewDms = parseInt( + getLocalStorageItem("last_checked_has_pending_review_dms") ?? "0", + 10 + ); + + if ( + forceReload || + !lastCheckedHasPendingReviewDms || + lastCheckedHasPendingReviewDms < Date.now() - minutesForRecheck * 60 * 1000 + ) { + hasPendingReviewDms = await fetchHasPendingDMs(); + setLocalStorageItem("has_pending_review_dms", String(hasPendingReviewDms)); + localStorage.setItem( + "last_checked_has_pending_review_dms", + Date.now().toString() + ); + } +} + +export async function loader({ + params, + request, +}: LoaderFunctionArgs): Promise { + const importId = params.import_id?.trim(); + if (!importId) { + throw new Error("Import ID is required."); + } + + const searchparams = new URL(request.url).searchParams; + let isDMS = Boolean(searchparams.get("dms")?.trim()); + + return { + importId, + isDMS, + }; +} diff --git a/client/src/pages/importer/importer_tutorial.tsx b/client/src/pages/importer/importer_tutorial.tsx new file mode 100644 index 0000000..d3c5384 --- /dev/null +++ b/client/src/pages/importer/importer_tutorial.tsx @@ -0,0 +1,154 @@ +import { ARTISTS_OR_CREATORS, SITE_NAME } from "#env/env-vars"; +import { PageSkeleton } from "#components/pages"; + +export function ImporterTutorialPage() { + return ( + +

    + Patreon, Fanbox, SubscribeStar, Gumroad, DLsite, Fantia, Boosty, Afdian +

    +

    + {SITE_NAME} needs your session key in order to access posts from the{" "} + {ARTISTS_OR_CREATORS.toLowerCase()} you are subscribed to. +

    +

    Below are the respective cookies for the supported paysites.

    + +
      +
    • + For Patreon, your session key is under{" "} + session_id. +
    • +
    • + For Fanbox, your session key is under{" "} + FANBOXSESSID. +
    • +
    • + For Gumroad, your session key is under{" "} + _gumroad_app_session. +
    • +
    • + For SubscribeStar, your session key is under{" "} + _personalization_id. +
    • +
    • + For DLsite, your session key is under{" "} + __DLsite_SID. +
    • +
    • + For Fantia, your session key is under{" "} + _session_id. +
    • +
    • + For Boosty, your session key is under{" "} + auth. +
    • +
    • + For Afdian, your session key is under{" "} + auth_token. +
    • +
    + +

    + After going to the paysite you want to import and signing in, ( + Patreon/ + Fanbox/ + Gumroad/ + SubscribeStar/ + DLsite English/ + DLsite Japan/ + Fantia/ + Boosty/ + Afdian) you need to find where cookies + are located in your browser. +

    + +

    Chrome

    +
      +
    • + Press F12 to open Developer tools. If it didn't work for you try + Ctrl+Shift+I or right click inspect element. +
    • +
    • + In the menu at the top, navigate to "Application" tab, if this isn't + visible at a first glance simply press {">"} + {">"} for more tabs. +
    • + Select Application in Developer tools. +
    • In the "Application" tab, go to "Cookies".
    • +
    • Within the "Cookies" dropdown, select "patreon.com".
    • +
    • + Now in list of cookies find session_id and select it, copy the + contents and that will be the value you will use. +
    • + Copy cookie in the correct menu +
    • + Paste the content of the cookie you copied and submit in the{" "} + {SITE_NAME} import page +
    • +
    + +

    Safari

    +
      +
    • + Ensure "Show Develop Menu" is enabled in Preferences ( + ⌘,) +
    • +
    • + Open Web Inspector (⌘⌥I) +
    • +
    • Go to Storage > Cookies
    • +
    • + Right-click the cookie for your service and click "Copy" +
    • +
    +

    Firefox

    +
      +
    • Open DevTools by pressing F12 and open the Storage tab
    • +
    • Go to Cookies > [site]
    • +
    • Go to Storage > Cookies
    • +
    • + Right-click the cookie for your service and click "Copy" +
    • +
    +

    + For other browsers, please consult browser documentation on how to + access stored cookies. +

    +

    Discord

    +

    Getting your token

    +
      +
    • Open Discord in browser of your choice
    • +
    • Open DevTools (F12, Safari see above)
    • +
    • Go to Console Tab
    • +
    • + Paste and execute the following snippet:{" "} + {`(webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken()`} +
    • +
    • + A "slightly.long.string" + will be returned at the bottom of the console. Copy the contents + between "". This is your self token. +
    • +
    +

    + The above should work with most browsers and the official Discord App, + although you open the DevTools via the following combination in the App{" "} + Ctrl + Shift + I +

    +

    + Instructions on how to get the channel IDs can be found + + here. + +

    +
    + ); +} diff --git a/client/src/pages/importer/importer_tutorial_fanbox.tsx b/client/src/pages/importer/importer_tutorial_fanbox.tsx new file mode 100644 index 0000000..1dbbb4e --- /dev/null +++ b/client/src/pages/importer/importer_tutorial_fanbox.tsx @@ -0,0 +1,79 @@ +import { ARTISTS_OR_CREATORS, SITE_NAME } from "#env/env-vars"; +import { PageSkeleton } from "#components/pages"; + +export function ImporterTutorialFanboxPage() { + return ( + +

    + {SITE_NAME} needs your session key in order to access posts from the{" "} + {ARTISTS_OR_CREATORS.toLowerCase()} you are subscribed to. +

    + +

    + After going to the paysite you want to import and signing in, ( + Fanbox) you need to find where + cookies are located in your browser, follow the instructions bellow. +

    + +

    Chrome

    +
      +
    • + Press F12 to open Developer tools. If it didn't work for you try + Ctrl+Shift+I or right click inspect element. +
    • +
    • + In the menu at the top, navigate to "Application" tab, if this isn't + visible at a first glance simply press {">"} + {">"} for more tabs. +
    • + Select Application in Developer tools. +
    • In the "Application" tab, go to "Cookies".
    • +
    • Within the "Cookies" dropdown, select "fanbox.cc".
    • +
    • + Now in list of cookies find FANBOXSESSID and select it, copy the + contents and that will be the value you will use. +
    • + Copy cookie in the correct menu +
    • + Paste the content of the cookie you copied and submit in the{" "} + {SITE_NAME} import page +
    • +
    + +

    Safari

    +
      +
    • + Ensure "Show Develop Menu" is enabled in Preferences ( + ⌘,) +
    • +
    • + Open Web Inspector (⌘⌥I) +
    • +
    • Go to Storage > Cookies
    • +
    • + Right-click the cookie for your service and click "Copy" +
    • +
    + +

    Firefox

    +
      +
    • Open DevTools by pressing F12 and open the Storage tab
    • +
    • Go to Cookies > [site]
    • +
    • Go to Storage > Cookies
    • +
    • + Right-click the cookie for your service and click "Copy" +
    • +
    +

    + For other browsers, please consult browser documentation on how to + access stored cookies. +

    +
    + ); +} diff --git a/client/src/pages/importer/index.ts b/client/src/pages/importer/index.ts new file mode 100644 index 0000000..0fa2339 --- /dev/null +++ b/client/src/pages/importer/index.ts @@ -0,0 +1 @@ +export { ImporterStatusPage } from "./importer_status"; diff --git a/client/src/pages/importer_list.html b/client/src/pages/importer_list.html deleted file mode 100644 index d934496..0000000 --- a/client/src/pages/importer_list.html +++ /dev/null @@ -1,269 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/links.html' import email_link %} -{% from 'components/tooltip.html' import register_message %} - -{% block title %} - Import paywall posts/comments/DMs to {{ g.site_name }}. -{% endblock title %} - -{% block content %} -
    -

    Import from paysite

    - {% include "components/importer_states.html" %} -
    -
    - - -
    - -
    - - - - - Learn how to get your session key. - - - - - -
    -
    -
    - - Your user ID. Can be found in Cookies -> auth_id. -
    -
    - - - - BC token. Can be found in Local Storage -> bcTokenSha, or the headers of an XHR request -> x-bc.
    - Paste this on the console localStorage.bcTokenSha -
    - -
    - -
    - - - - This needs to be set to the User-Agent - of the last device that logged into your OnlyFans account; leave it as the default value if you are on it - right now. -
    - -
    - -
    - - - - comma separated, no spaces - -
    - - - - - - - - - -
    - -
    - -
    -

    Important information

    -

    - Your session key is used to scrape paid posts from your feed. After downloading missing posts, the key is - immediately discarded and never stored without permission. -

    - {% if "fantia" in g.paysite_list %} -

    Fantia

    -
      -
    • At least one paid content must be unlocked for the post to be imported. Free posts cannot be archived at - this time.
    • -
    • In order to download post contents accurately, the importer will automatically enable adult-viewing mode for - duration of the import if you have it turned off. Do not change back to general-viewing during imports. -
    • -
    - {% endif %} -

    Auto-import

    -

    - The auto-import feature allows users to give {{ g.site_name }} permission to automatically detect and retrieve new - posts and creators by storing session keys long-term, without need for manual key submission. All keys are - encrypted using a strong RSA 4096 key. When the administrators start a new autoimport round, a computer outside of - {{ g.site_name }}'s infrastucture sends the private key to the backend, allowing it to decrypt all working keys - and start import tasks. Even if {{ g.site_name }}'s private database were to somehow be compromised, your tokens - would remain anonymous and secure.
    - If you are logged into {{ g.site_name }}, any key you submit with autoimport enabled can be managed under the Keys - section of your [Account] tab in the header. There, you will be able to view import logs or revoke access. - Please note that anonymously-submitted keys cannot be managed. -

    -
    -{% endblock content %} - -{% block components %} - {{ register_message("DM import") }} -{% endblock components %} diff --git a/client/src/pages/importer_list.js b/client/src/pages/importer_list.js deleted file mode 100644 index 1d7f68f..0000000 --- a/client/src/pages/importer_list.js +++ /dev/null @@ -1,174 +0,0 @@ -import { registerMessage, showTooltip } from "@wp/components"; - -/** - * @param {HTMLElement} section - */ -export function importerPage(section) { - const isLoggedIn = localStorage.getItem("logged_in") === "yes"; - /** - * @type {HTMLFormElement} - */ - const form = document.forms["import-list"]; - const currentService = form.querySelector("#service").value; - - /** - * @type {Record} - */ - const noteLookup = { - fansly: form.querySelector(".fansly__notes"), - onlyfans: form.querySelector(".onlyfans__notes"), - fanbox: form.querySelector(".fanbox__notes"), - candfans: form.querySelector(".candfans__notes"), - other: form.querySelector(".other__notes"), - }; - switchKeyNotesToggle(currentService, noteLookup); - form.addEventListener( - "change", - processChangeForService((service) => switchKeyNotesToggle(service, noteLookup)), - ); - - /** - * @type {Record} - */ - const sectionLookup = { - discord: form.querySelector("#discord-section"), - onlyfans: form.querySelector("#onlyfans-section"), - }; - displayOnlyActiveInputSectionsFieldsRequired(currentService, sectionLookup); - form.addEventListener( - "change", - processChangeForService((service) => displayOnlyActiveInputSectionsFieldsRequired(service, sectionLookup)), - ); - - /** - * @type {Record} - */ - const DMLookup = { - patreon: true, - fansly: true, - }; - ActivateDMSection(currentService, DMLookup, form.querySelector("#dm-consent")); - form.addEventListener( - "change", - processChangeForService((service) => ActivateDMSection(service, DMLookup, form.querySelector("#dm-consent"))), - ); - ActivateFanboxTestConsentSection(currentService, form.querySelector("#fanbox-test-consent")); - form.addEventListener( - "change", - processChangeForService((service) => ActivateFanboxTestConsentSection(service, form.querySelector("#fanbox-test-consent"))), - ); - - form.addEventListener("submit", handleSubmit(isLoggedIn)); - document.getElementById("user-agent").value = navigator.userAgent; -} - -/** - * @param {function} procesingFunction - * @returns {(event: Event) => void} - */ -function processChangeForService(procesingFunction) { - return (event) => { - if (event.target.id === "service") { - event.stopPropagation(); - /** - * @type {String} - */ - const selectValue = event.target.value; - procesingFunction(selectValue); - } - }; -} - -/** - * @param {String} selectService - * @param {Record} sectionLookup - * @returns {void} - */ -function displayOnlyActiveInputSectionsFieldsRequired(selectService, sectionLookup) { - let activeSection = sectionLookup[selectService]; - Object.values(sectionLookup).forEach((section) => section.classList.add("form__section--hidden")); - Object.values(sectionLookup).forEach((section) => - section.querySelectorAll("input").forEach((input) => (input.required = false)), - ); - if (activeSection) { - activeSection.classList.remove("form__section--hidden"); - activeSection.querySelectorAll("input").forEach((input) => (input.required = true)); - } -} - -/** - * @param {String} selectService - * @param {Record} noteLookup - * @returns {void} - */ -function switchKeyNotesToggle(selectService, noteLookup) { - Object.values(noteLookup).forEach((notes) => (notes.hidden = true)); - if (noteLookup[selectService]) { - noteLookup[selectService].hidden = false; - } else { - noteLookup["other"].hidden = false; - } -} - -/** - * @param {String} selectService - * @param {Record} DMLookup - * @param {HTMLElement} dmSection - * @returns {void} - */ -function ActivateDMSection(selectService, DMLookup, dmSection) { - let isActive = DMLookup[selectService]; - if (isActive) { - dmSection.classList.remove("form__section--hidden"); - dmSection.querySelector("input").checked = true; - } else { - dmSection.classList.add("form__section--hidden"); - dmSection.querySelector("input").checked = false; - } -} - -/** - * @param {String} selectService - * @param {HTMLElement} fanboxTestConsentSection - * @returns {void} - */ -function ActivateFanboxTestConsentSection(selectService, fanboxTestConsentSection) { - let isActive = selectService === "fanbox"; - if (isActive) { - fanboxTestConsentSection.classList.remove("form__section--hidden"); - fanboxTestConsentSection.querySelector("input").checked = false; - } else { - fanboxTestConsentSection.classList.add("form__section--hidden"); - fanboxTestConsentSection.querySelector("input").checked = false; - } -} - -/** - * @param {boolean} isLoggedIn - * @returns {(event: Event) => void} - */ -function handleSubmit(isLoggedIn) { - return (event) => { - /** - * @type {HTMLFormElement} - */ - const form = event.target; - /** - * @type {HTMLInputElement} - */ - const dmConsent = form.elements["save-dms"]; - const fanboxTestConsent = form.elements["fanbox-test-consent"]; - const service = form.elements["service"]; - - if (service.value === "patreon" && dmConsent.checked && !isLoggedIn) { - event.preventDefault(); - showTooltip(dmConsent, registerMessage(null)); - } - console.log(!fanboxTestConsent.checked); - console.log(service.value); - if (service.value === "fanbox" && !fanboxTestConsent.checked) { - event.preventDefault(); - showTooltip(fanboxTestConsent, registerMessage("You need to agree.")); - } - }; -} diff --git a/client/src/pages/importer_ok.html b/client/src/pages/importer_ok.html deleted file mode 100644 index 9453195..0000000 --- a/client/src/pages/importer_ok.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} -
    - {% include "components/importer_states.html" %} -

    Success

    -

    - Your session key has been submitted to the server. Posts will be added soon. Thank you for contributing!
    - If you're having trouble with the importer, contact admin. -

    -
    -{% endblock %} \ No newline at end of file diff --git a/client/src/pages/importer_status.html b/client/src/pages/importer_status.html deleted file mode 100644 index 91adb87..0000000 --- a/client/src/pages/importer_status.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/loading_icon.html' import loading_icon %} -{% from 'components/buttons.html' import button %} - -{% block title %} - Import {{ props.import_id }} -{% endblock title %} - -{% block meta %} - -{% endblock meta %} - -{% block content %} -
    - {% include "components/importer_states.html" %} -
    -

    Importer logs for {{ props.import_id }}

    -
    - {% if props.is_dms %} -
    - Hey! -

    - You gave the importer permission to access your messages. To protect your anonymity, you must manually approve each one. Wait until after the importer says Done importing DMs, then go here to choose which ones you wish to import. -

    -
    - {% endif %} -
    -
    -
    -
    - Status: - Fetching -
    -
    - Total: - -
    -
    -
    - {{ button('Reverse order', class_name='import__reverse') }} -
    -
    - -

    - {{ loading_icon() }} Wait until logs load... -

    -
      -
    -
    -
    -{% endblock content %} - -{% block components %} - {{ log_item() }} -{% endblock components %} - -{% macro log_item() %} -
  • -{% endmacro %} diff --git a/client/src/pages/importer_status.js b/client/src/pages/importer_status.js deleted file mode 100644 index 8fa917e..0000000 --- a/client/src/pages/importer_status.js +++ /dev/null @@ -1,164 +0,0 @@ -import { kemonoAPI } from "@wp/api"; -import { createComponent } from "@wp/js/component-factory"; -import { waitAsync } from "@wp/utils"; -import { initPendingReviewDms } from "@wp/js/pending-review-dms"; - -/** - * @typedef Stats - * @property {string} importID - * @property {HTMLSpanElement} status - * @property {HTMLSpanElement} count - * @property {number} cooldown - * @property {number} retries - */ - -/** - * TODOs: - * - service heuristics - * - error handling - * @param {HTMLElement} section - */ -export async function importerStatusPage(section) { - /** - * @type {HTMLDivElement} - */ - const importInfo = section.querySelector(".import__info"); - /** - * @type {[HTMLDivElement, HTMLDivElement]} - */ - const [importStats, buttonPanel] = importInfo.children; - const [status, count] = importStats.children; - /** - * @type {Stats} - */ - const stats = { - importID: document.head.querySelector("meta[name='import_id']").content, - status: status.children[1], - count: count.children[1], - cooldown: 5000, - retries: 0, - }; - /** - * @type {HTMLParagraphElement} - */ - const loadingPlaceholder = section.querySelector(".loading-placeholder"); - /** - * @type {HTMLOListElement} - */ - const logList = section.querySelector(".log-list"); - - initButtons(buttonPanel, logList); - const logs = await kemonoAPI.api.logs(stats.importID); - - if (logs) { - populateLogList(logs, logList, loadingPlaceholder); - stats.status.textContent = "In Progress"; - stats.count.textContent = logs.length; - count.classList.remove("import__count--invisible"); - - initPendingReviewDms(true).then(() => {}) - await waitAsync(stats.cooldown); - await updateLogList(logs, logList, stats); - } else { - loadingPlaceholder.classList.add("loading-placeholder--complete"); - alert("Failed to fetch the logs, try reloading the page."); - } -} - -/** - * @param {HTMLDivElement} buttonPanel - * @param {HTMLOListElement} logList - */ -function initButtons(buttonPanel, logList) { - /** - * @type {HTMLButtonElement[]} - */ - const [reverseButton] = buttonPanel.children; - - reverseButton.addEventListener("click", reverseList(logList)); -} - -/** - * @param {HTMLOListElement} logList - * @returns {(event: MouseEvent) => void} - */ -function reverseList(logList) { - return (event) => { - logList.classList.toggle("log-list--reversed"); - }; -} - -/** - * @param {string[]} logs - * @param {HTMLOListElement} logList - * @param {HTMLParagraphElement} loadingItem - */ -function populateLogList(logs, logList, loadingItem) { - const fragment = document.createDocumentFragment(); - - logs.forEach((log) => { - const logItem = LogItem(log); - fragment.appendChild(logItem); - }); - - loadingItem.classList.add("loading-placeholder--complete"); - logList.appendChild(fragment); - logList.classList.add("log-list--loaded"); -} - -/** - * TODO: finishing condition. - * @param {string[]} logs - * @param {HTMLOListElement} logList - * @param {Stats} stats - */ -async function updateLogList(logs, logList, stats) { - let newLogs = await kemonoAPI.api.logs(stats.importID); - - if (!newLogs) { - if (stats.retries === 5) { - stats.status.textContent = "Fatal Error"; - return; - } - - await waitAsync(stats.cooldown); - stats.retries++; - return await updateLogList(logs, logList, stats); - } - - const diff = newLogs.length - logs.length; - - if (diff === 0) { - stats.cooldown = stats.cooldown * 2; - await waitAsync(stats.cooldown); - initPendingReviewDms(false, 1).then(() => {}) - return await updateLogList(logs, logList, stats); - } - - const diffLogs = newLogs.slice(newLogs.length - diff); - const fragment = document.createDocumentFragment(); - diffLogs.forEach((log) => { - const logItem = LogItem(log); - fragment.appendChild(logItem); - }); - logs.push(...diffLogs); - logList.appendChild(fragment); - stats.count.textContent = logs.length; - - await waitAsync(stats.cooldown); - return await updateLogList(logs, logList, stats); -} - -/** - * @param {string} message - */ -function LogItem(message) { - /** - * @type {HTMLLIElement} - */ - const item = createComponent("log-list__item"); - - item.textContent = message; - - return item; -} diff --git a/client/src/pages/importer_tutorial.html b/client/src/pages/importer_tutorial.html deleted file mode 100644 index f3873ee..0000000 --- a/client/src/pages/importer_tutorial.html +++ /dev/null @@ -1,76 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} -{% include "components/importer_states.html" %} -
    -

    How to get your session key

    -

    Patreon, Fanbox, SubscribeStar, Gumroad, Fantia, Boosty, Afdian

    -

    {{ g.site_name }} needs your session key in order to access posts from the {{ g.artists_or_creators|lower }} you are subscribed to.

    -

    Below are the respective cookies for the supported paysites.

    -
      -
    • For Patreon, your session key is under session_id.
    • -
    • For Fanbox, your session key is under FANBOXSESSID.
    • -
    • For Gumroad, your session key is under _gumroad_app_session.
    • -
    • For SubscribeStar, your session key is under _personalization_id.
    • -
    • For Fantia, your session key is under _session_id.
    • -
    • For Boosty, your session key is under auth.
    • -
    • For Afdian, your session key is under auth_token.
    • -
    -

    After going to the paysite you want to import and signing in, ( - Patreon - / - Fanbox - / - Gumroad - / - SubscribeStar - / - Fantia - / - Boosty - / - Afdian - ) you need to find where cookies are located in your browser.

    -

    Chrome

    -
      -
    • Press F12 to open Developer tools. If it didn't work for you try Ctrl+Shift+I or right click inspect element.
    • -
    • In the menu at the top, navigate to "Application" tab, if this isn't visible at a first glance simply press >> for more tabs.
    • - Select Application in Developer tools. -
    • In the "Application" tab, go to "Cookies".
    • -
    • Within the "Cookies" dropdown, select "patreon.com".
    • -
    • Now in list of cookies find session_id and select it, copy the contents and that will be the value you will use.
    • - Copy cookie in the correct menu -
    • Paste the content of the cookie you copied and submit in the {{ g.site_name }} import page
    • -
    -

    Safari

    -
      -
    • Ensure "Show Develop Menu" is enabled in Preferences (⌘,)
    • -
    • Open Web Inspector (⌘⌥I)
    • -
    • Go to Storage > Cookies
    • -
    • Right-click the cookie for your service and click "Copy"
    • -
    -

    Firefox

    -
      -
    • Open DevTools by pressing F12 and open the Storage tab
    • -
    • Go to Cookies > [site]
    • -
    • Go to Storage > Cookies
    • -
    • Right-click the cookie for your service and click "Copy"
    • -
    -

    For other browsers, please consult browser documentation on how to access stored cookies.

    -

    Discord

    -

    Getting your token

    -
      -
    • Open Discord in browser of your choice
    • -
    • Open DevTools (F12, Safari see above)
    • -
    • Go to Console Tab
    • -
    • Paste and execute the following snippet: (webpackChunkdiscord_app.push([[''],{},e=>{m=[];for(let c in e.c)m.push(e.c[c])}]),m).find(m=>m?.exports?.default?.getToken!==void 0).exports.default.getToken()
    • -
    • A "slightly.long.string" will be returned at the bottom of the console. Copy the contents between "". This is your self token.
    • -
    -

    The above should work with most browsers and the official Discord App, although you open the DevTools via the following combination in the App Ctrl + Shift + I

    -

    - Instructions on how to get the channel IDs can be found - - here. - -

    -
    -{% endblock %} \ No newline at end of file diff --git a/client/src/pages/importer_tutorial_fanbox.html b/client/src/pages/importer_tutorial_fanbox.html deleted file mode 100644 index 817756c..0000000 --- a/client/src/pages/importer_tutorial_fanbox.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} -{% include "components/importer_states.html" %} -
    -

    How to get your Fanbox session key

    -

    {{ g.site_name }} needs your session key in order to access posts from the {{ g.artists_or_creators|lower }} you are subscribed to.

    - -

    After going to the paysite you want to import and signing in, ( - Fanbox - ) you need to find where cookies are located in your browser, follow the instructions bellow.

    -

    Chrome

    -
      -
    • Press F12 to open Developer tools. If it didn't work for you try Ctrl+Shift+I or right click inspect element.
    • -
    • In the menu at the top, navigate to "Application" tab, if this isn't visible at a first glance simply press >> for more tabs.
    • - Select Application in Developer tools. -
    • In the "Application" tab, go to "Cookies".
    • -
    • Within the "Cookies" dropdown, select "fanbox.cc".
    • -
    • Now in list of cookies find FANBOXSESSID and select it, copy the contents and that will be the value you will use.
    • - Copy cookie in the correct menu -
    • Paste the content of the cookie you copied and submit in the {{ g.site_name }} import page
    • -
    -

    Safari

    -
      -
    • Ensure "Show Develop Menu" is enabled in Preferences (⌘,)
    • -
    • Open Web Inspector (⌘⌥I)
    • -
    • Go to Storage > Cookies
    • -
    • Right-click the cookie for your service and click "Copy"
    • -
    -

    Firefox

    -
      -
    • Open DevTools by pressing F12 and open the Storage tab
    • -
    • Go to Cookies > [site]
    • -
    • Go to Storage > Cookies
    • -
    • Right-click the cookie for your service and click "Copy"
    • -
    -

    For other browsers, please consult browser documentation on how to access stored cookies.

    -
    -{% endblock %} \ No newline at end of file diff --git a/client/src/pages/matrix.tsx b/client/src/pages/matrix.tsx new file mode 100644 index 0000000..52f16e1 --- /dev/null +++ b/client/src/pages/matrix.tsx @@ -0,0 +1,42 @@ +import { PageSkeleton } from "#components/pages"; + +export function MatrixPage() { + const title = "Matrix ecosystem"; + const heading = "Welcome to the Matrix ecosystem"; + + return ( + + + + ); +} diff --git a/client/src/pages/post-revision.module.scss b/client/src/pages/post-revision.module.scss new file mode 100644 index 0000000..88af0d3 --- /dev/null +++ b/client/src/pages/post-revision.module.scss @@ -0,0 +1,5 @@ +@use "../css/config/variables/sass.scss" as *; + +.article { + margin: 0 auto +} diff --git a/client/src/pages/post-revision.tsx b/client/src/pages/post-revision.tsx new file mode 100644 index 0000000..d8ae7f8 --- /dev/null +++ b/client/src/pages/post-revision.tsx @@ -0,0 +1,208 @@ +import { useEffect } from "react"; +import { LoaderFunctionArgs, useLoaderData, Link } from "react-router"; +import { Helmet } from "@dr.pogodin/react-helmet"; +import { + ICONS_PREPEND, + IS_ARCHIVER_ENABLED, + KEMONO_SITE, + SITE_NAME, +} from "#env/env-vars"; +import { fetchArtistProfile } from "#api/profiles"; +import { fetchPostRevision, fetchPostComments } from "#api/posts"; +import { PageSkeleton } from "#components/pages"; +import { SliderAd } from "#components/advs"; +import { paysites, validatePaysite } from "#entities/paysites"; +import { + IComment, + IPost, + IPostAttachment, + IPostPreview, + IPostVideo, + PostOverview, +} from "#entities/posts"; +import { IArtistDetails } from "#entities/profiles"; + +import * as styles from "./post-revision.module.scss"; + +interface IProps { + post: IPost; + profile: IArtistDetails; + revisions: Awaited< + ReturnType + >["props"]["revisions"]; + flagged: string | null; + videos?: IPostVideo[]; + attachments?: IPostAttachment[]; + previews?: IPostPreview[]; + archives_enabled?: boolean; + comments: Promise; +} + +export function PostRevisionPage() { + const { + post, + profile, + revisions, + flagged, + videos, + attachments, + previews, + archives_enabled, + comments, + } = useLoaderData() as IProps; + const paysite = paysites[post.service]; + const postTitle = post.title ?? "Untitled"; + const artistName = profile.name ?? post.user; + + const title = !profile + ? `Post "${postTitle}"` + : `Post "${postTitle}" by "${artistName}" from ${paysite.title}`; + + function handlePrevNextLinks(event: KeyboardEvent) { + switch (event.key) { + case "ArrowLeft": + document + .querySelector(".post__nav-link.prev") + ?.click(); + break; + case "ArrowRight": + document + .querySelector(".post__nav-link.next") + ?.click(); + break; + } + } + + useEffect(() => { + document.addEventListener("keydown", handlePrevNextLinks); + + return () => { + document.removeEventListener("keydown", handlePrevNextLinks); + }; + }, []); + + return ( + + + + + + {!post.published ? undefined : ( + + )} + + {/* */} + + + + + + {/* */} + + + + + + + + + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Profile ID is required."); + } + } + + const postID = params.post_id?.trim(); + { + if (!postID) { + throw new Error("Post ID is required."); + } + } + + const revisionID = params.revision_id?.trim(); + { + if (!revisionID) { + throw new Error("Revision ID is required."); + } + } + + const profile = await fetchArtistProfile(service, profileID); + const { post, result_attachments, result_previews, videos, props } = + await fetchPostRevision(service, profileID, postID, revisionID); + const { flagged, revisions } = props; + + const comments = fetchPostComments(service, profileID, postID); + + const pageProps = { + profile, + post, + revisions, + attachments: result_attachments, + previews: result_previews, + videos, + archives_enabled: IS_ARCHIVER_ENABLED, + flagged, + comments, + } satisfies IProps; + + return pageProps; +} diff --git a/client/src/pages/post.html b/client/src/pages/post.html deleted file mode 100644 index 4918c42..0000000 --- a/client/src/pages/post.html +++ /dev/null @@ -1,420 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/timestamp.html' import timestamp %} -{% from 'components/links.html' import kemono_link, local_link %} -{% from 'components/fancy_image.html' import fancy_image, background_image %} -{% from 'components/image_link.html' import image_link %} -{% from 'components/ads.html' import middle_ad, slider_ad %} - -{% set paysite = g.paysites[props.service] %} -{% set post_title = post.title if post.title else 'Untitled' %} -{% set artist_name = props.artist.name if props.artist.name else props.user %} -{% set page_title = "\"" ~ post_title ~ "\" by " ~ props.artist.name ~ " from " ~ paysite.title ~ " | " ~ g.site_name if props.artist else post_title ~ " | " ~ g.site_name %} -{% set user_link = g.freesites.kemono.user.profile(post.service, post.user) %} -{% set user_icon = g.freesites.kemono.user.icon(post.service, post.user) %} -{% set user_banner = g.freesites.kemono.user.banner(post.service, post.user) %} -{% set post_link = g.freesites.kemono.post.link(post.service, post.user, post.id) %} - -{% block title %} - - {{ page_title }} - -{% endblock title %} - -{% block meta %} - - - - {% if post.published %} - - {% endif %} - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -{{ slider_ad() }} -
    - {% if post %} - - -
    -
    - {{ background_image(user_banner) }} - {{ image_link( - url=user_link, - src=user_icon, - is_lazy=false, - class_name='post__user-profile' - ) }} - -
    - -
    - -
    - {{ middle_ad() }} - {{ post_view(post, result_attachments, result_previews) }} -
    - -
    -

    Comments

    - {# TODO: comment filters #} -
    - {% for comment in comments %} - {% set is_user = comment.commenter == post.user %} -
    -
    - {% if is_user %} - - {% call kemono_link(comment.id, class_name="comment__icon") %} - {{ fancy_image( g.icons_prepend ~ '/icons/' ~ post.service ~ '/' ~ post.user) }} - {% endcall %} - - {% call local_link(comment.id, class_name="comment__name") %} - {{ props.artist.name if props.artist else g.artists_or_creators[:-1] }} - {% endcall %} - - {% else %} - {{ local_link(comment.id, comment.commenter_name or 'Anonymous' , "comment__name") }} - {% endif %} - - {% if comment.revisions %} - (edited) -
    -
    -
    -
    -
    -

    Comment edits

    - - -
    - -
    - {% for revision in comment.revisions + [comment] %} -
    - {{(revision.published or revision.added)|simple_datetime}} - {{revision.content}} -
    - {% endfor %} -
    -
    -
    -
    -
    - {% endif %} -
    -
    - {% if comment.parent_id %} - - {% endif %} -

    - {{ comment.content }} -

    -
    -
    - {{ timestamp(comment.published) }} -
    -
    - {% else %} -

    No comments found for this post.

    - {% endfor %} -
    - -
    - - {% else %} -

    Nobody here but us chickens!

    - {% endif %} -
    -{% endblock content %} - -{% block components %} - - - - Flagged - - - Revisions -{% endblock components %} - -{% macro post_view(post, attachments, previews) %} - {% if post.service == 'dlsite' and post.attachments|length > 1 %} -

    - This DLsite post was received as a split set of multiple files due to file size. Download all the files, then open the .exe file to compile them into a single one. -

    - {% endif %} - - {% if videos %} -

    Videos

    - -
      - {% for video in videos %} -
    • - {{ video.name }} - {% if video.caption %} - {{video.caption}} - {% endif %} - -
    • - {% endfor %} -
    - {% endif %} - - {% if attachments %} -

    Downloads

    -
      - {% for attachment in attachments %} -
    • - - Download {{ attachment.name }} - - {% if archives_enabled and ( attachment.extension in [".zip", ".rar", ".7z"] or attachment.name_extension in [".zip", ".rar", ".7z"] )%} - (browse ») - {% endif %} -
    • - {% endfor %} -
    - {% endif %} - - {% if post.incomplete_rewards %} -
    -
    {{ post.incomplete_rewards|safe }}
    -
    - {% endif %} - - {% if post.poll %} -

    Poll

    - -
    -
    -

    {{post.poll.title}}

    - {% if post.poll.description %} -
    {{post.poll.description}}
    - {% endif %} -
    -
      - {% for choice in post.poll.choices %} - {% set pct = choice.votes / (post.poll.total_votes or 1) * 100 %} -
    • - {{choice.text}} - {{choice.votes}} - -
    • - {% endfor %} -
    -
    -
      -
    • {{post.poll.created_at|simple_date}}
    • - {% if post.poll.closes_at %} -
    • —{{post.poll.closes_at|simple_date}}
    • - {% endif %} - {% if post.poll.allow_multiple %} -
    • multiple choice
    • - {% endif %} -
    • {{post.poll.total_votes}} votes
    • -
    -
    - {{post.poll}} - {% endif %} - - {% if post.content %} -

    Content

    -
    - {% if props.service == "subscribestar" -%} -
    - {%- endif %} - {% if props.service == 'fantia' or props.service == 'onlyfans' or props.service == 'fansly' or props.service == 'candfans' -%} -
    {{ post.content|safe }}
    - {% else -%} - {{ post.content|safe }} - {%- endif %} -
    - {% endif %} - - - {% if previews %} -

    Files

    -
    - {% for preview in previews %} - {% if preview.type == 'thumbnail' %} -
    -
    - - {# TODO: move backup image logic to the script #} - - - {% if preview.caption %} -
    {{preview.caption}}
    - {% endif %} -
    -
    - {% elif preview.type == 'embed' %} - -
    -

    - {{ preview.subject if preview.subject else '(No title)' }} -

    - {% if preview.description %} -

    - {{ preview.description }} -

    - {% endif %} -
    -
    - {% endif %} - {% endfor %} -
    - {% endif %} - -{% endmacro %} diff --git a/client/src/pages/post.js b/client/src/pages/post.js deleted file mode 100644 index ca08966..0000000 --- a/client/src/pages/post.js +++ /dev/null @@ -1,375 +0,0 @@ -import { kemonoAPI } from "@wp/api"; -import { addFavouritePost, findFavouritePost, removeFavouritePost } from "@wp/js/favorites"; -import { LoadingIcon, registerMessage, showTooltip } from "@wp/components"; -import { createComponent } from "@wp/js/component-factory"; -import { isLoggedIn } from "@wp/js/account"; -import MicroModal from "micromodal"; -import { diffChars } from "diff"; - -import "fluid-player/src/css/fluidplayer.css"; -import fluidPlayer from "fluid-player"; - -const meta = { - service: null, - user: null, - postID: null, -}; - -/** - * @param {HTMLElement} section - */ -export async function postPage(section) { - /** - * @type {HTMLElement} - */ - const buttonPanel = section.querySelector(".post__actions"); - - meta.service = document.head.querySelector("[name='service']").content; - meta.user = document.head.querySelector("[name='user']").content; - meta.postID = document.head.querySelector("[name='id']").content; - const postBody = section.querySelector(".post__body"); - - section.addEventListener("click", Expander); - - cleanupBody(postBody); - await initButtons(buttonPanel); - addRevisionHandler(); - - document.addEventListener("DOMContentLoaded", (d, ev) => {addShowTagsButton()}); - window.addEventListener("resize", (d, ev) => {addShowTagsButton()}); - - MicroModal.init(); - // diffComments(); - - Array.from(document.getElementsByTagName("video")).forEach((_, i) => { - fluidPlayer(`kemono-player${i}`, { - layoutControls: { - fillToContainer: false, - preload: "none", - }, - vastOptions: { - adList: window.videoAds, - adTextPosition: "top left", - maxAllowedVastTagRedirects: 2, - vastAdvanced: { - vastLoadedCallback: function () {}, - noVastVideoCallback: function () {}, - vastVideoSkippedCallback: function () {}, - vastVideoEndedCallback: function () {}, - }, - }, - }); - }); -} - -/** - * Apply some fixes to the content of the post. - * @param {HTMLElement} postBody - */ -function cleanupBody(postBody) { - const postContent = postBody.querySelector(".post__content"); - const isNoPostContent = !postContent || (!postContent.childElementCount && !postContent.childNodes.length); - - // content is empty - if (isNoPostContent) { - return; - } - - // pixiv post - if (meta.service === "fanbox") { - // its contents is a text node - if (!postContent.childElementCount && postContent.childNodes.length === 1) { - // wrap the text node into `
    `
    -      const [textNode] = Array.from(postContent.childNodes);
    -      const pre = document.createElement("pre");
    -      textNode.after(pre);
    -      pre.appendChild(textNode);
    -    }
    -
    -    // remove paragraphs with only `
    ` in them - const paragraphs = postContent.querySelectorAll("p"); - paragraphs.forEach((para) => { - if (para.childElementCount === 1 && para.firstElementChild.tagName === "BR") { - para.remove(); - } - }); - } - - Array.from(document.links).forEach((anchour) => { - // remove links to fanbox from the post - const hostname = anchour.hostname; - if (hostname.includes("downloads.fanbox.cc")) { - if (anchour.classList.contains("image-link")) { - anchour.remove(); - } else { - let el = document.createElement("span"); - el.textContent = anchour.textContent; - anchour.replaceWith(el); - } - } - else if (hostname.includes("fanbox.cc")){ - anchour.href = anchour.href.replace(/https?:\/\/(?:[a-zA-Z0-9-]*.)?fanbox\.cc\/(?:(?:manage\/)|(?:@[a-zA-Z\d]+\/)|)posts\/(\d+)/g, '/fanbox/post/$1'); - } - else if (hostname.includes("patreon.com")){ - anchour.href = anchour.href.replace( /https?:\/\/(?:[\w-]*.)?patreon\.com\/posts\/.*\b(\d+)\b(?:\?.*)?/g, '/patreon/post/$1'); - } - }); - - // Remove needless spaces and empty paragraphs. - /** - * @type {NodeListOf { - if (paragraph.nextElementSibling && paragraph.nextElementSibling.tagName === "BR") { - paragraph.nextElementSibling.remove(); - paragraph.remove(); - } else { - paragraph.remove(); - } - }); -} - -/** - * @param {HTMLElement} buttonPanel - */ -async function initButtons(buttonPanel) { - /** - * @type {HTMLButtonElement} - */ - const flagButton = buttonPanel.querySelector(".post__flag"); - /** - * @type {HTMLButtonElement} - */ - const favButton = createComponent("post__fav"); - const isFavorited = isLoggedIn && (await findFavouritePost(meta.service, meta.user, meta.postID)); - - if (isFavorited) { - const [icon, text] = favButton.children; - favButton.classList.add("post__fav--unfav"); - icon.textContent = "★"; - text.textContent = "Unfavorite"; - } - - if (!flagButton.classList.contains("post__flag--flagged")) { - flagButton.addEventListener("click", handleFlagging(meta.service, meta.user, meta.postID)); - } - - favButton.addEventListener("click", handleFavouriting(meta.service, meta.user, meta.postID)); - - buttonPanel.appendChild(favButton); - - document.addEventListener("keydown", (e) => { - switch (e.key) { - case "ArrowLeft": - document.querySelector(".post__nav-link.prev")?.click(); - break; - case "ArrowRight": - document.querySelector(".post__nav-link.next")?.click(); - break; - } - }); -} - -function addRevisionHandler() { - let selector = document.getElementById("post-revision-selection"); - if (selector) { - selector.addEventListener("change", (ev) => { - let revision = ev.target.selectedOptions[0].value; - if (revision) - location.pathname = `/${meta.service}/user/${meta.user}/post/${meta.postID}/revision/${revision}`; - else - location.pathname = `/${meta.service}/user/${meta.user}/post/${meta.postID}`; - }); - } -} - -/** - * @param {string} service - * @param {string} user - * @param {string} postID - * @returns {(event: MouseEvent) => Promise} - */ -function handleFlagging(service, user, postID) { - return async (event) => { - /** - * @type {HTMLButtonElement} - */ - const button = event.target; - const [icon, text] = button.children; - const loadingIcon = LoadingIcon(); - const isConfirmed = confirm( - "Are you sure you want to flag this post for reimport? Only do this if data in the post is broken/corrupted/incomplete.\nThis is not a deletion button.", - ); - - button.classList.add("post__flag--loading"); - button.disabled = true; - button.insertBefore(loadingIcon, text); - - try { - if (isConfirmed) { - const isFlagged = await kemonoAPI.posts.attemptFlag(service, user, postID); - - if (isFlagged) { - const parent = button.parentElement; - const newButton = createComponent("post__flag post__flag--flagged"); - - parent.insertBefore(newButton, button); - button.remove(); - } - } - } catch (error) { - console.error(error); - } finally { - loadingIcon.remove(); - button.disabled = false; - button.classList.remove("post__flag--loading"); - } - }; -} - -/** - * @param {string} service - * @param {string} user - * @param {string} postID - * @returns {(event: MouseEvent) => Promise} - */ -function handleFavouriting(service, user, postID) { - return async (event) => { - /** - * @type {HTMLButtonElement} - */ - const button = event.currentTarget; - const isLoggedIn = localStorage.getItem("logged_in") === "yes"; - - if (!isLoggedIn) { - showTooltip(button, registerMessage(null, "Favoriting")); - return; - } - - const [icon, text] = button.children; - const loadingIcon = LoadingIcon(); - - button.disabled = true; - button.classList.add("post__fav--loading"); - button.insertBefore(loadingIcon, text); - - try { - if (button.classList.contains("post__fav--unfav")) { - const isUnfavorited = await removeFavouritePost(service, user, postID); - - if (isUnfavorited) { - button.classList.remove("post__fav--unfav"); - icon.textContent = "☆"; - text.textContent = "Favorite"; - } - } else { - const isFavorited = await addFavouritePost(service, user, postID); - - if (isFavorited) { - button.classList.add("post__fav--unfav"); - icon.textContent = "★"; - text.textContent = "Unfavorite"; - } - } - } catch (error) { - console.error(error); - } finally { - loadingIcon.remove(); - button.disabled = false; - button.classList.remove("post__fav--loading"); - } - }; -} - -// expander.js -function Expand(c, t) { - if (!c.naturalWidth) { - return setTimeout(Expand, 10, c, t); - } - c.style.maxWidth = "100%"; - c.style.display = ""; - t.style.display = "none"; - t.style.opacity = ""; -} - -/** - * @param {MouseEvent} e - */ -function Expander(e) { - /** - * @type {HTMLElement} - */ - var t = e.target; - if (t.parentNode.classList.contains("fileThumb")) { - e.preventDefault(); - if (t.hasAttribute("data-src")) { - var c = document.createElement("img"); - c.setAttribute("src", t.parentNode.getAttribute("href")); - c.style.display = "none"; - t.parentNode.insertBefore(c, t.nextElementSibling); - t.style.opacity = "0.75"; - setTimeout(Expand, 10, c, t); - } else { - var a = t.parentNode; - a.firstChild.style.display = ""; - a.removeChild(t); - a.offsetTop < window.pageYOffset && a.scrollIntoView({ top: 0, behavior: "smooth" }); - } - } -} - -function addShowTagsButton() { - let div = document.querySelector("#post-tags > div"); - if (document.getElementById("show-tag-overflow-button")){ - document.getElementById("show-tag-overflow-button").remove(); - } - if (div && div.offsetWidth < div.scrollWidth) { - // tags overflow - let button = document.createElement("a"); - button.href = "javascript:void 0"; - button.id = "show-tag-overflow-button"; - button.textContent = "Show all »"; - button.onclick = (e) => { - if (div.classList.contains("show-overflow")) { - div.classList.remove("show-overflow"); - button.textContent = "Show all»"; - } else { - div.classList.add("show-overflow"); - button.textContent = "« Hide"; - } - } - div.parentElement.appendChild(button); - } -} - -function* pairwise(iterable) { - const iterator = iterable[Symbol.iterator](); - let a = iterator.next(); - if (a.done) return; - let b = iterator.next(); - while (!b.done) { - yield [a.value, b.value]; - a = b; - b = iterator.next(); - } -} - -function diffComments() { - let comments = Array.from(document.querySelectorAll(".comment-revisions-dialog .prose")); - let pairs = pairwise(comments); - for (let [old, new_] of pairs) { - let newSpan = document.createElement("span"); - newSpan.classList.add("prose"); - diffChars(old.textContent, new_.textContent) - .forEach(c => { - let span = document.createElement("span"); - if (c.added) { span.classList.add("added"); } - else if (c.removed) { span.classList.add("removed"); } - span.appendChild(document.createTextNode(c.value)); - newSpan.appendChild(span); - }); - - old.replaceWith(newSpan); - } -} diff --git a/client/src/pages/post.module.scss b/client/src/pages/post.module.scss new file mode 100644 index 0000000..88af0d3 --- /dev/null +++ b/client/src/pages/post.module.scss @@ -0,0 +1,5 @@ +@use "../css/config/variables/sass.scss" as *; + +.article { + margin: 0 auto +} diff --git a/client/src/pages/post.scss b/client/src/pages/post.scss index aa5d66e..469245f 100644 --- a/client/src/pages/post.scss +++ b/client/src/pages/post.scss @@ -1,4 +1,4 @@ -@use "../css/config/variables" as *; +@use "../css/config/variables/sass" as *; .post { &__nav-list { @@ -74,9 +74,10 @@ &__actions { font-size: 1.5em; + display: flex; & > * { - margin-right: 1em; + margin-right: 0.5em; user-select: none; &:last-child { @@ -85,40 +86,6 @@ } } - &__flag { - display: inline-block; - color: hsl(3, 100%, 69%); - font-weight: bold; - text-shadow: - hsl(0, 0%, 0%) 0px 0px 3px, - hsl(0, 0%, 0%) -1px -1px 0px, - hsl(0, 0%, 0%) 1px 1px 0px; - background-color: transparent; - border: transparent; - - // hack to overwrite * selector color - & span { - color: hsl(3, 100%, 69%); - } - - &--flagged { - color: hsl(0, 0%, 45%); - - // hack to overwrite * selector color - & span { - color: hsl(0, 0%, 45%); - } - } - - &--loading { - cursor: progress; - - & .post__flag-icon { - display: none; - } - } - } - &__fav { color: hsl(0, 0%, 100%); font-weight: bold; diff --git a/client/src/pages/post.tsx b/client/src/pages/post.tsx new file mode 100644 index 0000000..12260e1 --- /dev/null +++ b/client/src/pages/post.tsx @@ -0,0 +1,202 @@ +import { useEffect } from "react"; +import { LoaderFunctionArgs, useLoaderData, Link } from "react-router"; +import { Helmet } from "@dr.pogodin/react-helmet"; +import { + ICONS_PREPEND, + IS_ARCHIVER_ENABLED, + KEMONO_SITE, + SITE_NAME, +} from "#env/env-vars"; +import { fetchArtistProfile } from "#api/profiles"; +import { fetchPost, fetchPostComments } from "#api/posts"; +import { PageSkeleton } from "#components/pages"; +import { SliderAd } from "#components/advs"; +import { paysites, validatePaysite } from "#entities/paysites"; +import { + IComment, + IPost, + IPostAttachment, + IPostPreview, + IPostVideo, + PostOverview, +} from "#entities/posts"; +import { IArtistDetails } from "#entities/profiles"; + +import * as styles from "./post.module.scss"; + +interface IProps { + post: IPost; + profile: IArtistDetails; + revisions: Awaited>["props"]["revisions"]; + flagged: string | null; + videos?: IPostVideo[]; + attachments?: IPostAttachment[]; + previews?: IPostPreview[]; + archives_enabled?: boolean; + comments: Promise; +} + +export function PostPage() { + const { + post, + profile, + revisions, + flagged, + videos, + attachments, + previews, + archives_enabled, + comments, + } = useLoaderData() as IProps; + const paysite = paysites[post.service]; + const postTitle = post.title ?? "Untitled"; + const artistName = profile.name ?? post.user; + + const title = !profile + ? `Post "${postTitle}"` + : `Post "${postTitle}" by "${artistName}" from ${paysite.title}`; + + function handlePrevNextLinks(event: KeyboardEvent) { + switch (event.key) { + case "ArrowLeft": + document + .querySelector(".post__nav-link.prev") + ?.click(); + break; + case "ArrowRight": + document + .querySelector(".post__nav-link.next") + ?.click(); + break; + } + } + + useEffect(() => { + document.addEventListener("keydown", handlePrevNextLinks); + + return () => { + document.removeEventListener("keydown", handlePrevNextLinks); + }; + }, []); + + return ( + + + + + + {!post.published ? undefined : ( + + )} + + {/* */} + + + + + + {/* */} + + + + + + + + + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Profile ID is required."); + } + } + + const postID = params.post_id?.trim(); + { + if (!postID) { + throw new Error("Post ID is required."); + } + } + + const profile = await fetchArtistProfile(service, profileID); + const { post, attachments, previews, videos, props } = await fetchPost( + service, + profileID, + postID + ); + const { flagged, revisions } = props; + + const comments = fetchPostComments(service, profileID, postID); + + const pageProps = { + profile, + post, + revisions, + attachments, + previews, + videos, + archives_enabled: IS_ARCHIVER_ENABLED, + flagged, + comments, + } satisfies IProps; + + return pageProps; +} diff --git a/client/src/pages/post/data.tsx b/client/src/pages/post/data.tsx new file mode 100644 index 0000000..a8a623e --- /dev/null +++ b/client/src/pages/post/data.tsx @@ -0,0 +1,30 @@ +import { LoaderFunctionArgs, redirect } from "react-router"; +import { fetchPostData } from "#api/posts"; +import { createPostURL } from "#lib/urls"; +import { HTTP_STATUS } from "#lib/http"; +import { validatePaysite } from "#entities/paysites"; + +export async function loader({ params }: LoaderFunctionArgs) { + const inputService = params.service?.trim(); + + if (!inputService) { + throw new Error("Service is required."); + } + + validatePaysite(inputService); + + const postID = params.post_id?.trim(); + + if (!postID) { + throw new Error("Post ID is required."); + } + + const { service, artist_id, post_id } = await fetchPostData( + inputService, + postID + ); + + const url = String(createPostURL(service, artist_id, post_id)); + + return redirect(url, HTTP_STATUS.SEE_OTHER); +} diff --git a/client/src/pages/posts.html b/client/src/pages/posts.html deleted file mode 100644 index ae6d223..0000000 --- a/client/src/pages/posts.html +++ /dev/null @@ -1,57 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/post.html' import post_card %} -{% from 'components/ads.html' import slider_ad, header_ad, footer_ad %} - -{% block content %} - {{ slider_ad() }} -
    -
    -

    Posts

    -
    -
    - {% include 'components/paginator.html' %} -
    - - -
    -
    - - {{ header_ad() }} - - {% call card_list() %} - {% for post in results %} - {{ post_card(post) }} - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no posts for your query. -

    -
    - {% endfor %} - {% endcall %} - - {{ footer_ad() }} - -
    - {% include 'components/paginator.html' %} -
    -
    -{% endblock %} - - diff --git a/client/src/pages/posts.js b/client/src/pages/posts.js deleted file mode 100644 index 7c10cd4..0000000 --- a/client/src/pages/posts.js +++ /dev/null @@ -1,35 +0,0 @@ -import { CardList, PostCard, registerPaginatorKeybinds } from "@wp/components"; -import { isLoggedIn } from "@wp/js/account"; -import { findFavouriteArtist, findFavouritePost } from "@wp/js/favorites"; - -/** - * @param {HTMLElement} section - */ -export function postsPage(section) { - const cardListElement = section.querySelector(".card-list"); - if (!cardListElement){ - return; - } - const { cardList, cardItems } = CardList(cardListElement); - - cardItems.forEach(async (card) => { - registerPaginatorKeybinds(); - - const { postID, userID, service } = PostCard(card); - const favPost = isLoggedIn && (await findFavouritePost(service, userID, postID)); - const favUser = isLoggedIn && (await findFavouriteArtist(userID, service)); - - if (favPost) { - card.classList.add("post-card--fav"); - } - - if (favUser) { - const postHeader = card.querySelector(".post-card__header"); - const postFooter = card.querySelector(".post-card__footer"); - - postHeader.classList.add("post-card__header--fav"); - postFooter.classList.add("post-card__footer--fav"); - /* userName.textContent = favUser.name; this is doing nothing does it */ - } - }); -} diff --git a/client/src/pages/posts.tsx b/client/src/pages/posts.tsx new file mode 100644 index 0000000..a729bc6 --- /dev/null +++ b/client/src/pages/posts.tsx @@ -0,0 +1,219 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { parseOffset } from "#lib/pagination"; +import { createPostsPageURL } from "#lib/urls"; +import { fetchPosts } from "#api/posts"; +import { PageSkeleton } from "#components/pages"; +import { Paginator } from "#components/pagination"; +import { FooterAd, HeaderAd, SliderAd } from "#components/advs"; +import { CardList, PostCard } from "#components/cards"; +import { ButtonSubmit, FormRouter } from "#components/forms"; +import { IPost } from "#entities/posts"; +import { findFavouritePosts, findFavouriteProfiles } from "#entities/account"; +import { useRef, useState } from "react"; + +interface IProps { + count: number; + trueCount: number; + offset?: number; + posts: IPost[]; + query?: string; + tags?: string[]; +} + +export function PostsPage() { + const { count, trueCount, offset, query, posts, tags } = + useLoaderData() as IProps; + const [isLoading, setIsLoading] = useState(false); + const title = "Posts"; + const heading = "Posts"; + + return ( + + + + + +
    + setIsLoading(loading)} + /> + + + String(createPostsPageURL(offset, query, tags)) + } + /> +
    + + + {count === 0 ? ( +
    +

    Nobody here but us chickens!

    +

    There are no posts for your query.

    +
    + ) : ( + posts.map((post) => ( + + )) + )} +
    + +
    + + String(createPostsPageURL(offset, query, tags)) + } + /> +
    + + +
    + ); +} + +interface ISearchFormProps + extends Pick { + onLoadingChange: (loading: boolean) => void; +} + +// TODO: TAGS! add tag search!! +function SearchForm({ query, tags, onLoadingChange }: ISearchFormProps) { + const timeoutRef = useRef(null); + const onInputChange = (e: React.ChangeEvent) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + + const target = e.currentTarget as HTMLInputElement; + + onLoadingChange(true); + + timeoutRef.current = setTimeout(() => { + if (target.form) target.form.requestSubmit(); + onLoadingChange(false); + }, 1000); + }; + + return ( + + {(state) => ( + <> +
    + + { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + onLoadingChange(false); + }} + > + + +
    + + )} +
    + ); +} + +export async function loader({ request }: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let offset: number | undefined = undefined; + { + const parsedOffset = searchParams.get("o")?.trim(); + + if (parsedOffset) { + offset = parseOffset(parsedOffset); + } + } + + const query = searchParams.get("q")?.trim(); + const tags = searchParams.getAll("tag"); + const { count, true_count, posts } = await fetchPosts(offset, query, tags); + const postsData = posts.map(({ service, user, id }) => ({ + service, + user, + id, + })); + const profilesData = posts.reduce<{ service: string; id: string }[]>( + (profilesData, post) => { + const match = profilesData.find( + (profileData) => + profileData.id === post.user && profileData.service === post.service + ); + + if (!match) { + profilesData.push({ service: post.service, id: post.user }); + } + + return profilesData; + }, + [] + ); + const favPosts = await findFavouritePosts(postsData); + const favProfiles = await findFavouriteProfiles(profilesData); + const postsWithFavs = posts.map((post) => { + const isFavPost = Boolean( + favPosts.find( + ({ service, user, id }) => + id === post.id && user === post.user && service === post.service + ) + ); + const isFavProfile = Boolean( + favProfiles.find( + ({ service, id }) => id === post.user && service === post.service + ) + ); + + if (!isFavPost && !isFavProfile) { + return post; + } + + return { + ...post, + isFavourite: isFavPost, + isFavouriteProfile: isFavProfile, + }; + }); + + return { + offset, + query, + tags, + count, + trueCount: true_count, + posts: postsWithFavs, + }; +} diff --git a/client/src/pages/posts/archive.html b/client/src/pages/posts/archive.html deleted file mode 100644 index 1edf998..0000000 --- a/client/src/pages/posts/archive.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "components/shell.html" %} - -{% block title %} - Archived Files | {{ g.site_name }} -{% endblock %} - -{% block content %} -
    -
    -

    Archive Files

    -
    -
    - -
    -

    - {% if archive.password %} - Archive password: {{archive.password}} - {% elif archive.password == "" %} - Archive needs password, but none was provided. Click to input - {% endif %} -

    -
    - - {% for file_name in archive.file_list %} - {% if file_serving_enabled and archive.password %} - {{file_name}}
    - {% elif file_serving_enabled and archive.password == None %} - {{file_name}}
    - {% else %} - {{file_name}}
    - {% endif %} - {% else %} - {% if archive %} - Archive is empty or missing password. - {% else %} - File does not exist or is not an archive. - {% endif %} - {% endfor %} - - -{% endblock %} diff --git a/client/src/pages/posts/popular.html b/client/src/pages/posts/popular.html deleted file mode 100644 index 3c4e918..0000000 --- a/client/src/pages/posts/popular.html +++ /dev/null @@ -1,114 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/card_list.html" import card_list %} -{% from "components/cards/post.html" import post_fav_card %} -{% from "components/ads.html" import slider_ad, header_ad, footer_ad %} - -{% block title %} - Popular Posts | {{ g.site_name }} -{% endblock %} - -{% block content %} - {{ slider_ad() }} - - - -{% endblock %} diff --git a/client/src/pages/posts/popular.tsx b/client/src/pages/posts/popular.tsx new file mode 100644 index 0000000..9e41a49 --- /dev/null +++ b/client/src/pages/posts/popular.tsx @@ -0,0 +1,378 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createPopularPostsPageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { fetchPopularPosts } from "#api/posts"; +import { PageSkeleton } from "#components/pages"; +import { Paginator } from "#components/pagination"; +import { FooterAd, HeaderAd, SliderAd } from "#components/advs"; +import { CardList, PostCard } from "#components/cards"; +import { + IPopularPostsPeriod, + IPost, + validatePeriod, +} from "#entities/posts"; +import { KemonoLink } from "#components/links"; +import { findFavouritePosts, findFavouriteProfiles } from "#entities/account"; + +interface IProps { + /** + * Datetime string. + */ + minDate: string; + /** + * Datetime string. + */ + maxDate: string; + /** + * Human description of range. + */ + rangeDescription: string; + /** + * Date string. + */ + earliestDateForPopular: string; + /** + * Value is a tuple of date strings. + */ + navigationDates: Record; + scale?: IPopularPostsPeriod; + /** + * Date string. + */ + today: string; + count: number; + posts: IPost[]; + offset?: number; + /** + * Datetime string. + */ + date?: string; +} + +/** + * A tuple of date strings. + */ +type INavigationDate = [prev: string, next: string, current: string]; + +export function PopularPostsPage() { + const { + minDate, + maxDate, + rangeDescription, + navigationDates, + earliestDateForPopular, + scale, + today, + count, + offset, + date, + posts, + } = useLoaderData() as IProps; + const title = `Popular posts for ${rangeDescription}`; + + return ( + + Popular Posts for{" "} + {rangeDescription} + + } + > + + +
    + + + + + + +
    + + String(createPopularPostsPageURL(date, scale, offset)) + } + /> + + + + + {count === 0 ? ( +
    +

    Nobody here but us chickens!

    +

    There are no posts for your query.

    +
    + ) : ( + posts.map((post) => ( + + )) + )} +
    + + + +
    + + String(createPopularPostsPageURL(date, scale, offset)) + } + /> +
    +
    +
    +
    + ); +} + +interface IDailySelectorProps + extends Pick { + navigationDate: INavigationDate; +} + +function DailySelector({ + earliestDateForPopular, + scale, + today, + navigationDate, +}: IDailySelectorProps) { + const [prev, next, current] = navigationDate; + + return ( +
    + + {prev < earliestDateForPopular ? ( + next » + ) : ( + + « prev + + )} + {" "} + + {scale === "day" ? ( + Day + ) : ( + + Day + + )} + {" "} + + {next > today ? ( + next » + ) : ( + + next » + + )} + +
    + ); +} + +interface IWeeklySelectorProps + extends Pick { + navigationDate: [string, string, string]; +} + +function WeeklySelector({ + earliestDateForPopular, + scale, + today, + navigationDate, +}: IWeeklySelectorProps) { + const [prev, next, current] = navigationDate; + + return ( +
    + + {prev < earliestDateForPopular ? ( + next » + ) : ( + + « prev + + )} + {" "} + + {scale === "week" ? ( + Week + ) : ( + + Week + + )} + {" "} + + {next > today ? ( + next » + ) : ( + + next » + + )} + +
    + ); +} + +interface IMonthlySelectorProps + extends Pick { + navigationDate: [string, string, string]; +} + +function MonthlySelector({ + earliestDateForPopular, + scale, + today, + navigationDate, +}: IMonthlySelectorProps) { + const [prev, next, current] = navigationDate; + + return ( +
    + + {prev < earliestDateForPopular ? ( + next » + ) : ( + + « prev + + )} + {" "} + + {scale === "month" ? ( + Month + ) : ( + + Month + + )} + {" "} + + + {next > today ? ( + next » + ) : ( + + next » + + )} + + +
    + ); +} + +export async function loader({ request }: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + const inputDate = searchParams.get("date")?.trim(); + + const scale = searchParams.get("period")?.trim() ?? "recent"; + validatePeriod(scale); + + let offset: number | undefined; + { + const inputOffset = searchParams.get("o")?.trim(); + + if (inputOffset) { + offset = parseOffset(inputOffset); + } + } + + const { + info, + props, + results: posts, + } = await fetchPopularPosts(inputDate, scale, offset); + const { count, earliest_date_for_popular, today } = props; + const { date, range_desc, min_date, max_date, navigation_dates } = info; + const postsData = posts.map(({ service, user, id }) => ({ + service, + user, + id, + })); + const profilesData = posts.reduce<{ service: string; id: string }[]>( + (profilesData, post) => { + const match = profilesData.find( + (profileData) => + profileData.id === post.user && profileData.service === post.service + ); + + if (!match) { + profilesData.push({ service: post.service, id: post.user }); + } + + return profilesData; + }, + [] + ); + const favPosts = await findFavouritePosts(postsData); + const favProfiles = await findFavouriteProfiles(profilesData); + const postsWithFavs = posts.map((post) => { + const isFavPost = Boolean( + favPosts.find( + ({ service, user, id }) => + id === post.id && user === post.user && service === post.service + ) + ); + const isFavProfile = Boolean( + favProfiles.find( + ({ service, id }) => id === post.user && service === post.service + ) + ); + + if (!isFavPost && !isFavProfile) { + return post; + } + + return { + ...post, + isFavourite: isFavPost, + isFavouriteProfile: isFavProfile, + }; + }); + + return { + date, + count, + scale, + offset, + posts: postsWithFavs, + today, + earliestDateForPopular: earliest_date_for_popular, + rangeDescription: range_desc, + minDate: min_date, + maxDate: max_date, + navigationDates: navigation_dates, + }; +} diff --git a/client/src/pages/posts/random.tsx b/client/src/pages/posts/random.tsx new file mode 100644 index 0000000..3959f56 --- /dev/null +++ b/client/src/pages/posts/random.tsx @@ -0,0 +1,10 @@ +import { redirect } from "react-router"; +import { createPostURL } from "#lib/urls"; +import { fetchRandomPost } from "#api/posts"; + +export async function loader() { + const { service, artist_id, post_id } = await fetchRandomPost(); + const url = String(createPostURL(service, artist_id, post_id)); + + return redirect(url); +} diff --git a/client/src/pages/user.scss b/client/src/pages/profile.scss similarity index 98% rename from client/src/pages/user.scss rename to client/src/pages/profile.scss index 3febb0b..8e31111 100644 --- a/client/src/pages/user.scss +++ b/client/src/pages/profile.scss @@ -1,4 +1,4 @@ -@use "../css/config/variables" as *; +@use "../css/config/variables/sass" as *; .tabs { margin: 0 auto; diff --git a/client/src/pages/profile.tsx b/client/src/pages/profile.tsx new file mode 100644 index 0000000..72b7e94 --- /dev/null +++ b/client/src/pages/profile.tsx @@ -0,0 +1,255 @@ +import { LoaderFunctionArgs, redirect, useLoaderData } from "react-router"; +import { createDiscordServerPageURL, createProfilePageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { ElementType } from "#lib/types"; +import { fetchProfilePosts } from "#api/profiles"; +import { FooterAd, SliderAd } from "#components/advs"; +import { Paginator } from "#components/pagination"; +import { CardList, PostCard } from "#components/cards"; +import { ProfilePageSkeleton } from "#components/pages"; +import { ButtonSubmit, FormRouter } from "#components/forms"; +import { + ProfileHeader, + Tabs, + IArtistDetails, + getArtist, +} from "#entities/profiles"; +import { paysites } from "#entities/paysites"; +import { IPost } from "#entities/posts"; +import { findFavouritePosts } from "#entities/account"; +import { useRef, useState } from "react"; + +interface IProps { + profile: IArtistDetails; + postsData?: { + count: number; + offset?: number; + posts: (IPost & { isFavourite: boolean })[]; + }; + + query?: string; + tags?: string[]; + dmCount?: number; + hasLinks?: boolean; +} + +export function ProfilePage() { + const { profile, postsData, query, tags, dmCount, hasLinks } = + useLoaderData() as IProps; + const [isLoading, setIsLoading] = useState(false); + const { service, id, name } = profile; + const paysite = paysites[service]; + const title = `Posts of "${name}" from "${paysite.title}"`; + + return ( + + + + + +
    + + + {!(postsData && (postsData?.count !== 0 || query)) ? undefined : ( + <> + setIsLoading(loading)} + /> + + String( + createProfilePageURL({ + service, + profileID: id, + offset, + query, + tags, + }) + ) + } + /> + + )} +
    + + {!postsData ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no posts for your query.

    +
    + ) : ( + <> + + {postsData.posts.map((post) => ( + + ))} + + + + +
    + + String( + createProfilePageURL({ + service, + profileID: id, + offset, + query, + tags, + }) + ) + } + /> +
    + + )} +
    + ); +} + +interface ISearchFormProps + extends Pick { + onLoadingChange: (loading: boolean) => void; +} + +function SearchForm({ query, onLoadingChange }: ISearchFormProps) { + const timeoutRef = useRef(null); + + const onInputChange = (e: React.ChangeEvent) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + + const target = e.currentTarget as HTMLInputElement; + + onLoadingChange(true); + + timeoutRef.current = setTimeout(() => { + if (target.form) target.form.requestSubmit(); + onLoadingChange(false); + }, 1000); + }; + + return ( + + {(state) => ( + <> +
    + + { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + onLoadingChange(false); + }} + > + + +
    + + )} +
    + ); +} + +export async function loader({ + params, + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + const service = params.service?.trim() || ""; + const profileID = params.creator_id?.trim() || ""; + + if (service === "discord") { + return redirect(String(createDiscordServerPageURL(profileID))); + } + + const offsetParam = searchParams.get("o")?.trim(); + const offset = offsetParam ? parseOffset(offsetParam) : undefined; + const query = searchParams.get("q")?.trim(); + const tags = searchParams.getAll("tag"); + + const profile = await getArtist(service, profileID); + + if (profileID == profile.public_id && profile.public_id != profile.id) { + return redirect(String(createProfilePageURL({ service, profileID: profile.id }))); + } + + const { props, results: posts } = await fetchProfilePosts( + service, + profileID, + offset, + query, + tags + ); + const { count, dm_count, has_links } = props; + const hasLinks = !has_links || has_links === "0" ? false : true; + const favPostData = await findFavouritePosts( + posts.map(({ service, user, id }) => { + return { + service, + user, + id, + }; + }) + ); + const finalPosts = posts.map< + ElementType["postsData"]["posts"]> + >((post) => { + const match = favPostData.find( + ({ service, user, id }) => + service === post.service && user === post.user && id === post.id + ); + + return { ...post, isFavourite: !match ? false : true }; + }); + + return { + profile, + query, + tags, + postsData: { + count, + offset, + posts: finalPosts, + }, + dmCount: dm_count, + hasLinks, + }; +} diff --git a/client/src/pages/artist/_index.scss b/client/src/pages/profile/_index.scss similarity index 80% rename from client/src/pages/artist/_index.scss rename to client/src/pages/profile/_index.scss index 7227f44..cdeb657 100644 --- a/client/src/pages/artist/_index.scss +++ b/client/src/pages/profile/_index.scss @@ -1,3 +1,4 @@ @use "dms"; @use "fancards"; @use "linked_accounts"; +@use "tags"; diff --git a/client/src/pages/profile/announcements.tsx b/client/src/pages/profile/announcements.tsx new file mode 100644 index 0000000..071f0aa --- /dev/null +++ b/client/src/pages/profile/announcements.tsx @@ -0,0 +1,107 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { Helmet } from "@dr.pogodin/react-helmet"; +import { ICONS_PREPEND, KEMONO_SITE, SITE_NAME } from "#env/env-vars"; +import { fetchAnnouncements } from "#api/posts"; +import { fetchArtistProfile } from "#api/profiles"; +import { PageSkeleton } from "#components/pages"; +import { CardList } from "#components/cards"; +import { ProfileHeader, Tabs, IArtistDetails } from "#entities/profiles"; +import { paysites, validatePaysite } from "#entities/paysites"; +import { IAnnouncement } from "#entities/posts"; +import { AnnouncementPreview } from "#entities/posts"; + +interface IProps { + service: string; + profile: IArtistDetails; + announcements: IAnnouncement[]; +} + +export function AnnouncementsPage() { + const { service, profile, announcements } = useLoaderData() as IProps; + const paysite = paysites[service]; + const title = `Announcements of "${profile.name}" from ${paysite.title}`; + const heading = "Announcements"; + + return ( + + + + + + {/* */} + + + + + + {/* */} + + + + +
    + +
    + + + {!announcements.length ? ( +
    +

    + Nobody here but us chickens! +

    +

    + There are no Announcements for your query. +

    +
    + ) : ( + announcements.map((announcement) => ( + + )) + )} +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Artist ID is required."); + } + } + + const profile = await fetchArtistProfile(service, profileID); + const announcements = await fetchAnnouncements(service, profileID); + + return { + service, + profile, + announcements, + }; +} diff --git a/client/src/pages/artist/dms.scss b/client/src/pages/profile/dms.scss similarity index 73% rename from client/src/pages/artist/dms.scss rename to client/src/pages/profile/dms.scss index 3a755a4..80f7338 100644 --- a/client/src/pages/artist/dms.scss +++ b/client/src/pages/profile/dms.scss @@ -1,4 +1,4 @@ -@use "../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .site-section--dms { .no-results { diff --git a/client/src/pages/profile/dms.tsx b/client/src/pages/profile/dms.tsx new file mode 100644 index 0000000..d3d69ce --- /dev/null +++ b/client/src/pages/profile/dms.tsx @@ -0,0 +1,101 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { Helmet } from "@dr.pogodin/react-helmet"; +import { ICONS_PREPEND, KEMONO_SITE, SITE_NAME } from "#env/env-vars"; +import { fetchProfileDMs } from "#api/dms"; +import { PageSkeleton } from "#components/pages"; +import { CardList, DMCard } from "#components/cards"; +import { ProfileHeader, Tabs, IArtist } from "#entities/profiles"; +import { paysites, validatePaysite } from "#entities/paysites"; +import { IApprovedDM } from "#entities/dms"; + +interface IProps { + profile: IArtist; + service: string; + dmCount: number; + dms: IApprovedDM[]; +} + +export function ProfileDMsPage() { + const { profile, service, dmCount, dms } = useLoaderData() as IProps; + const paysite = paysites[service]; + const title = `DMs of "${profile.name}" (${profile.id}) from ${paysite.title}`; + const heading = "DMs"; + + return ( + + + + + + {/* */} + + + + + + {/* */} + + + + +
    + +
    + + + {dmCount === 0 ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no DMs for your query.

    +
    + ) : ( + dms.map((dm) => ) + )} +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Artist ID is required."); + } + } + + const { props } = await fetchProfileDMs(service, profileID); + const { artist, dm_count, dms } = props; + + return { + profile: artist, + service, + dmCount: dm_count, + dms, + }; +} diff --git a/client/src/pages/artist/fancards.scss b/client/src/pages/profile/fancards.scss similarity index 75% rename from client/src/pages/artist/fancards.scss rename to client/src/pages/profile/fancards.scss index d634373..bcaac05 100644 --- a/client/src/pages/artist/fancards.scss +++ b/client/src/pages/profile/fancards.scss @@ -1,14 +1,15 @@ div#fancard-container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); - margin-top: 0.5em; - gap: 0.5em 0.25em; + display: flex; + flex-wrap: wrap; + justify-content: center; article.fancard__file { width: 400px; height: 293px; display: inline-grid; background-color: var(--colour1-secondary); + margin: 0.5em; + margin-bottom: 0.25em; span { padding-top: 0.5em; diff --git a/client/src/pages/profile/fancards.tsx b/client/src/pages/profile/fancards.tsx new file mode 100644 index 0000000..ad09908 --- /dev/null +++ b/client/src/pages/profile/fancards.tsx @@ -0,0 +1,123 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { Helmet } from "@dr.pogodin/react-helmet"; +import { + ICONS_PREPEND, + KEMONO_SITE, + SITE_NAME, + THUMBNAILS_PREPEND, +} from "#env/env-vars"; +import { fetchFanboxProfileFancards } from "#api/profiles"; +import { PageSkeleton } from "#components/pages"; +import { validatePaysite } from "#entities/paysites"; +import { + ProfileHeader, + Tabs, + IArtistDetails, + getArtist, +} from "#entities/profiles"; +import { IFanCard } from "#entities/files"; + +interface IProps { + profile: IArtistDetails; + cards: IFanCard[]; +} + +export function FancardsPage() { + const { profile, cards } = useLoaderData() as IProps; + const title = "Fancards"; + const heading = "Fancards"; + + return ( + + + + + + {/* */} + + + + + + {/* */} + + + + +
    + +
    + {cards.length === 0 ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no uploads for your query.

    +
    + ) : ( + cards.map((card) => ( +
    + + Added {card.added.slice(0, 7)} + + + + +
    + )) + )} +
    +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + + if (service !== "fanbox") { + throw new Error(`Service must be "fanbox".`); + } + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Artist ID is required."); + } + } + + const profile = await getArtist(service, profileID); + const cards = await fetchFanboxProfileFancards(profileID); + + return { + profile, + cards, + }; +} diff --git a/client/src/pages/artist/linked_accounts.scss b/client/src/pages/profile/linked_accounts.scss similarity index 100% rename from client/src/pages/artist/linked_accounts.scss rename to client/src/pages/profile/linked_accounts.scss diff --git a/client/src/pages/profile/linked_accounts.tsx b/client/src/pages/profile/linked_accounts.tsx new file mode 100644 index 0000000..f31b4ee --- /dev/null +++ b/client/src/pages/profile/linked_accounts.tsx @@ -0,0 +1,100 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createProfileNewLinksPageURL } from "#lib/urls"; +import { fetchArtistProfile, fetchProfileLinks } from "#api/profiles"; +import { CardList, ArtistCard } from "#components/cards"; +import { ProfilePageSkeleton } from "#components/pages"; +import { ProfileHeader, Tabs, IArtistDetails } from "#entities/profiles"; +import { validatePaysite } from "#entities/paysites"; + +interface IProps { + service: string; + profile: IArtistDetails; + links: Awaited>; +} + +export function ProfileLinksPage() { + const { service, profile, links } = useLoaderData() as IProps; + const title = "Linked profiles"; + const heading = "Linked Profiles"; + + return ( + + + +
    + +
    + + + + + {links.length === 0 ? ( +

    + No linked accounts found. +

    + ) : ( + links.map((profile) => ( + + )) + )} +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Artist ID is required."); + } + } + + const profile = await fetchArtistProfile(service, profileID); + const links = await fetchProfileLinks(service, profileID); + + return { + service, + profile, + links, + }; +} diff --git a/client/src/pages/profile/new-linked-profile.module.scss b/client/src/pages/profile/new-linked-profile.module.scss new file mode 100644 index 0000000..b698d5c --- /dev/null +++ b/client/src/pages/profile/new-linked-profile.module.scss @@ -0,0 +1,3 @@ +.selected { + border: 2px solid green; +} diff --git a/client/src/pages/profile/new-linked-profile.tsx b/client/src/pages/profile/new-linked-profile.tsx new file mode 100644 index 0000000..368faaf --- /dev/null +++ b/client/src/pages/profile/new-linked-profile.tsx @@ -0,0 +1,277 @@ +import { useEffect, useState } from "react"; +import clsx from "clsx"; +import { + ActionFunctionArgs, + LoaderFunctionArgs, + useActionData, + useLoaderData, +} from "react-router"; +import { SITE_NAME } from "#env/env-vars"; +import { AVAILABLE_PAYSITE_LIST } from "#env/derived-vars"; +import { ElementType } from "#lib/types"; +import { fetchAddProfileLink } from "#api/account"; +import { + ProfilePageSkeleton, + createAccountPageLoader, +} from "#components/pages"; +import { FormRouter, FormSection, ButtonSubmit } from "#components/forms"; +import { InputHidden } from "#components/forms/inputs"; +import { ArtistCard } from "#components/cards"; +import { Button } from "#components/buttons"; +import { + ProfileHeader, + Tabs, + IArtistDetails, + getArtist, + getArtists, +} from "#entities/profiles"; +import { validatePaysite } from "#entities/paysites"; + +import * as styles from "./new-linked-profile.module.scss"; + +interface IProps { + profile: IArtistDetails; +} + +interface IAction { + message: string; +} + +export function NewProfileLinkPage() { + const { profile } = useLoaderData() as IProps; + const [currentService, changeCurrentService] = useState(); + const [currentQuery, changeCurrentQuery] = useState(); + const [profileSuggestions, changeProfileSuggestions] = + useState>>(); + const [selectedProfile, changeSelectedProfile] = + useState>["artists"]>>(); + const title = "Link new profile"; + const heading = "Link New Profile"; + + useEffect(() => { + (async () => { + const profiles = await getArtists({ + service: currentService, + query: currentQuery, + }); + + changeProfileSuggestions(profiles); + })(); + }, [currentService, currentQuery]); + + return ( + + + +
    + +
    + +

    + If you believe this profile has other profiles on {SITE_NAME}, you can + use this form to request they be linked. +

    + + + id="new_link_form" + className="form--wide" + method="POST" + successElement={({ message }) => ( +
      +
    • {message}
    • +
    + )} + > + + + + + + + + + { + const query = event.currentTarget.value.trim(); + + changeCurrentQuery(query.length === 0 ? undefined : query); + }} + /> + + + + + + + + + + + Request link + + + +
    +
    + {!profileSuggestions ? ( + <>Please select a creator. + ) : profileSuggestions.count === 0 ? ( + <>No results found. + ) : ( + profileSuggestions.artists.slice(0, 20).map((profile) => ( +
    + + +
    + )) + )} +
    +
    +
    + ); +} + +export const loader = createAccountPageLoader(async function loader({ + params, +}: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Profile ID is required."); + } + } + + const profile = await getArtist(service, profileID); + + return { profile }; +}); + +export async function action({ + params, + request, +}: ActionFunctionArgs): Promise { + if (request.method !== "POST") { + throw new Error(`Unknown method "${request.method}".`); + } + + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Profile ID is required."); + } + } + + const formData = await request.formData(); + + const inputData = (formData.get("creator") as string | null) + ?.trim() + .split("/"); + + if (!inputData) { + throw new Error("Input data is required."); + } + + if (inputData.length !== 2) { + throw new Error("Invalid input data length."); + } + + const [suggestedService, suggestedProfileID] = inputData; + + const reason = (formData.get("reason") as string | null)?.trim(); + + const { message } = await fetchAddProfileLink( + service, + profileID, + suggestedService, + suggestedProfileID, + reason + ); + + return { message }; +} diff --git a/client/src/pages/tags.scss b/client/src/pages/profile/tags.scss similarity index 93% rename from client/src/pages/tags.scss rename to client/src/pages/profile/tags.scss index 73e6282..8160aa1 100644 --- a/client/src/pages/tags.scss +++ b/client/src/pages/profile/tags.scss @@ -1,4 +1,4 @@ -@use "../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; h2#all-tags-header { margin-left: auto; diff --git a/client/src/pages/profile/tags.tsx b/client/src/pages/profile/tags.tsx new file mode 100644 index 0000000..7e642b7 --- /dev/null +++ b/client/src/pages/profile/tags.tsx @@ -0,0 +1,73 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createProfileTagURL } from "#lib/urls"; +import { PageSkeleton } from "#components/pages"; +import { getTags } from "#entities/tags"; +import { paysites, validatePaysite } from "#entities/paysites"; +import { ProfileHeader, Tabs } from "#entities/profiles"; + +interface IProps extends Awaited> {} + +export function ProfileTagsPage() { + const { service, artist, tags } = useLoaderData() as IProps; + const paysite = paysites[service]; + const title = `Tags of "${artist.name}" from ${paysite.title}`; + + return ( + + + +
    + +
    + +
    + {tags.length === 0 ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no tags for your query.

    +
    + ) : ( + tags.map((tag, index) => ( + + )) + )} +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Artist ID is required."); + } + } + + const result = await getTags(service, profileID); + + return result; +} diff --git a/client/src/pages/profiles.module.scss b/client/src/pages/profiles.module.scss new file mode 100644 index 0000000..e913635 --- /dev/null +++ b/client/src/pages/profiles.module.scss @@ -0,0 +1,7 @@ +.loading { + text-align: center; +} + +.error { + text-align: center; +} diff --git a/client/src/pages/profiles.tsx b/client/src/pages/profiles.tsx new file mode 100644 index 0000000..f3f65f4 --- /dev/null +++ b/client/src/pages/profiles.tsx @@ -0,0 +1,327 @@ +import clsx from "clsx"; +import { Suspense, useRef, useState } from "react"; +import { + useLoaderData, + LoaderFunctionArgs, + Await, + useAsyncError, +} from "react-router"; +import { PAYSITE_LIST } from "#env/env-vars"; +import { + ARTISTS_OR_CREATORS_LOWERCASE, + AVAILABLE_PAYSITE_LIST, +} from "#env/derived-vars"; +import { createArtistsPageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { PageSkeleton } from "#components/pages"; +import { FooterAd, HeaderAd, SliderAd } from "#components/advs"; +import { Paginator } from "#components/pagination"; +import { CardList, ArtistCard } from "#components/cards"; +import { ButtonSubmit, FormRouter, FormSection } from "#components/forms"; +import { LoadingIcon } from "#components/loading"; +import { getArtists } from "#entities/profiles"; + +import * as styles from "./profiles.module.scss"; + +interface IProps { + results: ReturnType; + query?: string; + service?: string; + sort_by?: ISortField; + order?: "asc" | "desc"; + offset?: number; + true_count?: number; +} + +const sortFields = [ + "favorited", + "indexed", + "updated", + "name", + "service", +] as const; + +type ISortField = (typeof sortFields)[number]; + +function validateSortField(input: unknown): asserts input is ISortField { + if (!sortFields.includes(input as ISortField)) { + throw new Error(`Invalid sort field value "${input}".`); + } +} + +export function ArtistsPage() { + const { results, query, service, sort_by, order, offset } = + useLoaderData() as IProps; + const title = "Artists"; + const heading = "Artists"; + + return ( + + + + + +
    + + }> + } resolve={results}> + {(resolvedResult: Awaited) => ( + { + const url = createArtistsPageURL( + offset, + query, + service, + sort_by, + order + ); + + return String(url); + }} + /> + )} + + +
    + + + + Loading creators... please wait! +

    + } + > + }> + {(resolvedResult: Awaited) => + resolvedResult.artists.length === 0 ? ( +

    + No {ARTISTS_OR_CREATORS_LOWERCASE} found for your query. +

    + ) : ( + resolvedResult.artists.map((artist) => ( + + )) + ) + } +
    +
    +
    + +
    + }> + } resolve={results}> + {(resolvedResult: Awaited) => ( + { + const url = createArtistsPageURL( + offset, + query, + service, + sort_by, + order + ); + + return String(url); + }} + /> + )} + + +
    + + +
    + ); +} + +interface ISearchFormProps + extends Pick { } + +function SearchForm({ query, service, sort_by, order }: ISearchFormProps) { + const sortRef = useRef(null); + const timeoutRef = useRef(null); + const [sortDirection, setSortDirection] = useState(order); + + const onSortChange = (e: React.MouseEvent) => { + e.preventDefault(); + setSortDirection(sortDirection === "asc" ? "desc" : "asc"); + if (sortRef.current) { + sortRef.current.value = sortDirection === "asc" ? "desc" : "asc"; + sortRef.current.form?.requestSubmit(); + } + } + const onSelectChange = (e: React.ChangeEvent) => e.currentTarget.form?.requestSubmit(); + const onInputChange = (e: React.ChangeEvent) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + + const target = e.currentTarget as HTMLInputElement; + + timeoutRef.current = setTimeout(() => { + if (target.form) target.form.requestSubmit(); + }, 500); + }; + + return ( + + {(state) => ( + <> +
    + + + + +
    +
    + + + +
    + + + )} +
    + ); +} + +function CollectionError() { + const error = useAsyncError(); + console.error(error); + + return ( +
    +

    Failed to load artists.

    +
    + Details + {/* @ts-expect-error vague type definition */} +

    {error?.statusText || error?.message}

    +
    +
    + ); +} + +export async function loader({ + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let offset: IProps["offset"] | undefined = undefined; + { + const inputOffset = searchParams.get("o")?.trim(); + + if (inputOffset) { + offset = parseOffset(inputOffset); + } + } + + let query: IProps["query"] | undefined = searchParams.get("q")?.trim(); + + let sort_by: IProps["sort_by"] | undefined = undefined; + { + const inputValue = searchParams.get("sort_by")?.trim(); + + if (inputValue) { + validateSortField(inputValue); + sort_by = inputValue; + } + } + + let order_by: IProps["order"] | undefined = undefined; + { + const inputValue = searchParams.get("order")?.trim(); + + if (inputValue) { + if (inputValue !== "asc" && inputValue !== "desc") { + throw new Error(`Invalid order by field "${inputValue}".`); + } + + order_by = inputValue; + } + } + + let service: IProps["service"] = undefined; + { + const inputValue = searchParams.get("service")?.trim(); + + if (inputValue) { + if (!PAYSITE_LIST.includes(inputValue)) { + throw new Error(`Unknown service "${inputValue}".`); + } + } + + service = inputValue; + } + + const results = getArtists({ + offset, + order: order_by, + service, + sort_by, + query, + }); + + const pageProps = { + results, + sort_by, + order: order_by, + offset, + service, + query, + } satisfies IProps; + + return pageProps; +} diff --git a/client/src/pages/profiles/random.tsx b/client/src/pages/profiles/random.tsx new file mode 100644 index 0000000..575cad7 --- /dev/null +++ b/client/src/pages/profiles/random.tsx @@ -0,0 +1,10 @@ +import { redirect } from "react-router"; +import { createProfilePageURL } from "#lib/urls"; +import { fetchRandomArtist } from "#api/profiles"; + +export async function loader() { + const { service, artist_id } = await fetchRandomArtist(); + const url = String(createProfilePageURL({ service, profileID: artist_id })); + + return redirect(url); +} diff --git a/client/src/pages/profiles/updated.tsx b/client/src/pages/profiles/updated.tsx new file mode 100644 index 0000000..857d811 --- /dev/null +++ b/client/src/pages/profiles/updated.tsx @@ -0,0 +1,97 @@ +import clsx from "clsx"; +import { useLoaderData, LoaderFunctionArgs } from "react-router"; +import { ARTISTS_OR_CREATORS_LOWERCASE } from "#env/derived-vars"; +import { createArtistsUpdatedPageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { PageSkeleton } from "#components/pages"; +import { FooterAd, HeaderAd, SliderAd } from "#components/advs"; +import { Paginator } from "#components/pagination"; +import { CardList, ArtistCard } from "#components/cards"; +import { getArtists } from "#entities/profiles"; + +interface IProps { + results: Awaited>["artists"]; + offset: number; + count: number; +} + +export function ArtistsUpdatedPage() { + const { results, count, offset } = useLoaderData() as IProps; + const title = "Latest cached updated artists"; + const heading = "Latest Cached Updated Artists"; + + return ( + + + +
    + { + const url = createArtistsUpdatedPageURL(offset); + + return String(url); + }} + /> +
    + + + + + {results.length === 0 ? ( +

    + No {ARTISTS_OR_CREATORS_LOWERCASE} found for your query. +

    + ) : ( + results.map((artist) => ( + + )) + )} +
    + +
    + { + const url = createArtistsUpdatedPageURL(offset); + + return String(url); + }} + /> +
    + + +
    + ); +} + +export async function loader({ request }: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + const sort_by = "updated"; + + let offset: IProps["offset"] = 0; + { + const inputOffset = searchParams.get("o")?.trim(); + + if (inputOffset) { + offset = parseOffset(inputOffset); + } + } + + const { artists, count } = await getArtists({ offset, sort_by }); + + return { + results: artists, + count, + offset, + }; +} diff --git a/client/src/pages/review_dms/dms.js b/client/src/pages/review_dms/dms.js deleted file mode 100644 index eb87036..0000000 --- a/client/src/pages/review_dms/dms.js +++ /dev/null @@ -1,14 +0,0 @@ -import { initPendingReviewDms } from "@wp/js/pending-review-dms"; - -export async function reviewDMsPage() { - - const status_selector = document.getElementById("status"); - status_selector.addEventListener("change", async function (e) { - e.preventDefault(); - const currentUrl = new URL(window.location.href); - const urlParams = currentUrl.searchParams; - urlParams.set('status', status_selector.value); - window.location.href = currentUrl.toString(); - }); - await initPendingReviewDms(true); -} diff --git a/client/src/pages/review_dms/dms.scss b/client/src/pages/review_dms/dms.scss index 3309d35..a3d42de 100644 --- a/client/src/pages/review_dms/dms.scss +++ b/client/src/pages/review_dms/dms.scss @@ -1,4 +1,4 @@ -@use "../../css/config/variables" as *; +@use "../../css/config/variables/sass" as *; .site-section--review-dms { .dms { diff --git a/client/src/pages/review_dms/review_dms.html b/client/src/pages/review_dms/review_dms.html deleted file mode 100644 index 7c13def..0000000 --- a/client/src/pages/review_dms/review_dms.html +++ /dev/null @@ -1,81 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/cards/dm.html' import dm_card %} - -{% block title %} - Approve DMs for import to {{ g.site_name }}. -{% endblock title %} - -{% block meta %} - -{% endblock meta %} - -{% block content %} -
    -
    -

    - DM Review for {{ props.import_id }} -

    -
    -
    - - -
    -
    -
    - {% if props.dms %} -
    - {% for dm in props.dms %} -
    - -
    - {{ dm_card(dm, class_name='dms__card', is_private=true, artist=(dm|attr("artist") or {}) ) }} - -
    -
    - {% endfor %} - {% if props.status == "ignored" %} - - {% endif %} -
    - -
    -
    - {% else %} -

    - There are no DMs waiting for approval. -

    - {% endif %} -
    -{% endblock %} diff --git a/client/src/pages/review_dms/review_dms.module.scss b/client/src/pages/review_dms/review_dms.module.scss new file mode 100644 index 0000000..0e3a7bb --- /dev/null +++ b/client/src/pages/review_dms/review_dms.module.scss @@ -0,0 +1,3 @@ +.link { + text-align: center; +} diff --git a/client/src/pages/review_dms/review_dms.tsx b/client/src/pages/review_dms/review_dms.tsx new file mode 100644 index 0000000..5075b6c --- /dev/null +++ b/client/src/pages/review_dms/review_dms.tsx @@ -0,0 +1,157 @@ +import clsx from "clsx"; +import { + ActionFunctionArgs, + LoaderFunctionArgs, + redirect, + useLoaderData, +} from "react-router"; +import { createAccountDMsReviewPageURL } from "#lib/urls"; +import { fetchApproveDMs, fetchDMsForReview } from "#api/account/dms"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; +import { DMCard } from "#components/cards"; +import { FormRouter } from "#components/forms"; +import { KemonoLink } from "#components/links"; +import { IUnapprovedDM } from "#entities/dms"; + +import * as styles from "./review_dms.module.scss"; + +interface IProps { + status: "pending" | "ignored"; + dms: IUnapprovedDM[]; +} + +export function DMsReviewPage() { + const { status, dms } = useLoaderData() as IProps; + const title = `Approve DMs`; + const heading = `DM Review`; + + return ( + +

    + + {status === "pending" ? <>Ignored DMs : <>Pending DMs} + +

    + + {!dms || dms.length === 0 ? ( +

    There are no DMs waiting for approval.

    + ) : ( + <> + + {dms.map((dm) => ( +
    + +
    + + +
    +
    + ))} + + {status === "ignored" && ( + + )} + +
    + +
    +
    + + )} +
    + ); +} + +export const loader = createAccountPageLoader(async function loader({ + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let status: undefined | IProps["status"] = undefined; + { + const inputStatus = searchParams.get("status")?.trim() ?? "pending"; + + if (inputStatus !== "pending" && inputStatus !== "ignored") { + throw new Error(`Unknown status "${inputStatus}".`); + } + + status = inputStatus; + } + + const { dms } = await fetchDMsForReview(status); + + return { + status, + dms, + }; +}); + +export async function action({ request }: ActionFunctionArgs) { + try { + if (request.method !== "POST") { + throw new Error(`Unknown method ${request.method}.`); + } + + const formData = await request.formData(); + const approvedHashes = formData.getAll("approved_hashes") as string[]; + const deleteIgnored = + (formData.get("delete_ignored") as string | null)?.trim() === "true"; + + if (approvedHashes.length === 0) { + throw new Error("At least one DM must be provided for approval."); + } + + await fetchApproveDMs(approvedHashes, deleteIgnored); + + return redirect(String(createAccountDMsReviewPageURL())); + } catch (error) { + return error; + } +} diff --git a/client/src/pages/schema.html b/client/src/pages/schema.html deleted file mode 100644 index f7829da..0000000 --- a/client/src/pages/schema.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'components/shell.html' %} -{% block content %} - - -{% endblock %} \ No newline at end of file diff --git a/client/src/pages/search_hash.html b/client/src/pages/search_hash.html deleted file mode 100644 index 5b92c83..0000000 --- a/client/src/pages/search_hash.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "components/shell.html" %} -{% from "components/file_hash_search.html" import search_form %} - -{% block title %} - Search files | {{ g.site_name }} -{% endblock title %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/search_hash.js b/client/src/pages/search_hash.js deleted file mode 100644 index 404875d..0000000 --- a/client/src/pages/search_hash.js +++ /dev/null @@ -1,43 +0,0 @@ -import sha256 from "sha256-wasm"; - -export function searchHashPage() { - const FORM = document.getElementById("file-search"); - const FILE = document.getElementById("file"); - const HASH = document.getElementById("hash"); - - FORM.addEventListener("submit", async function (e) { - - e.preventDefault(); - let hash = undefined; - if (FILE.value !== "") { - hash = await getFileHash(FILE.files[0]); - } else if (HASH.value !== "") { - if (HASH.value.match(/[A-Fa-f0-9]{64}/)) { - hash = HASH.value; - } else { - alert("Invalid SHA256 hash"); - } - } else { - alert("Neither file or hash provided"); - } - - if (hash) { - window.location.search = "?hash=" + hash; - } - }); -} - -async function getFileHash(file) { - const fileSize = file.size; - const chunkSize = 1024 * 1024; // 1Mi - let offset = 0; - let hash = new sha256(); - - while (offset < fileSize) { - const arr = new Uint8Array(await file.slice(offset, chunkSize + offset).arrayBuffer()); - hash.update(arr); - offset += chunkSize; - } - - return hash.digest("hex"); -} diff --git a/client/src/pages/search_hash.module.scss b/client/src/pages/search_hash.module.scss new file mode 100644 index 0000000..af0a7ea --- /dev/null +++ b/client/src/pages/search_hash.module.scss @@ -0,0 +1,21 @@ +.searchForm { + text-align: center; + padding-top: 1em; + padding-bottom: 0.5em; + width: fit-content; + margin-left: auto; + margin-right: auto; + + span { + user-select: none; + } + + .error { + color: red; + } + + button { + width: 100%; + margin-top: 1em; + } +} diff --git a/client/src/pages/search_hash.tsx b/client/src/pages/search_hash.tsx new file mode 100644 index 0000000..785545a --- /dev/null +++ b/client/src/pages/search_hash.tsx @@ -0,0 +1,149 @@ +// TODO: https://github.com/Daninet/hash-wasm probably +// since this one wasn't updated in 3 years +import sha256 from "sha256-wasm"; +import { + createDiscordChannelPageURL, +} from "#lib/urls"; +import { fetchSearchFileByHash } from "#api/files"; +import { PageSkeleton } from "#components/pages"; +import { CardList, PostCard } from "#components/cards"; +import { KemonoLink } from "#components/links"; +import { MouseEvent, useEffect, useState } from "react"; +import { LoadingIcon } from "#components/loading"; +import { useSearchParams } from "react-router"; + +import * as styles from "./search_hash.module.scss"; + +export function SearchFilesPage() { + const [searchParams, setSearchParams] = useSearchParams(); + const initialHash = searchParams.get("hash")?.toLowerCase(); + const title = "Search files"; + const heading = "Search Files"; + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [files, setFiles] = useState(null); + const [hash, setHash] = useState(initialHash ?? ""); + const [result, setResult] = useState> | null>(null); + + function setHashParam(hash: string) { + setSearchParams(params => { + params.set("hash", hash); + return params; + }, { replace: true }); + } + + async function lookup(hash: string) { + setLoading(true); + + try { + if (files?.length) { + let fileHash = await getFileHash(files[0]); + setResult(await fetchSearchFileByHash(fileHash)); + setHash(fileHash); + setHashParam(fileHash); + } else { + if (hash.match(/[a-f0-9]{64}/)) { + setResult(await fetchSearchFileByHash(hash)); + setHash(hash); + setHashParam(hash); + } else { + setError("Invalid hash!"); + } + } + } finally { + setLoading(false); + } + } + + useEffect(() => { + if (initialHash) { + lookup(initialHash); + } + }, []); + + async function submitClicked(event: MouseEvent) { + event.preventDefault(); + await lookup(hash); + } + + return ( + +
    +
    + + setFiles(e.target.files)} /> +
    + +
    + +
    + —or— +
    +
    +
    + +
    + + setHash(e.target.value.toLowerCase())} value={hash} /> +
    + +
    + {error} +
    + + +
    + + {loading ? : result?.posts?.length ? ( + + {result.posts.map((post, index) => ( + + ))} + + ) : result?.discord_posts?.length ? ( + <> +

    Discord

    + {result.discord_posts.map((post, index) => ( +

    + + Server {post.server} channel {post.channel} + +

    + ))} + + ) : ( +
    +

    + Nobody here but us chickens! +

    +

    There are no posts for your query.

    +
    + )} +
    + ); +} + +async function getFileHash(file: File) { + const fileSize = file.size; + const chunkSize = 1024 * 1024; // 1Mi + let offset = 0; + let hash = new sha256(); + + while (offset < fileSize) { + const arr = new Uint8Array( + await file.slice(offset, chunkSize + offset).arrayBuffer() + ); + hash.update(arr); + offset += chunkSize; + } + + return hash.digest("hex"); +} diff --git a/client/src/pages/search_results.html b/client/src/pages/search_results.html deleted file mode 100644 index b2195bd..0000000 --- a/client/src/pages/search_results.html +++ /dev/null @@ -1,41 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/card_list.html" import card_list %} -{% from "components/cards/post.html" import post_card %} -{% from "components/file_hash_search.html" import search_form %} - -{% block title %} - File search results | {{ g.site_name }} -{% endblock title %} - -{% block meta %} - -{% endblock meta %} - -{% block content %} - -{% endblock content %} diff --git a/client/src/pages/search_results.js b/client/src/pages/search_results.js deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/pages/share.html b/client/src/pages/share.html deleted file mode 100644 index 7515bb0..0000000 --- a/client/src/pages/share.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/site_section.html' import site_section, site_section_header %} - -{% block scripts_extra %} - - -{% endblock scripts_extra %} - -{% block content %} -{% call site_section('upload') %} -
    - {{ site_section_header(share.name) }} -
    {{ share.description }}
    - {% for file in share_files %} -
  • - - Download {{ file['filename'] }} - -
  • - {% endfor %} -
    -{% endcall %} -{% endblock %} diff --git a/client/src/pages/share.tsx b/client/src/pages/share.tsx new file mode 100644 index 0000000..6946fbd --- /dev/null +++ b/client/src/pages/share.tsx @@ -0,0 +1,54 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createFileURL } from "#lib/urls"; +import { fetchShare } from "#api/shares"; +import { PageSkeleton } from "#components/pages"; +import { IShare, IShareFile } from "#entities/files"; + +interface IProps { + share: IShare; + files: IShareFile[]; +} + +export function SharePage() { + const { share, files } = useLoaderData() as IProps; + const title = `Share "${share.name}"`; + const heading = `Share "${share.name}"`; + + return ( + +
    +
    {share.description}
    + + {files.map((file) => ( +
  • + + Download {file.filename} + +
  • + ))} +
    +
    + ); +} + +export async function loader({ params }: LoaderFunctionArgs): Promise { + const shareID = params.share_id?.trim(); + + if (!shareID) { + throw new Error("Share ID is required."); + } + + const { share, share_files } = await fetchShare(shareID); + + return { + share, + files: share_files, + }; +} diff --git a/client/src/pages/shares-all.tsx b/client/src/pages/shares-all.tsx new file mode 100644 index 0000000..2019774 --- /dev/null +++ b/client/src/pages/shares-all.tsx @@ -0,0 +1,75 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createSharesPageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { fetchShares } from "#api/shares"; +import { PageSkeleton } from "#components/pages"; +import { Paginator } from "#components/pagination"; +import { CardList, ShareCard } from "#components/cards"; +import { IShare } from "#entities/files"; + +interface IProps { + count: number; + offset?: number; + shares: IShare[]; +} + +export function SharesPage() { + const { count, offset, shares } = useLoaderData() as IProps; + const title = "Filehaus"; + const heading = "Filehaus"; + + return ( + +
    + String(createSharesPageURL(offset))} + /> +
    + + + {count === 0 ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no uploads.

    +
    + ) : ( + shares.map((share) => ) + )} +
    + +
    + String(createSharesPageURL(offset))} + /> +
    +
    + ); +} + +export async function loader({ request }: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + + let offset: number | undefined = undefined; + { + const inputOffset = searchParams.get("o")?.trim(); + + if (inputOffset) { + offset = parseOffset(inputOffset); + } + } + + const { props } = await fetchShares(offset); + const { count, shares } = props; + + return { + offset, + count, + shares, + }; +} diff --git a/client/src/pages/shares.html b/client/src/pages/shares.html deleted file mode 100644 index d46faec..0000000 --- a/client/src/pages/shares.html +++ /dev/null @@ -1,49 +0,0 @@ -{% extends 'components/shell.html' %} - -{% import 'components/site.html' as site %} -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/share.html' import share_card %} - -{% block content %} -{% call site.section("all-dms", title="Filehaus") %} -
    - {% include 'components/paginator.html' %} - {# - - #} -
    - - {% call card_list("phone") %} - {% for dm in props.shares %} - {{ share_card(dm) }} - {% else %} -
    -

    Nobody here but us chickens!

    -

    - There are no uploads. -

    -
    - {% endfor %} - {% endcall %} - -
    - {% include 'components/paginator.html' %} -
    -{% endcall %} -{% endblock %} diff --git a/client/src/pages/shares.tsx b/client/src/pages/shares.tsx new file mode 100644 index 0000000..64d1cf7 --- /dev/null +++ b/client/src/pages/shares.tsx @@ -0,0 +1,107 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { createProfilesSharesPageURL } from "#lib/urls"; +import { parseOffset } from "#lib/pagination"; +import { fetchProfileShares } from "#api/shares"; +import { PageSkeleton } from "#components/pages"; +import { Paginator } from "#components/pagination"; +import { CardList, ShareCard } from "#components/cards"; +import { validatePaysite } from "#entities/paysites"; +import { IShare } from "#entities/files"; + +interface IProps { + service: string; + profileID: string; + offset?: number; + count: number; + shares: IShare[]; +} + +export function ProfileSharesPage() { + const { service, profileID, count, offset, shares } = + useLoaderData() as IProps; + const title = "Filehaus"; + const heading = "Filehaus"; + + return ( + +
    + + String(createProfilesSharesPageURL(service, profileID, offset)) + } + /> +
    + + + {!count ? ( +
    +

    + Nobody here but us chickens! +

    +

    There are no uploads.

    +
    + ) : ( + shares.map((share) => ) + )} +
    + +
    + + String(createProfilesSharesPageURL(service, profileID, offset)) + } + /> +
    +
    + ); +} + +export async function loader({ + params, + request, +}: LoaderFunctionArgs): Promise { + const searchParams = new URL(request.url).searchParams; + const service = params.service?.trim(); + { + if (!service) { + throw new Error("Service name is required."); + } + + validatePaysite(service); + } + + const profileID = params.creator_id?.trim(); + { + if (!profileID) { + throw new Error("Artist ID is required."); + } + } + + let offset: number | undefined = undefined; + { + const inputOffset = searchParams.get("o")?.trim(); + + if (inputOffset) { + offset = parseOffset(inputOffset); + } + } + + const { results, props } = await fetchProfileShares( + service, + profileID, + offset + ); + const { share_count } = props; + + return { + service, + profileID, + shares: results, + count: share_count, + offset, + }; +} diff --git a/client/src/pages/success.html b/client/src/pages/success.html deleted file mode 100644 index eb5aa8a..0000000 --- a/client/src/pages/success.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends 'components/shell.html' %} - -{% block content %} -

    {{ props.message or 'Success!' }}

    - {% if props.redirect %} - - {% endif %} -{% endblock %} diff --git a/client/src/pages/swagger_schema.html b/client/src/pages/swagger_schema.html deleted file mode 100644 index aa38187..0000000 --- a/client/src/pages/swagger_schema.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - SwaggerUI - - - -
    - - - - - \ No newline at end of file diff --git a/client/src/pages/tags-all.tsx b/client/src/pages/tags-all.tsx new file mode 100644 index 0000000..cccba09 --- /dev/null +++ b/client/src/pages/tags-all.tsx @@ -0,0 +1,36 @@ +import { LoaderFunctionArgs, useLoaderData } from "react-router"; +import { PageSkeleton } from "#components/pages"; +import { ITag } from "#entities/tags"; +import { createTagPageURL } from "#lib/urls"; +import { fetchTags } from "#api/tags"; + +interface IProps { + tags: ITag[]; +} + +export function TagsPage() { + const { tags } = useLoaderData() as IProps; + const title = "All tags"; + const heading = "All Tags"; + + return ( + +
    + {tags.map((tag) => ( + + ))} +
    +
    + ); +} + +export async function loader({}: LoaderFunctionArgs): Promise { + const { tags } = await fetchTags(); + + return { tags }; +} diff --git a/client/src/pages/tags.html b/client/src/pages/tags.html deleted file mode 100644 index decf5b2..0000000 --- a/client/src/pages/tags.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends "components/shell.html" %} - -{% from "components/headers.html" import user_header %} - -{% block title %} - Tags | {{g.site_name}} -{% endblock %} - -{% block content %} - -{% endblock %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} diff --git a/client/src/pages/updated.html b/client/src/pages/updated.html deleted file mode 100644 index 09520f1..0000000 --- a/client/src/pages/updated.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/user.html' import user_card, user_card_header %} - -{% block content %} -
    - {% if results|length %} -
    - {% include 'components/paginator.html' %} -
    - {% endif %} - {% call card_list('phone') %} - {% for user in results %} - {{ user_card(user, is_date=true) }} - {% else %} -

    - No {{ g.artists_or_creators|lower }} found. -

    - {% endfor %} - {% endcall %} - {% if results|length %} -
    - {% include 'components/paginator.html' %} -
    - {% endif %} -
    -{% endblock %} diff --git a/client/src/pages/updated.js b/client/src/pages/updated.js deleted file mode 100644 index 4ae8559..0000000 --- a/client/src/pages/updated.js +++ /dev/null @@ -1,23 +0,0 @@ -import { CardList, registerPaginatorKeybinds } from "@wp/components"; -import { isLoggedIn } from "@wp/js/account"; -import { findFavouriteArtist } from "@wp/js/favorites"; - -/** - * @param {HTMLElement} section - */ -export async function updatedPage(section) { - registerPaginatorKeybinds(); - - const cardListElement = section.querySelector(".card-list"); - const { cardContainer } = CardList(cardListElement); - - for await (const userCard of cardContainer.children) { - const { id, service } = userCard.dataset; - - const isFaved = isLoggedIn && (await findFavouriteArtist(id, service)); - - if (isFaved) { - userCard.classList.add("user-card--fav"); - } - } -} diff --git a/client/src/pages/upload.html b/client/src/pages/upload.html deleted file mode 100644 index ea74e65..0000000 --- a/client/src/pages/upload.html +++ /dev/null @@ -1,87 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/site_section.html' import site_section, site_section_header %} - -{% block scripts_extra %} - - -{% endblock scripts_extra %} - -{% block content %} -{% call site_section('upload') %} -
    - {{ site_section_header('Upload file') }} -
    -
    - {% if request.args.get('service') and request.args.get('user') %} - - - {% else %} - {# #} - {% endif %} -
    -
    - - - - example, "February 2020 Rewards" - -
    -
    - - - - Specify what the file/archive is, where the original data can be found, include relevant keys/passwords, etc. - -
    -
      -
    -
    - Add files -
    -
    - -
    -
    -
    -
    -
    -{% endcall %} -{% endblock %} diff --git a/client/src/pages/upload.js b/client/src/pages/upload.js deleted file mode 100644 index a8a6975..0000000 --- a/client/src/pages/upload.js +++ /dev/null @@ -1,57 +0,0 @@ -import Dashboard from "@uppy/dashboard"; -import Form from "@uppy/form"; -import Uppy from "@uppy/core"; -import Tus from "@uppy/tus"; - -import "@uppy/dashboard/dist/style.min.css"; -import "@uppy/core/dist/style.min.css"; - -// import "@wp/js/resumable"; - -/** - * @param {HTMLElement} section - */ -export async function uploadPage(section) { - Array.from(document.getElementsByTagName("textarea")).forEach((tx) => { - function onTextareaInput() { - this.style.height = "auto"; - this.style.height = this.scrollHeight + "px"; - } - tx.setAttribute("style", "height:" + tx.scrollHeight + "px;overflow-y:hidden;"); - tx.addEventListener("input", onTextareaInput, false); - }); - - const uppy = new Uppy({ - restrictions: { - maxTotalFileSize: 2 * 1024 * 1024 * 1024, - maxNumberOfFiles: 10, - minNumberOfFiles: 1, - }, - }) - .use(Dashboard, { - note: "Up to 10 files permitted.", - fileManagerSelectionType: "both", - target: "#upload", - // inline: true, - inline: false, - trigger: "#upload-button", - theme: "dark", - }) - .use(Tus, { - // endpoint: 'https://tusd.tusdemo.net/files/', - endpoint: "http://localhost:1080/files/", - retryDelays: [0, 1000, 3000, 5000], - }) - .use(Form, { - resultName: "uppyResult", - target: "#upload-form", - submitOnSuccess: false, - }); - - uppy.on("complete", ({ successful }) => { - successful.forEach((file) => { - const fileList = document.getElementById("file-list"); - fileList.innerHTML += `
  • ${file.meta.name}
  • `; - }); - }); -} diff --git a/client/src/pages/upload.tsx b/client/src/pages/upload.tsx new file mode 100644 index 0000000..bac8d70 --- /dev/null +++ b/client/src/pages/upload.tsx @@ -0,0 +1,179 @@ +import { useEffect, useState } from "react"; +import { + ActionFunctionArgs, + LoaderFunctionArgs, + useLoaderData, +} from "react-router"; +import Uppy from "@uppy/core"; +import Dashboard from "@uppy/dashboard"; +import Form from "@uppy/form"; +import Tus from "@uppy/tus"; +import { FormRouter, FormSection } from "#components/forms"; +import { InputHidden } from "#components/forms/inputs"; +import { PageSkeleton, createAccountPageLoader } from "#components/pages"; + +import "@uppy/dashboard/dist/style.min.css"; +import "@uppy/core/dist/style.min.css"; + +interface IProps { + service?: string; + profileID?: string; +} + +export function Component() { + const { service, profileID } = useLoaderData() as IProps; + const [fileList, changeFileList] = useState(); + const title = "Upload file"; + const heading = "Upload File"; + + useEffect(() => { + (async () => { + try { + const uppy = new Uppy({ + restrictions: { + maxTotalFileSize: 2 * 1024 * 1024 * 1024, + maxNumberOfFiles: 10, + minNumberOfFiles: 1, + }, + }) + .use(Dashboard, { + note: "Up to 10 files permitted.", + fileManagerSelectionType: "both", + target: "#upload", + // inline: true, + inline: false, + trigger: "#upload-button", + theme: "dark", + }) + .use(Tus, { + // endpoint: 'https://tusd.tusdemo.net/files/', + endpoint: "http://localhost:1080/files/", + retryDelays: [0, 1000, 3000, 5000], + }) + .use(Form, { + resultName: "uppyResult", + target: "#upload-form", + submitOnSuccess: false, + }); + + uppy.on("complete", ({ successful }) => { + const files = successful?.map( + (file) => file.meta.name + ); + + changeFileList(files); + }); + } catch (error) { + console.error(error); + } + })(); + }, []); + + return ( + +
    + "Finish"} + > + + {Boolean(service && profileID) && ( + <> + + + + )} + + + + + + example, "February 2020 Rewards" + + + + + + + Specify what the file/archive is, where the original data can be + found, include relevant keys/passwords, etc. + + + +
      + {fileList?.map((name, index) => ( +
    • {name as string}
    • + ))} +
    + +
    + Add files +
    +
    + +
    +
    +
    + ); +} + +Component.displayName = "PostsUploadPage"; + +export const loader = createAccountPageLoader(async function loader({ + request, +}: LoaderFunctionArgs): Promise { + throw new Error("Not implemented"); + const searchParams = new URL(request.url).searchParams; + + const service = searchParams.get("service")?.trim(); + const profileID = searchParams.get("user")?.trim(); + + return { + service, + profileID, + }; +}); + +export async function action({ request }: ActionFunctionArgs) { + throw new Error("Not implemented"); + if (request.method !== "POST") { + throw new Error(`Unknown method "${request.method}".`); + } + + const formData = await request.formData(); + + const service = (formData.get("service") as string | null)?.trim(); + const profileID = (formData.get("user") as string | null)?.trim(); + const content = (formData.get("content") as string | null)?.trim(); +} diff --git a/client/src/pages/user.html b/client/src/pages/user.html deleted file mode 100644 index be968d5..0000000 --- a/client/src/pages/user.html +++ /dev/null @@ -1,91 +0,0 @@ -{% extends 'components/shell.html' %} - -{% from 'components/ads.html' import slider_ad, header_ad, footer_ad %} -{% from 'components/headers.html' import user_header %} -{% from "components/loading_icon.html" import loading_icon %} -{% from 'components/card_list.html' import card_list %} -{% from 'components/cards/post.html' import post_card %} - -{% set paysite = g.paysites[props.service] %} -{% set page_title = 'Posts of ' ~ props.name ~ ' from ' ~ paysite.title ~ ' | ' ~ g.site_name %} - -{% block title %} - {{ page_title }} -{% endblock title %} - -{% block meta %} - - - - -{% endblock meta %} - -{% block opengraph %} - - - - - -{% endblock opengraph %} - -{% block content %} -{{ slider_ad() }} -
    - {{ user_header(request, props) }} -
    - {% include 'components/tabs.html' %} - {% if results or request.args.get('q') %} - {% include 'components/paginator.html' %} -
    - - -
    - {% endif %} -
    - {% if results or request.args.get('q') %} - {{ header_ad() }} - - {% call card_list() %} - {% for post in results %} - {{ post_card(post) }} - {% endfor %} - {% endcall %} - - {{ footer_ad() }} - -
    - {% include 'components/paginator.html' %} -
    - {% endif %} - {% if not results %} -
    -

    Nobody here but us chickens!

    -

    - There are no posts for your query. -

    -
    - {% endif %} - -
    -{% endblock content %} - -{% block components %} - - {{ loading_icon() }} -{% endblock components %} diff --git a/client/src/pages/user.js b/client/src/pages/user.js deleted file mode 100644 index b27401e..0000000 --- a/client/src/pages/user.js +++ /dev/null @@ -1,122 +0,0 @@ -import { addFavouriteArtist, findFavouriteArtist, findFavouritePost, removeFavouriteArtist } from "@wp/js/favorites"; -import { CardList, PostCard, registerMessage, registerPaginatorKeybinds, showTooltip } from "@wp/components"; -import { createComponent } from "@wp/js/component-factory"; -import { isLoggedIn } from "@wp/js/account"; - -/** - * @param {HTMLElement} section - */ -export async function userPage(section) { - registerPaginatorKeybinds(); - - const artistID = document.head.querySelector("[name='id']")?.content; - const artistService = document.head.querySelector("[name='service']")?.content; - /** - * @type {HTMLElement} - */ - const buttonsPanel = section.querySelector(".user-header__actions"); - const cardListElement = section.querySelector(".card-list"); - - document.styleSheets[0].insertRule(".post-card__footer > div > img { display: none; }", 0); - await initButtons(buttonsPanel, artistID, artistService); - const urlPath = document.location.pathname; - if (urlPath.includes("fancards") ||urlPath.includes("tags")) return; - if (cardListElement) { - await initCardList(cardListElement); - } -} - -/** - * @param {HTMLElement} panelElement - * @param {string} artistID - * @param {string} artistService - */ -async function initButtons(panelElement, artistID, artistService) { - /** - * @type {HTMLButtonElement} - */ - const favButton = createComponent("user-header__favourite"); - if (!favButton) return; - const favItem = await findFavouriteArtist(artistID, artistService); - if (localStorage.getItem("logged_in") && favItem) { - favButton.classList.add("user-header__favourite--unfav"); - const [icon, text] = favButton.children; - icon.textContent = "★"; - text.textContent = "Unfavorite"; - } - - favButton.addEventListener("click", handleFavouriting(artistID, artistService)); - - panelElement?.appendChild(favButton); -} - -/** - * @param {HTMLElement} cardListElement - */ -async function initCardList(cardListElement) { - const { cardItems } = CardList(cardListElement); - - cardItems.forEach(async (card) => { - const { postID, userID, service } = PostCard(card); - const favPost = isLoggedIn && (await findFavouritePost(service, userID, postID)); - - if (favPost) { - card.classList.add("post-card--fav"); - } - }); -} - -/** - * @param {string} id - * @param {string} service - * @returns {(event: MouseEvent) => Promise} - */ -function handleFavouriting(id, service) { - return async (event) => { - /** - * @type {HTMLButtonElement} - */ - const button = event.target; - - if (!isLoggedIn) { - showTooltip(button, registerMessage(null, "Favoriting")); - return; - } - - const [icon, text] = button.children; - /** - * @type {HTMLElement} - */ - const loadingIcon = createComponent("loading-icon"); - - button.disabled = true; - button.classList.add("user-header__favourite--loading"); - button.insertBefore(loadingIcon, text); - - try { - if (button.classList.contains("user-header__favourite--unfav")) { - const isRemoved = await removeFavouriteArtist(id, service); - - if (isRemoved) { - button.classList.remove("user-header__favourite--unfav"); - icon.textContent = "☆"; - text.textContent = "Favorite"; - } - } else { - const isAdded = await addFavouriteArtist(id, service); - - if (isAdded) { - button.classList.add("user-header__favourite--unfav"); - icon.textContent = "★"; - text.textContent = "Unfavorite"; - } - } - } catch (error) { - console.error(error); - } finally { - loadingIcon.remove(); - button.disabled = false; - button.classList.remove("user-header__favourite--loading"); - } - }; -} diff --git a/client/src/router.tsx b/client/src/router.tsx new file mode 100644 index 0000000..34acc26 --- /dev/null +++ b/client/src/router.tsx @@ -0,0 +1,17 @@ +import { createBrowserRouter } from "react-router"; + +import { routes } from "./routes"; + +export const router = createBrowserRouter( + routes, + { + future: { + v7_relativeSplatPath: true, + v7_normalizeFormMethod: true, + v7_startTransition: true, + v7_fetcherPersist: true, + v7_partialHydration: true, + v7_skipActionErrorRevalidation: true, + }, + } +); diff --git a/client/src/routes.tsx b/client/src/routes.tsx new file mode 100644 index 0000000..f856872 --- /dev/null +++ b/client/src/routes.tsx @@ -0,0 +1,438 @@ +import { Layout } from "#components/layout"; +import { ErrorPage } from "#components/pages"; +import { HomePage } from "#pages/home"; +import { ImporterTutorialPage } from "#pages/importer/importer_tutorial"; +import { ImporterTutorialFanboxPage } from "#pages/importer/importer_tutorial_fanbox"; +import { ImporterPage } from "#pages/importer/importer_list"; +import { SearchFilesPage } from "#pages/search_hash"; +import { ImporterOKPage } from "#pages/importer/importer_ok"; +import { + AdministratorDashboardPage, + loader as administratorDashboardPageLoader, +} from "#pages/account/administrator/dashboard"; +import { + AccountLoginPage, + action as accountLoginPageAction, +} from "#pages/account/login"; +import { ArtistsPage, loader as artistsPageLoader } from "#pages/profiles"; +import { + ArtistsUpdatedPage, + loader as artistsUpdatedPageLoader, +} from "#pages/profiles/updated"; +import { loader as artistRandomPageLoader } from "#pages/profiles/random"; +import { ProfilePage, loader as profilePageLoader } from "#pages/profile"; +import { + ProfileTagsPage, + loader as profileTagsPageLoader, +} from "#pages/profile/tags"; +import { + FancardsPage, + loader as fancardsLoader, +} from "#pages/profile/fancards"; +import { + ProfileSharesPage, + loader as profileSharesPageLoader, +} from "#pages/shares"; +import { + ProfileDMsPage, + loader as profileDMsPageLoader, +} from "#pages/profile/dms"; +import { + AnnouncementsPage, + loader as announcementsPageLoader, +} from "#pages/profile/announcements"; +import { + ProfileLinksPage, + loader as profileLinksPageLoader, +} from "#pages/profile/linked_accounts"; +import { + NewProfileLinkPage, + loader as newProfileLinkPageLoader, + action as newProfileLinkPageAction, +} from "#pages/profile/new-linked-profile"; +import { DMsPage, loader as dmsPageLoader } from "#pages/all_dms"; +import { loader as accountFavoritesPageLoader } from "#pages/favorites"; +import { SharePage, loader as sharePageLoader } from "#pages/share"; +import { SharesPage, loader as sharesPageLoader } from "#pages/shares-all"; +import { PostPage, loader as postPageLoader } from "#pages/post"; +import { loader as postPageDataLoader } from "#pages/post/data"; +import { PostsPage, loader as postsPageLoader } from "#pages/posts"; +import { + PopularPostsPage, + loader as popularPostsPageLoader, +} from "#pages/posts/popular"; +import { TagsPage, loader as tagsPageLoader } from "#pages/tags-all"; +import { + DiscordServerPage, + loader as discordServerPageLoader, +} from "#pages/discord"; +import { + DiscordChannelPage, + loader as discordChannelPageLoader, +} from "#pages/discord-channel"; +import { + ArchiveFilePage, + loader as archiveFilePageLoader, +} from "#pages/file/archive"; +import { loader as postRandomPageLoader } from "#pages/posts/random"; +import { + DMsReviewPage, + loader as dmsReviewPageLoader, + action as dmsReviewPageAction, +} from "#pages/review_dms/review_dms"; +import { + PostRevisionPage, + loader as postRevisionPageLoader, +} from "#pages/post-revision"; +import { + ImporterStatusPage, + loader as importerStatusPageLoader, +} from "#pages/importer/importer_status"; +import { AccountPage, loader as accountPageLoader } from "#pages/account/home"; +import { + AccountNotificationsPage, + loader as accountNotificationsPageLoader, +} from "#pages/account/notifications"; +import { + AccountAutoImportKeysPage, + loader as accountAutoImportKeysPageLoader, + action as accountAutoImportKeysPageAction, +} from "#pages/account/keys"; +import { + AccountChangePasswordPage, + loader as accountChangePasswordPageLoader, + action as accountChangePasswordPageAction, +} from "#pages/account/change_password"; +import { + AdministratorAccountsPage, + loader as administratorAccountsPageLoader, + baseLoader as administratorAccountsPageBaseLoader, +} from "#pages/account/administrator/accounts"; +import { + ModeratorDashboardPage, + loader as moderatorDashboardPageLoader, +} from "#pages/account/moderator/dashboard"; +import { + ProfileLinkRequestsPage, + loader as profileLinkRequestsPageLoader, +} from "#pages/account/moderator/profile_links"; +import { + RegisterPage, + action as registerPageAction, +} from "#pages/account/register"; +import { loader as accountLogoutPageLoader } from "#pages/authentication/logout"; +import { loader as favoritesLegacyPageLoader } from "#pages/account/favorites/legacy"; +import { + FavoriteProfilesPage, + loader as favoritesProfilesPageLoader, +} from "#pages/account/favorites/profiles"; +import { + FavoritePostsPage, + loader as favoritesPostsPageLoader, +} from "#pages/account/favorites/posts"; +import { Compliance2257Page } from "#pages/2257"; +import { ContactPage } from "#pages/contact"; +import { DMCAPage } from "#pages/dmca"; +import { FanboxImportsPage } from "#pages/fanboximports"; +import { GumroadAndCoPage } from "#pages/gumroad-and-co"; +import { MatrixPage } from "#pages/matrix"; +import { + AdministratorAccountOverviewPage, + loader as administratorAccountOverviewPageLoader, + action as administratorAccountOverviewPageAction, +} from "#pages/account/administrator/account"; +import { + LegacyFilePage, + loader as legacyFilePageLoader, +} from "#pages/file/legacy"; +import { Error404 } from "#pages/errors/404"; + +export const routes = [ + { + path: "/", + element: , + errorElement: , + children: [ + { + errorElement: , + children: [ + { index: true, element: }, + { + path: "/2257", + element: , + }, + { + path: "/contact", + element: , + }, + { + path: "/dmca", + element: , + }, + { + path: "/fanboximports", + element: , + }, + { + path: "/gumroad-and-co", + element: , + }, + { + path: "/matrix", + element: , + }, + { + path: "/importer", + element: , + }, + { + path: "/importer/ok", + element: , + }, + { + path: "/importer/tutorial", + element: , + }, + { + path: "/importer/tutorial_fanbox", + element: , + }, + { + path: "/importer/status/:import_id", + element: , + loader: importerStatusPageLoader, + }, + { + path: "/search_hash", + element: , + }, + { + path: "/artists", + element: , + loader: artistsPageLoader, + }, + { + path: "/artists/updated", + element: , + loader: artistsUpdatedPageLoader, + }, + { + path: "/artists/random", + loader: artistRandomPageLoader, + }, + { + path: "/posts", + element: , + loader: postsPageLoader, + }, + { + path: "/posts/popular", + element: , + loader: popularPostsPageLoader, + }, + { + path: "/posts/tags", + element: , + loader: tagsPageLoader, + }, + { + path: "/posts/archives/:file_hash", + element: , + loader: legacyFilePageLoader, + }, + { + path: "/file/:file_hash", + element: , + loader: archiveFilePageLoader, + }, + { + path: "/posts/random", + loader: postRandomPageLoader, + }, + { + path: "/favorites", + loader: favoritesLegacyPageLoader, + }, + { + path: "/discord/server/:server_id", + element: , + loader: discordServerPageLoader, + }, + { + path: "/discord/server/:server_id/:channel_id", + element: , + loader: discordChannelPageLoader, + }, + { + path: "/:service/user/:creator_id", + element: , + loader: profilePageLoader, + }, + { + path: "/:service/user/:creator_id/tags", + element: , + loader: profileTagsPageLoader, + }, + { + path: "/:service/user/:creator_id/fancards", + element: , + loader: fancardsLoader, + }, + { + path: "/:service/user/:creator_id/shares", + element: , + loader: profileSharesPageLoader, + }, + { + path: "/:service/user/:creator_id/dms", + element: , + loader: profileDMsPageLoader, + }, + { + path: "/:service/user/:creator_id/announcements", + element: , + loader: announcementsPageLoader, + }, + { + path: "/:service/user/:creator_id/links", + element: , + loader: profileLinksPageLoader, + }, + { + path: "/:service/user/:creator_id/post/:post_id", + element: , + loader: postPageLoader, + }, + { + path: "/:service/user/:creator_id/post/:post_id/revision/:revision_id", + element: , + loader: postRevisionPageLoader, + }, + { + path: "/:service/post/:post_id", + loader: postPageDataLoader, + }, + { + path: "/dms", + element: , + loader: dmsPageLoader, + }, + { + path: "/shares", + element: , + loader: sharesPageLoader, + }, + { + path: "/share/:share_id", + element: , + loader: sharePageLoader, + }, + { + path: "/documentation/api", + lazy: () => import("#pages/documentation/api"), + }, + { + path: "/authentication/register", + element: , + action: registerPageAction, + }, + { + path: "/authentication/login", + element: , + action: accountLoginPageAction, + }, + { + path: "/authentication/logout", + loader: accountLogoutPageLoader, + }, + { + path: "/account", + element: , + loader: accountPageLoader, + }, + { + path: "/account/favorites", + loader: accountFavoritesPageLoader, + }, + { + path: "/account/favorites/artists", + element: , + loader: favoritesProfilesPageLoader, + }, + { + path: "/account/favorites/posts", + element: , + loader: favoritesPostsPageLoader, + }, + { + path: "/account/notifications", + element: , + loader: accountNotificationsPageLoader, + }, + { + path: "/account/keys", + element: , + loader: accountAutoImportKeysPageLoader, + action: accountAutoImportKeysPageAction, + }, + { + path: "/account/change_password", + element: , + loader: accountChangePasswordPageLoader, + action: accountChangePasswordPageAction, + }, + { + path: "/account/:service/user/:creator_id/links/new", + element: , + loader: newProfileLinkPageLoader, + action: newProfileLinkPageAction, + }, + { + path: "/account/posts/upload", + lazy: () => import("#pages/upload"), + }, + { + path: "/account/review_dms", + element: , + loader: dmsReviewPageLoader, + action: dmsReviewPageAction, + }, + { + path: "/account/moderator", + element: , + loader: moderatorDashboardPageLoader, + }, + { + path: "/account/moderator/tasks/creator_links", + element: , + loader: profileLinkRequestsPageLoader, + }, + { + path: "/account/administrator", + element: , + loader: administratorDashboardPageLoader, + }, + { + path: "/account/administrator/accounts", + loader: administratorAccountsPageBaseLoader, + }, + { + path: "/account/administrator/accounts/:page", + element: , + loader: administratorAccountsPageLoader, + }, + { + path: "/account/administrator/account/:account_id", + element: , + loader: administratorAccountOverviewPageLoader, + action: administratorAccountOverviewPageAction, + }, + { + path: "/*", + element: , + } + ], + }, + ], + }, +]; diff --git a/client/src/templates/page.html b/client/src/templates/page.html deleted file mode 100644 index 66555e3..0000000 --- a/client/src/templates/page.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends 'components/shell.html' %} - -{% import 'components/site.html' as site %} - -{% set page_title = 'Heading | ' ~ g.site_name %} - -{% block title %} - - {{ page_title }} - -{% endblock title %} - -{% block content %} -{% call site.section('modifier', 'Heading') %} - -{% endcall %} -{% endblock content %} diff --git a/client/src/types/global.d.ts b/client/src/types/global.d.ts deleted file mode 100644 index b50a491..0000000 --- a/client/src/types/global.d.ts +++ /dev/null @@ -1,93 +0,0 @@ -interface KemonoAPI { - favorites: KemonoAPI.Favorites; - posts: KemonoAPI.Posts; - api: KemonoAPI.API; - dms: KemonoAPI.dms; -} - -namespace KemonoAPI { - interface Post { - id: string; - service: string; - title: string; - user: string; - added: string; - published: string; - attachments: string[]; - content: string; - edited: null; - embed: {}; - file: {}; - shared_file: boolean; - faved_seq?: number; - } - - interface User { - id: string; - name: string; - service: string; - indexed: string; - updated: string; - faved_seq?: number; - } - - interface Favorites { - retrieveFavoriteArtists: () => Promise; - favoriteArtist: (service: string, id: string) => Promise; - unfavoriteArtist: (service: string, id: string) => Promise; - retrieveFavoritePosts: () => Promise; - favoritePost: (service: string, user: string, post_id: string) => Promise; - unfavoritePost: (service: string, user: string, post_id: string) => Promise; - } - - namespace Favorites { - interface User extends KemonoAPI.User {} - - interface Post { - id: string; - service: string; - user: string; - } - } - - interface dms { - retrieveHasPendingDMs: () => Promise; - } - interface Posts { - attemptFlag: (service: string, user: string, post_id: string) => Promise; - } - - interface API { - bans: () => Promise; - bannedArtist: (id: string, service: string) => Promise; - creators: () => Promise; - logs: (importID: string) => Promise; - } - - namespace API { - interface BanItem { - id: string; - service: string; - } - - interface BannedArtist { - name: string; - } - - interface LogItem {} - } -} - -namespace Events { - interface Click { - (event: MouseEvent): void; - } - - interface NavClick { - (event: NavClickEvent): void; - } - - interface NavClickEvent extends MouseEvent { - target: HTMLButtonElement; - } -} diff --git a/client/src/utils/_index.js b/client/src/utils/_index.js deleted file mode 100644 index 903d953..0000000 --- a/client/src/utils/_index.js +++ /dev/null @@ -1,217 +0,0 @@ -export { KemonoError } from "./kemono-error"; - -const defaultDelay = parseInt(document.documentElement.style.getPropertyValue("--duration-global")); - -/** - * @param {string} name - * @param {string} url - * @returns - */ -function getParameterByName(name, url) { - if (!url) url = window.location.href; - name = name.replace(/[[]]/g, "\\$&"); - var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"); - var results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ""; - return decodeURIComponent(results[2].replace(/\+/g, " ")); -} - -/** - * @param {() => void} func - * @param {number} wait - * @param {boolean} immediate - * @returns {void} - */ -function debounce(func, wait, immediate) { - let timeout; - return function () { - var context = this; - var args = arguments; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - - function later() { - timeout = null; - if (!immediate) func.apply(context, args); - } - }; -} - -/** - * @param {number} time - * @returns - */ -export function setTimeoutAsync(time = defaultDelay) { - const timeOut = new Promise((resolve) => { - setTimeout(resolve, time); - }); - return timeOut; -} - -/** - * Iterate over the list of images - * and add `image_link` class - * if they are a descendant of an `a` element - * and don't have that class already. - * @param {HTMLImageElement[] | HTMLCollectionOf} imageElements - */ -export function fixImageLinks(imageElements) { - const images = Array.from(imageElements); - - images.forEach((image) => { - const link = image.closest("a"); - - if ( - link && - // && !image.nextSibling - // && !image.previousSibling - // TODO: fix this later - !link.classList.contains("user-header__profile") && - !link.classList.contains("user-card") && - !link.classList.contains("image-link") && - !link.classList.contains("global-sidebar-entry-item") - ) { - link.classList.add("image-link"); - } - }); -} - -/** - * @type {{[paysite:string]: {title: string, color: string, user: { profile: (userID: string) => string }, post: {}}}} - */ -export const paysites = { - patreon: { - title: "Patreon", - color: "#fa5742", - user: { - profile: (userID) => `https://www.patreon.com/user?u=${userID}`, - }, - post: {}, - }, - fanbox: { - title: "Pixiv Fanbox", - color: "#2c333c", - user: { - profile: (userID) => `https://www.pixiv.net/fanbox/creator/${userID}`, - }, - post: {}, - }, - subscribestar: { - title: "SubscribeStar", - color: "#009688", - user: { - profile: (userID) => `https://subscribestar.adult/${userID}`, - }, - post: {}, - }, - gumroad: { - title: "Gumroad", - color: "#2b9fa4", - user: { - profile: (userID) => `https://gumroad.com/${userID}`, - }, - post: {}, - }, - discord: { - title: "Discord", - color: "#5165f6", - user: { - profile: (userID) => ``, - }, - post: {}, - }, - dlsite: { - title: "DLsite", - color: "#052a83", - user: { - profile: (userID) => `https://www.dlsite.com/eng/circle/profile/=/maker_id/${userID}`, - }, - post: {}, - }, - fantia: { - title: "Fantia", - color: "#e1097f", - user: { - profile: (userID) => `user_id: f"https://fantia.jp/fanclubs/${userID}`, - }, - post: {}, - }, - boosty: { - title: "Boosty", - color: "#fd6035", - user: { - profile: (userID) => `https://boosty.to/${userID}`, - }, - post: {}, - }, - afdian: { - title: "Afdian", - color: "#9169df", - user: { - profile: (userID) => ``, - }, - post: {}, - }, - fansly: { - title: "Fansly", - color: "#2399f7", - user: { - profile: (userID) => `https://fansly.com/${userID}`, - }, - post: {}, - }, - onlyfans: { - title: "OnlyFans", - color: "#008ccf", - user: { - profile: (userID) => `https://onlyfans.com/${userID}`, - }, - post: {}, - }, - candfans: { - title: "CandFans", - color: "#e8486c", - user: { - profile: (userID) => `https://candfans.jp/${userID}`, - }, - post: {}, - }, -}; - -export const freesites = { - kemono: { - title: "Kemono", - user: { - /** - * @param {string} service - * @param {string} artistID - */ - profile: (service, artistID) => `/${service}/${service === "discord" ? "server" : "user"}/${artistID}`, - /** - * @param {string} service - * @param {string} artistID - */ - icon: (service, artistID) => `/icons/${service}/${artistID}`, - banner: (service, artistID) => `/banners/${service}/${artistID}`, - }, - post: { - /** - * @param {string} service - * @param {string} userID - * @param {string} postID - * @returns - */ - link: (service, userID, postID) => `/${service}/user/${userID}/post/${postID}`, - }, - }, -}; - -/** - * @param {number} time - */ -export function waitAsync(time) { - return new Promise((resolve) => setTimeout(resolve, time)); -} diff --git a/client/src/utils/kemono-error.js b/client/src/utils/kemono-error.js deleted file mode 100644 index a54c25b..0000000 --- a/client/src/utils/kemono-error.js +++ /dev/null @@ -1,23 +0,0 @@ -const errorList = { - 0: "Could not connect to server.", - 1: "Could not favorite post.", - 2: "Could not unfavorite post.", - 3: "Could not favorite artist.", - 4: "Could not unfavorite artist.", - 5: "There might already be a flag here.", - 6: "Could not retrieve the list of bans.", - 7: "Could not retrieve banned artist.", - 8: "Could not retrieve artists.", - 9: "Could not retrieve import logs.", -}; - -export class KemonoError extends Error { - /** - * @param {number} code - */ - constructor(code) { - super(); - this.code = String(code).padStart(3, "0"); - this.message = `${this.code}: ${errorList[code]}`; - } -} diff --git a/client/static/boosty.svg b/client/static/boosty.svg new file mode 100644 index 0000000..68f7172 --- /dev/null +++ b/client/static/boosty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/static/favicon-coomer.ico b/client/static/favicon-coomer.ico new file mode 100644 index 0000000..8e4d5c8 Binary files /dev/null and b/client/static/favicon-coomer.ico differ diff --git a/client/static/small_icons/boosty.png b/client/static/small_icons/boosty.png new file mode 100644 index 0000000..6573b94 Binary files /dev/null and b/client/static/small_icons/boosty.png differ diff --git a/client/static/small_icons/canfans.png b/client/static/small_icons/candfans.png similarity index 100% rename from client/static/small_icons/canfans.png rename to client/static/small_icons/candfans.png diff --git a/client/static/sort.svg b/client/static/sort.svg new file mode 100644 index 0000000..e72ea3d --- /dev/null +++ b/client/static/sort.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..815731a --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".", + "outDir": "./dist", + "sourceMap": true, + "lib": ["dom", "esnext"], + "types": ["vite/client", "@modyfi/vite-plugin-yaml/modules"], + "allowJs": true, + "checkJs": true, + "skipLibCheck": true, + "strict": true, + "esModuleInterop": true, + "target": "ES2017", + "module": "esnext", + "moduleResolution": "bundler", + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true + }, + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "dist", "dev", "static"] +} diff --git a/client/vite.config.mjs b/client/vite.config.mjs new file mode 100644 index 0000000..f9b4625 --- /dev/null +++ b/client/vite.config.mjs @@ -0,0 +1,179 @@ +// @ts-check +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import legacy from "@vitejs/plugin-legacy"; +import ViteYaml from "@modyfi/vite-plugin-yaml"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import { createHtmlPlugin } from "vite-plugin-html"; +import { patchCssModules } from 'vite-css-modules' +import { + siteName, + favicon, + analyticsEnabled, + analyticsCode, + kemonoSite, + sentryDSN, + iconsPrepend, + bannersPrepend, + thumbnailsPrepend, + artistsOrCreators, + disableDMs, + disableFAQ, + disableFilehaus, + sidebarItems, + footerItems, + bannerGlobal, + bannerWelcome, + homeBackgroundImage, + homeMascotPath, + homeLogoPath, + homeWelcomeCredits, + homeAnnouncements, + paysiteList, + headerAd, + middleAd, + footerAd, + sliderAd, + videoAd, + isArchiveServerEnabled, + apiServerBaseURL, + apiServerPort, + gitCommitHash, + isFileServingEnabled, + buildDate, + AnnouncementBannerGlobal, +} from "./configs/vars.mjs"; + +export const baseConfig = defineConfig(async ({ command, mode }) => { + /** + * @type {import("vite").UserConfig} + */ + const config = { + plugins: [ + legacy({ + targets: ["defaults", "not IE 11"], + }), + patchCssModules({ exportMode: "named" }), + ViteYaml(), + // TODO: remove it after settling the static files situation + // by using the vite option of copying public folder instead + viteStaticCopy({ + structured: true, + targets: [ + { + src: "static", + dest: "./", + }, + ], + }), + createHtmlPlugin({ + entry: "./src/index.tsx", + inject: { + data: { + title: siteName, + analytics: !analyticsEnabled + ? undefined + : !analyticsCode + ? undefined + : atob(analyticsCode), + }, + tags: [ + { + tag: "link", + attrs: { + rel: "icon", + href: favicon, + }, + injectTo: "head", + }, + { + tag: "meta", + attrs: { + name: "og:type", + content: "website", + }, + injectTo: "head", + }, + { + tag: "meta", + attrs: { + name: "og:site_name", + content: siteName, + }, + injectTo: "head", + }, + { + tag: "meta", + attrs: { + name: "og:title", + content: siteName, + }, + injectTo: "head", + }, + { + tag: "meta", + attrs: { + name: "og:image", + content: `${kemonoSite}/static/kemono-logo.svg`, + }, + injectTo: "head", + }, + { + tag: "meta", + attrs: { + name: "og:image:width", + content: "150", + }, + injectTo: "head", + }, + { + tag: "meta", + attrs: { + name: "og:image:height", + content: "150", + }, + injectTo: "head", + }, + ], + }, + }), + react(), + ], + define: { + BUNDLER_ENV_KEMONO_SITE: JSON.stringify(kemonoSite), + BUNDLER_ENV_SENTRY_DSN: JSON.stringify(sentryDSN), + BUNDLER_ENV_SITE_NAME: JSON.stringify(siteName), + BUNDLER_ENV_ICONS_PREPEND: JSON.stringify(iconsPrepend), + BUNDLER_ENV_BANNERS_PREPEND: JSON.stringify(bannersPrepend), + BUNDLER_ENV_THUMBNAILS_PREPEND: JSON.stringify(thumbnailsPrepend), + BUNDLER_ENV_ARTISTS_OR_CREATORS: JSON.stringify(artistsOrCreators), + BUNDLER_ENV_DISABLE_DMS: JSON.stringify(disableDMs), + BUNDLER_ENV_DISABLE_FAQ: JSON.stringify(disableFAQ), + BUNDLER_ENV_DISABLE_FILEHAUS: JSON.stringify(disableFilehaus), + BUNDLER_ENV_SIDEBAR_ITEMS: JSON.stringify(sidebarItems), + BUNDLER_ENV_FOOTER_ITEMS: JSON.stringify(footerItems), + BUNDLER_ENV_BANNER_GLOBAL: JSON.stringify(bannerGlobal), + BUNDLER_ENV_ANNOUNCEMENT_BANNER_GLOBAL: JSON.stringify(AnnouncementBannerGlobal), + BUNDLER_ENV_BANNER_WELCOME: JSON.stringify(bannerWelcome), + BUNDLER_ENV_HOME_BACKGROUND_IMAGE: JSON.stringify(homeBackgroundImage), + BUNDLER_ENV_HOME_MASCOT_PATH: JSON.stringify(homeMascotPath), + BUNDLER_ENV_HOME_LOGO_PATH: JSON.stringify(homeLogoPath), + BUNDLER_ENV_HOME_WELCOME_CREDITS: JSON.stringify(homeWelcomeCredits), + BUNDLER_ENV_HOME_ANNOUNCEMENTS: JSON.stringify(homeAnnouncements), + BUNDLER_ENV_PAYSITE_LIST: JSON.stringify(paysiteList), + BUNDLER_ENV_HEADER_AD: JSON.stringify(headerAd), + BUNDLER_ENV_MIDDLE_AD: JSON.stringify(middleAd), + BUNDLER_ENV_FOOTER_AD: JSON.stringify(footerAd), + BUNDLER_ENV_SLIDER_AD: JSON.stringify(sliderAd), + BUNDLER_ENV_VIDEO_AD: JSON.stringify(videoAd), + BUNDLER_ENV_IS_ARCHIVER_ENABLED: JSON.stringify(isArchiveServerEnabled), + BUNDLER_ENV_API_SERVER_BASE_URL: JSON.stringify(apiServerBaseURL), + BUNDLER_ENV_API_SERVER_PORT: JSON.stringify(apiServerPort), + BUNDLER_ENV_GIT_COMMIT_HASH: JSON.stringify(gitCommitHash), + BUNDLER_ENV_BUILD_DATE: JSON.stringify(buildDate), + BUNDLER_ENV_IS_FILE_SERVING_ENABLED: JSON.stringify(isFileServingEnabled), + }, + }; + + return config; +}); diff --git a/client/vite.dev.mjs b/client/vite.dev.mjs new file mode 100644 index 0000000..6e75486 --- /dev/null +++ b/client/vite.dev.mjs @@ -0,0 +1,65 @@ +// @ts-check +import { defineConfig, mergeConfig } from "vite"; +import { baseConfig } from "./vite.config.mjs"; +import { + apiServerBaseURL, + apiServerPort, +} from "./configs/vars.mjs"; + +const config = defineConfig(async (configEnv) => { + /** + * @type {import("vite").UserConfig} + */ + const targetBackend = `${apiServerBaseURL}${apiServerPort ? `:${apiServerPort}` : ''}`; + const proxyConfig = {}; + if (targetBackend.includes("kemono.su")) { + proxyConfig["/api/v1/creators"] = { + target: "https://kemono.su", // target the base URL for the proxy + changeOrigin: true, + secure: true, + rewrite: (path) => path.replace(/^\/api\/v1\/creators/, '/api/v1/creators.txt'), + }; + } + proxyConfig["/api"] = { + target: targetBackend, + changeOrigin: true, + secure: true, + }; + proxyConfig["/icons"] = { + target: targetBackend, + changeOrigin: true, + secure: true, + }; + proxyConfig["/banners"] = { + target: targetBackend, + changeOrigin: true, + secure: true, + }; + proxyConfig["/thumbnail"] = { + target: targetBackend, + changeOrigin: true, + secure: true, + }; + proxyConfig["/data"] = { + target: targetBackend, + changeOrigin: true, + secure: true, + }; + + const devConfig = { + server: { + host: "0.0.0.0", + port: 3450, + strictPort: true, + proxy: proxyConfig, + allowedHosts: ["all"], + }, + }; + + const resolvedBase = await baseConfig(configEnv); + const finalConfig = mergeConfig(resolvedBase, devConfig); + + return finalConfig; +}); + +export default config; diff --git a/client/vite.prod.mjs b/client/vite.prod.mjs new file mode 100644 index 0000000..d06f201 --- /dev/null +++ b/client/vite.prod.mjs @@ -0,0 +1,32 @@ +// @ts-check +import { defineConfig, mergeConfig } from "vite"; +import { baseConfig } from "./vite.config.mjs"; +import { apiServerBaseURL, apiServerPort } from "./configs/vars.mjs"; + +const config = defineConfig(async (configEnv) => { + /** + * @type {import("vite").UserConfig} + */ + const prodConfig = { + preview: { + host: "0.0.0.0", + port: 5000, + strictPort: true, + proxy: { + "/api": `${apiServerBaseURL}:${apiServerPort}`, + }, + }, + build: { + cssCodeSplit: false, + sourcemap: true, + outDir: "dist", + emptyOutDir: true, + }, + }; + const resolvedBase = await baseConfig(configEnv); + const finalConfig = mergeConfig(resolvedBase, prodConfig); + + return finalConfig; +}); + +export default config; diff --git a/client/webpack.config.js b/client/webpack.config.js index e5c0ee1..4c36536 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -1,46 +1,107 @@ -const path = require("path"); +// @ts-check const { DefinePlugin } = require("webpack"); const CopyWebpackPlugin = require("copy-webpack-plugin"); -const { buildHTMLWebpackPluginsRecursive } = require("./configs/build-templates"); +const HTMLWebpackPlugin = require("html-webpack-plugin"); const { kemonoSite, - nodeEnv, + sentryDSN, + siteName, iconsPrepend, bannersPrepend, thumbnailsPrepend, - creatorsLocation, -} = require("./configs/vars"); - -const projectPath = path.resolve(__dirname, "src"); -const pagesPath = path.join(projectPath, "pages"); -const pagePlugins = buildHTMLWebpackPluginsRecursive(pagesPath, { - fileExtension: "html", - pluginOptions: { - inject: false, - minify: false, - }, -}); + artistsOrCreators, + disableDMs, + disableFAQ, + disableFilehaus, + sidebarItems, + footerItems, + bannerGlobal, + bannerWelcome, + homeBackgroundImage, + homeMascotPath, + homeLogoPath, + paysiteList, + homeWelcomeCredits, + homeAnnouncements, + headerAd, + middleAd, + footerAd, + sliderAd, + videoAd, + isArchiveServerEnabled, + apiServerBaseURL, + apiServerPort, + analyticsEnabled, + analyticsCode, + gitCommitHash, + isFileServingEnabled, + buildDate, +} = require("./configs/vars.mjs"); /** - * TODO: make separate entries for `admin` and `moderator` * @type import("webpack").Configuration */ const webpackConfig = { entry: { - global: path.join(projectPath, "js", "global.js"), - admin: path.join(projectPath, "js", "admin.js"), - // moderator: path.join(projectPath, "js", "moderator.js"), + index: "./src/index.tsx", }, plugins: [ - ...pagePlugins, + // ...pagePlugins, + new HTMLWebpackPlugin({ + title: siteName, + filename: "index.html", + favicon: "./static/favicon.ico", + meta: { + "og:type": "website", + "og:site_name": siteName, + "og:title": siteName, + "og:image": `${kemonoSite}/static/kemono-logo.svg`, + "og:image:width": "150", + "og:image:height": "150", + }, + templateParameters: { + analytics: !analyticsEnabled + ? undefined + : !analyticsCode + ? undefined + : atob(analyticsCode), + }, + template: "./src/index.html", + }), + // https://webpack.js.org/plugins/define-plugin/ new DefinePlugin({ BUNDLER_ENV_KEMONO_SITE: JSON.stringify(kemonoSite), - BUNDLER_ENV_NODE_ENV: JSON.stringify(nodeEnv), + BUNDLER_ENV_SENTRY_DSN: JSON.stringify(sentryDSN), + BUNDLER_ENV_SITE_NAME: JSON.stringify(siteName), BUNDLER_ENV_ICONS_PREPEND: JSON.stringify(iconsPrepend), BUNDLER_ENV_BANNERS_PREPEND: JSON.stringify(bannersPrepend), BUNDLER_ENV_THUMBNAILS_PREPEND: JSON.stringify(thumbnailsPrepend), - BUNDLER_ENV_CREATORS_LOCATION: JSON.stringify(creatorsLocation), + BUNDLER_ENV_ARTISTS_OR_CREATORS: JSON.stringify(artistsOrCreators), + BUNDLER_ENV_DISABLE_DMS: JSON.stringify(disableDMs), + BUNDLER_ENV_DISABLE_FAQ: JSON.stringify(disableFAQ), + BUNDLER_ENV_DISABLE_FILEHAUS: JSON.stringify(disableFilehaus), + BUNDLER_ENV_SIDEBAR_ITEMS: JSON.stringify(sidebarItems), + BUNDLER_ENV_FOOTER_ITEMS: JSON.stringify(footerItems), + BUNDLER_ENV_BANNER_GLOBAL: JSON.stringify(bannerGlobal), + BUNDLER_ENV_BANNER_WELCOME: JSON.stringify(bannerWelcome), + BUNDLER_ENV_HOME_BACKGROUND_IMAGE: JSON.stringify(homeBackgroundImage), + BUNDLER_ENV_HOME_MASCOT_PATH: JSON.stringify(homeMascotPath), + BUNDLER_ENV_HOME_LOGO_PATH: JSON.stringify(homeLogoPath), + BUNDLER_ENV_HOME_WELCOME_CREDITS: JSON.stringify(homeWelcomeCredits), + BUNDLER_ENV_HOME_ANNOUNCEMENTS: JSON.stringify(homeAnnouncements), + BUNDLER_ENV_PAYSITE_LIST: JSON.stringify(paysiteList), + BUNDLER_ENV_HEADER_AD: JSON.stringify(headerAd), + BUNDLER_ENV_MIDDLE_AD: JSON.stringify(middleAd), + BUNDLER_ENV_FOOTER_AD: JSON.stringify(footerAd), + BUNDLER_ENV_SLIDER_AD: JSON.stringify(sliderAd), + BUNDLER_ENV_VIDEO_AD: JSON.stringify(videoAd), + BUNDLER_ENV_IS_ARCHIVER_ENABLED: JSON.stringify(isArchiveServerEnabled), + BUNDLER_ENV_API_SERVER_BASE_URL: JSON.stringify(apiServerBaseURL), + BUNDLER_ENV_API_SERVER_PORT: JSON.stringify(apiServerPort), + BUNDLER_ENV_GIT_COMMIT_HASH: JSON.stringify(gitCommitHash), + BUNDLER_ENV_BUILD_DATE: JSON.stringify(buildDate), + BUNDLER_ENV_IS_FILE_SERVING_ENABLED: JSON.stringify(isFileServingEnabled), }), new CopyWebpackPlugin({ patterns: [ @@ -52,18 +113,7 @@ const webpackConfig = { }), ], resolve: { - extensions: [".js"], - alias: { - ["@wp/pages"]: path.join(projectPath, "pages", "_index.js"), - ["@wp/components"]: path.join(projectPath, "pages", "components", "_index.js"), - ["@wp/env"]: path.join(projectPath, "env"), - ["@wp/lib"]: path.join(projectPath, "lib"), - ["@wp/js"]: path.join(projectPath, "js"), - ["@wp/css"]: path.join(projectPath, "css"), - ["@wp/assets"]: path.join(projectPath, "assets"), - ["@wp/api"]: path.join(projectPath, "api", "_index.js"), - ["@wp/utils"]: path.join(projectPath, "utils", "_index.js"), - }, + extensions: [".tsx", ".ts", ".js"], fallback: { stream: false, }, diff --git a/client/webpack.dev.js b/client/webpack.dev.js index ab8884e..9eb2cf7 100644 --- a/client/webpack.dev.js +++ b/client/webpack.dev.js @@ -1,9 +1,14 @@ +// @ts-check const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const path = require("path"); const { merge } = require("webpack-merge"); - +const yaml = require("yaml"); +const { + kemonoSite, + apiServerBaseURL, + apiServerPort, +} = require("./configs/vars.mjs"); const baseConfig = require("./webpack.config"); -const { kemonoSite } = require("./configs/vars"); const projectPath = path.resolve(__dirname, "src"); @@ -13,25 +18,25 @@ const projectPath = path.resolve(__dirname, "src"); const devServer = { host: "0.0.0.0", port: 3450, - devMiddleware: { - writeToDisk: true, - }, - watchFiles: { - options: { - poll: 500, - aggregateTimeout: 500, + proxy: [ + { + context: ["/api","/icons","/data", "/thumbnail"], + target: `localhost:3449`, }, - }, + ], static: { directory: path.resolve(__dirname, "static"), watch: true, }, - hot: false, - liveReload: true, + hot: true, + // liveReload: true, client: { overlay: true, progress: true, }, + historyApiFallback: { + index: "/index.html", + }, }; /** @@ -39,11 +44,8 @@ const devServer = { */ const webpackConfigDev = { mode: "development", - devtool: "eval-source-map", + devtool: "inline-source-map", devServer: devServer, - entry: { - development: path.join(projectPath, "development", "entry.js"), - }, plugins: [ new MiniCssExtractPlugin({ filename: "static/bundle/css/[name].css", @@ -51,12 +53,21 @@ const webpackConfigDev = { }), ], module: { + parser: { + javascript: { + exportsPresence: "error", + }, + }, rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, { test: /\.s[ac]ss$/i, - exclude: /\.module.s[ac]ss$/i, use: [ - MiniCssExtractPlugin.loader, + "style-loader", { loader: "css-loader", options: { @@ -89,6 +100,13 @@ const webpackConfigDev = { test: /\.css$/, use: ["style-loader", "css-loader"], }, + { + test: /\.yaml$/i, + type: "json", + parser: { + parse: yaml.parse, + }, + }, ], }, output: { @@ -96,7 +114,6 @@ const webpackConfigDev = { filename: "static/bundle/js/[name].bundle.js", assetModuleFilename: "static/bundle/assets/[name][ext][query]", publicPath: "/", - clean: true, }, }; diff --git a/client/webpack.prod.js b/client/webpack.prod.js index ff02923..6475d4d 100644 --- a/client/webpack.prod.js +++ b/client/webpack.prod.js @@ -1,7 +1,10 @@ -const path = require("path"); +// @ts-check +const path = require("path"); const MiniCSSExtractPlugin = require("mini-css-extract-plugin"); const { merge } = require("webpack-merge"); +const yaml = require("yaml"); +const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); const baseConfig = require("./webpack.config"); const { kemonoSite } = require("./configs/vars"); @@ -10,8 +13,9 @@ const { kemonoSite } = require("./configs/vars"); */ const webpackConfigProd = { mode: "production", - // devtool: "source-map", + devtool: "source-map", plugins: [ + new BundleAnalyzerPlugin({ analyzerMode: "static", openAnalyzer: false }), new MiniCSSExtractPlugin({ filename: "static/bundle/css/[name]-[contenthash].css", chunkFilename: "static/bundle/css/[id]-[contenthash].chunk.css", @@ -20,19 +24,25 @@ const webpackConfigProd = { module: { rules: [ { - test: /\.m?js$/i, - exclude: /node_modules/, - use: { - loader: "babel-loader", - options: { - presets: [["@babel/preset-env", { targets: "defaults" }]], - plugins: ["@babel/plugin-transform-runtime"], + test: /\.tsx?$/, + use: [ + { + loader: "babel-loader", + options: { + presets: [ + ["@babel/preset-env", { targets: "defaults" }], + ["@babel/preset-typescript"], + ["@babel/preset-react"], + ], + plugins: ["@babel/plugin-transform-runtime"], + }, }, - }, + "ts-loader", + ], + exclude: /node_modules/, }, { test: /\.s[ac]ss$/i, - exclude: /\.module\.s[ac]ss$/i, use: [ MiniCSSExtractPlugin.loader, { @@ -85,28 +95,26 @@ const webpackConfigProd = { test: /\.css$/, use: ["style-loader", "css-loader"], }, + { + test: /\.yaml$/i, + type: "json", + parser: { + parse: yaml.parse, + }, + }, ], }, output: { path: path.resolve(__dirname, "dist"), filename: "static/bundle/js/[name]-[contenthash].bundle.js", - assetModuleFilename: "static/bundle/assets/[name]-[contenthash][ext][query]", - // sourceMapFilename: "source-maps/[file].map[query]", + assetModuleFilename: + "static/bundle/assets/[name]-[contenthash][ext][query]", publicPath: "/", clean: true, }, optimization: { moduleIds: "deterministic", runtimeChunk: "single", - splitChunks: { - cacheGroups: { - vendor: { - test: /[\\/]node_modules[\\/]/, - name: "vendors", - chunks: "all", - }, - }, - }, }, }; diff --git a/config.example.json b/config.example.json index f2a558c..0a70c3c 100644 --- a/config.example.json +++ b/config.example.json @@ -1,16 +1,18 @@ { - "site": "http://localhost:5000", + "site": "http://localhost:3450", "development_mode": true, "automatic_migrations": true, "webserver": { "secret": "To SECRET name.", - "port": 80, + "base_url": "http://localhost", + "port": 3449, "ui": { "home": { "site_name": "Kemono" }, "config": { - "paysite_list": ["patreon", "fanbox", "afdian"] + "paysite_list": ["patreon", "fanbox", "afdian"], + "artists_or_creators": "Artists" } } }, diff --git a/config.example.production.json b/config.example.production.json new file mode 100644 index 0000000..9fbbb9a --- /dev/null +++ b/config.example.production.json @@ -0,0 +1,31 @@ +{ + "site": "http://localhost:3450", + "development_mode": true, + "automatic_migrations": true, + "webserver": { + "secret": "To SECRET name.", + "base_url": "https://kemono.su", + "ui": { + "home": { + "site_name": "Kemono" + }, + "config": { + "paysite_list": ["patreon", "fanbox", "afdian"], + "artists_or_creators": "Artists" + } + } + }, + "database": { + "host": "postgres", + "user": "kemono", + "password": "kemono", + "database": "kemono" + }, + "redis": { + "defaults": { + "host": "redis", + "port": 6379, + "db": 0 + } + } +} diff --git a/db/migrations/20241110_00_DASAD-add-favorite-counts-table.py b/db/migrations/20241110_00_DASAD-add-favorite-counts-table.py new file mode 100644 index 0000000..19fe141 --- /dev/null +++ b/db/migrations/20241110_00_DASAD-add-favorite-counts-table.py @@ -0,0 +1,62 @@ +""" +Add a favorite_counts table to aleviate the query time. +""" + +from yoyo import step + +__depends__ = {"20240223_00_ASDAS-reset_relation_id_seq"} + +steps = [ + step( + """ + CREATE TABLE IF NOT EXISTS favorite_counts ( + service varchar NOT NULL, + artist_id varchar NOT NULL, + favorite_count INTEGER DEFAULT 0, + PRIMARY KEY (service, artist_id) + ); + """ + ), + step( + """ + CREATE OR REPLACE FUNCTION update_favorite_count() + RETURNS TRIGGER AS $$ + BEGIN + -- For INSERT event: increment the count + IF TG_OP = 'INSERT' THEN + INSERT INTO favorite_counts (service, artist_id, favorite_count) + VALUES (NEW.service, NEW.artist_id, 1) + ON CONFLICT (service, artist_id) + DO UPDATE SET favorite_count = favorite_counts.favorite_count + 1; + + -- For DELETE event: decrement the count + ELSIF TG_OP = 'DELETE' THEN + UPDATE favorite_counts + SET favorite_count = favorite_count - 1 + WHERE service = OLD.service AND artist_id = OLD.artist_id; + END IF; + + RETURN NULL; -- triggers on INSERT/DELETE return NULL + END; + $$ LANGUAGE plpgsql; + """ + ), + step( + """ + CREATE TRIGGER update_favorite_count_trigger + AFTER INSERT OR DELETE ON account_artist_favorite + FOR EACH ROW + EXECUTE FUNCTION update_favorite_count(); + """ + ), + step( + """ + INSERT INTO favorite_counts (service, artist_id, favorite_count) + SELECT service, artist_id, COUNT(*) AS favorite_count + FROM account_artist_favorite + GROUP BY service, artist_id + ON CONFLICT (service, artist_id) + DO UPDATE SET favorite_count = EXCLUDED.favorite_count; + """ + ), +] diff --git a/db/migrations/20250328_01_KV3ap-add-reason-to-flags.py b/db/migrations/20250328_01_KV3ap-add-reason-to-flags.py new file mode 100644 index 0000000..75ed32d --- /dev/null +++ b/db/migrations/20250328_01_KV3ap-add-reason-to-flags.py @@ -0,0 +1,31 @@ +""" +Create new post_flags table. +""" + +from yoyo import step + +__depends__ = {"20241110_00_DASAD-add-favorite-counts-table"} + +steps = [ + step(""" + CREATE TABLE "public"."post_flags" ( + "post_id" TEXT NOT NULL, + "creator_id" TEXT NOT NULL, + "service" TEXT NOT NULL, + "contributor_id" int4 NOT NULL, + "created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "reason" int2 NOT NULL, + "reason_text" TEXT, + "flagger_ip_hash" UUID, + CONSTRAINT "post_flags_pk" PRIMARY KEY ("post_id", "creator_id", "service", "contributor_id") + ); + """, 'DROP TABLE "public"."post_flags"'), + step( + """CREATE INDEX "idx_post_flags_on_contributor_id_and_created_at" ON "post_flags" ("contributor_id", "created_at");""", + """DROP INDEX "idx_post_flags_on_contributor_id_and_created_at";""" + ), + step( + """CREATE INDEX "idx_post_flags_on_ip_hash_and_created_at" ON "post_flags" ("flagger_ip_hash", "created_at");""", + """DROP INDEX "idx_post_flags_on_ip_hash_and_created_at";""" + ), +] diff --git a/db/schema/public/accounts.sql b/db/schema/public/accounts.sql new file mode 100644 index 0000000..39bcbf5 --- /dev/null +++ b/db/schema/public/accounts.sql @@ -0,0 +1,52 @@ +-- accounts +CREATE TABLE account( + id serial PRIMARY KEY, + username varchar NOT NULL, + password_hash varchar NOT NULL, + created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + role varchar DEFAULT 'consumer', + UNIQUE (username) +); + +CREATE TABLE account_artist_favorite( + id serial, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + account_id int NOT NULL REFERENCES account(id), + service varchar(20) NOT NULL, + artist_id varchar(255) NOT NULL, + PRIMARY KEY (service, id), + UNIQUE (account_id, service, artist_id) +); + +CREATE INDEX ON account_artist_favorite(service, artist_id); + +CREATE TABLE account_post_favorite( + id serial, + created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, + account_id int NOT NULL REFERENCES account(id), + service varchar(20) NOT NULL, + artist_id varchar(255) NOT NULL, + post_id varchar(255) NOT NULL, + PRIMARY KEY (service, id), + UNIQUE (account_id, service, artist_id, post_id) +); + +CREATE TABLE notifications( + id bigserial PRIMARY KEY, + account_id int NOT NULL, + type SMALLINT NOT NULL, + extra_info jsonb, + created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + is_seen boolean NOT NULL DEFAULT FALSE, + FOREIGN KEY (account_id) REFERENCES account(id) +); + +CREATE INDEX account_idx ON account USING BTREE(username, created_at, ROLE); + +CREATE INDEX ON account_post_favorite(service, artist_id, post_id); + +CREATE INDEX notifications_account_id_idx ON notifications USING BTREE("account_id"); + +CREATE INDEX notifications_created_at_idx ON notifications USING BTREE("created_at"); + +CREATE INDEX notifications_type_idx ON notifications USING BTREE("type"); diff --git a/db/schema/public/artists.sql b/db/schema/public/artists.sql new file mode 100644 index 0000000..3438305 --- /dev/null +++ b/db/schema/public/artists.sql @@ -0,0 +1,107 @@ +-- Lookup (aka artists) +CREATE TABLE lookup( + "id" varchar(255) NOT NULL, + "name" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "indexed" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + public_id text, + relation_id integer, + PRIMARY KEY (id, service) +); + +CREATE TABLE creators( + creator_id text NOT NULL, + service text NOT NULL, + creator_name text NOT NULL, + creator_slug text, + creator_internal_id text, + short_description text NOT NULL, + description text NOT NULL, + icon text, + banner text, + is_nsfw boolean, + deleted_at timestamp without time zone, + stopped_at timestamp without time zone, + paused_at timestamp without time zone, + post_count integer, + media_count integer, + tiers jsonb[], + access_groups jsonb[], + published_at timestamp without time zone, + added_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp without time zone, + public_posts_refreshed_at timestamp without time zone, + public_posts_full_refreshed_at timestamp without time zone, + CONSTRAINT creators_pkey PRIMARY KEY (creator_id, service) +); + +CREATE TABLE creators_revisions( + revision_id serial NOT NULL PRIMARY KEY, + creator_id text NOT NULL, + service text NOT NULL, + creator_name text NOT NULL, + creator_slug text, + creator_internal_id text, + short_description text NOT NULL, + description text NOT NULL, + icon text, + banner text, + is_nsfw boolean, + deleted_at timestamp without time zone, + stopped_at timestamp without time zone, + paused_at timestamp without time zone, + post_count integer, + media_count integer, + tiers jsonb[], + access_groups jsonb[], + published_at timestamp without time zone, + added_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp without time zone, + public_posts_refreshed_at timestamp without time zone, + public_posts_full_refreshed_at timestamp without time zone +); + +CREATE TYPE unapproved_link_status AS ENUM( + 'pending', + 'approved', + 'rejected' +); + +CREATE TABLE unapproved_link_requests( + id int PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + from_service text NOT NULL, + from_id text NOT NULL, + to_service text NOT NULL, + to_id text NOT NULL, + reason text, + requester_id int NOT NULL REFERENCES account(id), + status unapproved_link_status NOT NULL DEFAULT 'pending', + FOREIGN KEY (from_service, from_id) REFERENCES lookup(service, id), + FOREIGN KEY (to_service, to_id) REFERENCES lookup(service, id), + UNIQUE (from_service, from_id, to_service, to_id) +); + +CREATE INDEX name_idx ON lookup USING btree("name"); + +CREATE INDEX lookup_id_idx ON lookup USING btree("id"); + +CREATE INDEX lookup_service_idx ON lookup USING btree("service"); + +CREATE INDEX lookup_indexed_idx ON lookup USING btree("indexed"); + +CREATE SEQUENCE lookup_relation_id_seq; + +CREATE INDEX lookup_relation_id_index ON lookup USING btree(relation_id); + +CREATE INDEX lookup_public_id_idx ON lookup(public_id); + +CREATE INDEX lookup_relation_id_idx ON lookup(relation_id); + +-- the migrations refer to `updated_idx` index +-- but it wasn't declared prior being changed +CREATE INDEX updated_idx ON lookup USING btree("updated"); + +CREATE INDEX creators_revisions_creator_id_service_idx ON creators_revisions USING btree(creator_id, service); + +CREATE INDEX unapproved_link_requests_status_id_idx ON unapproved_link_requests(status, id); diff --git a/db/schema/public/dms.sql b/db/schema/public/dms.sql new file mode 100644 index 0000000..4d9fe21 --- /dev/null +++ b/db/schema/public/dms.sql @@ -0,0 +1,64 @@ +-- DMs +CREATE TABLE dms_temp_old( + "id" varchar(255) NOT NULL, + "user" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "content" text NOT NULL DEFAULT '', + "embed" jsonb NOT NULL DEFAULT '{}', + "added" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" timestamp, + "file" jsonb NOT NULL, + PRIMARY KEY (id, service) +); + +CREATE TABLE unapproved_dms_temp_old( + "import_id" varchar(255) NOT NULL, + contributor_id varchar(255), + "id" varchar(255) NOT NULL, + "user" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "content" text NOT NULL DEFAULT '', + "embed" jsonb NOT NULL DEFAULT '{}', + "added" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" timestamp, + "file" jsonb NOT NULL, + PRIMARY KEY (id, service) +); + +-- the old tables weren't dropped, apparently + +CREATE TABLE dms( + "hash" varchar NOT NULL, + "user" varchar(255) NOT NULL, + service varchar(20) NOT NULL, + "content" text NOT NULL DEFAULT ''::text, + embed jsonb NOT NULL DEFAULT '{}' ::jsonb, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + published timestamp NULL, + file jsonb NOT NULL, + CONSTRAINT dms_pkey PRIMARY KEY ("hash", "user", service) +); + +CREATE TABLE unapproved_dms( + "hash" varchar NOT NULL, + "user" varchar(255) NOT NULL, + service varchar(20) NOT NULL, + contributor_id varchar(255) NOT NULL, + "content" text NOT NULL DEFAULT ''::text, + embed jsonb NOT NULL DEFAULT '{}' ::jsonb, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + published timestamp NULL, + file jsonb NOT NULL, + import_id varchar(255) NOT NULL, + remote_user_id_hash varchar NULL, + deleted_at timestamp NULL, + CONSTRAINT unapproved_dms_pkey PRIMARY KEY ("hash", "user", service, contributor_id) +); + +CREATE INDEX dm_idx ON dms_temp_old USING btree("user"); + +CREATE INDEX dms_user_idx ON dms_temp_old("user"); + +CREATE INDEX unapproved_dm_idx ON unapproved_dms USING btree("import_id"); + +CREATE INDEX unapproved_dms_contributor_id_user_idx ON unapproved_dms(contributor_id, "user"); diff --git a/db/schema/public/extensions.sql b/db/schema/public/extensions.sql new file mode 100644 index 0000000..6e98b00 --- /dev/null +++ b/db/schema/public/extensions.sql @@ -0,0 +1,5 @@ +CREATE EXTENSION pgroonga; + +CREATE EXTENSION pgcrypto; + +CREATE EXTENSION citext; diff --git a/db/schema/public/files.sql b/db/schema/public/files.sql new file mode 100644 index 0000000..1b31a89 --- /dev/null +++ b/db/schema/public/files.sql @@ -0,0 +1,67 @@ +-- files +CREATE TABLE files( + id serial PRIMARY KEY, + hash varchar NOT NULL, + mtime timestamp NOT NULL, + ctime timestamp NOT NULL, + mime varchar, + ext varchar, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (hash) +); + +CREATE TABLE file_post_relationships( + file_id int NOT NULL REFERENCES files(id), + filename varchar NOT NULL, + service varchar NOT NULL, + "user" varchar NOT NULL, + post varchar NOT NULL, + contributor_id int REFERENCES account(id), + inline boolean NOT NULL DEFAULT FALSE, + PRIMARY KEY (file_id, service, "user", post) +); + +CREATE TABLE file_discord_message_relationships( + file_id int NOT NULL REFERENCES files(id), + filename varchar NOT NULL, + server varchar NOT NULL, + channel varchar NOT NULL, + id varchar NOT NULL, + contributor_id int REFERENCES account(id), + PRIMARY KEY (file_id, SERVER, channel, id) +); + +CREATE TABLE file_server_relationships( + file_id int NOT NULL REFERENCES files(id), + remote_path varchar NOT NULL +); + +CREATE TABLE archive_files( + file_id int NOT NULL REFERENCES files(id), + -- this for sure won't have problems down the line + files text[] NOT NULL, + password TEXT, + CONSTRAINT archive_files_pk PRIMARY KEY (file_id) +); + +CREATE INDEX file_id_idx ON file_post_relationships USING btree("file_id"); + +CREATE INDEX file_post_service_idx ON file_post_relationships USING btree("service"); + +CREATE INDEX file_post_user_idx ON file_post_relationships USING btree("user"); + +CREATE INDEX file_post_id_idx ON file_post_relationships USING btree("post"); + +CREATE INDEX file_post_contributor_id_idx ON file_post_relationships USING btree("contributor_id"); + +CREATE INDEX file_discord_id_idx ON file_discord_message_relationships USING btree("file_id"); + +CREATE INDEX file_discord_message_server_idx ON file_discord_message_relationships USING btree("server"); + +CREATE INDEX file_discord_message_channel_idx ON file_discord_message_relationships USING btree("channel"); + +CREATE INDEX file_discord_message_id_idx ON file_discord_message_relationships USING btree("id"); + +CREATE INDEX file_discord_message_contributor_id_idx ON file_discord_message_relationships USING btree("contributor_id"); + +CREATE INDEX file_server_relationships_remote_path_idx ON file_server_relationships USING btree(remote_path); diff --git a/db/schema/public/posts/comments.sql b/db/schema/public/posts/comments.sql new file mode 100644 index 0000000..33a7f12 --- /dev/null +++ b/db/schema/public/posts/comments.sql @@ -0,0 +1,34 @@ +CREATE TABLE comments( + "id" varchar(255) NOT NULL, + "post_id" varchar(255) NOT NULL, + "parent_id" varchar(255), + "commenter" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "content" text NOT NULL DEFAULT '', + "added" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" timestamp, + deleted_at timestamp, + commenter_name text, + PRIMARY KEY (id, service) +); + +CREATE TABLE comments_revisions( + revision_id serial4 NOT NULL, + id varchar(255) NOT NULL, + post_id varchar(255) NOT NULL, + parent_id varchar(255) NULL, + commenter varchar(255) NOT NULL, + service varchar(20) NOT NULL, + "content" text NOT NULL DEFAULT ''::text, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + published timestamp NULL, + deleted_at timestamp, + commenter_name text, + CONSTRAINT comments_revisions_pkey PRIMARY KEY (revision_id) +); + +CREATE INDEX comments_revisions_post_id_idx ON comments_revisions USING btree(post_id); + +CREATE INDEX comments_revisions_id_idx ON comments_revisions USING btree(id); + +CREATE INDEX comment_idx ON comments USING btree("post_id"); diff --git a/db/schema/public/posts/discord.sql b/db/schema/public/posts/discord.sql new file mode 100644 index 0000000..9eb232a --- /dev/null +++ b/db/schema/public/posts/discord.sql @@ -0,0 +1,59 @@ +-- Posts (Discord) +CREATE TABLE discord_channels( + channel_id text NOT NULL, + server_id text NOT NULL, + name text NOT NULL, + parent_channel_id text NULL, + topic text NULL, + theme_color text NULL, + is_nsfw bool NOT NULL, + position int NOT NULL DEFAULT 0, + icon_emoji text NULL, + type int NOT NULL DEFAULT 0, + CONSTRAINT discord_channels_pkey PRIMARY KEY (channel_id) +); + +CREATE TABLE discord_posts( + "id" varchar(255) NOT NULL, + "author" jsonb NOT NULL, + "server" varchar(255) NOT NULL, + "channel" varchar(255) NOT NULL, + "content" text NOT NULL DEFAULT '', + "added" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" timestamp, + "edited" timestamp, + "embeds" jsonb[] NOT NULL, + "mentions" jsonb[] NOT NULL, + "attachments" jsonb[] NOT NULL, + PRIMARY KEY (id, SERVER, channel) +); + +CREATE TABLE discord_posts_revisions( + revision_id serial4 NOT NULL, + id varchar(255) NOT NULL, + author jsonb NOT NULL, + "server" varchar(255) NOT NULL, + channel varchar(255) NOT NULL, + "content" text NOT NULL DEFAULT ''::text, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + published timestamp NULL, + edited timestamp NULL, + embeds _jsonb NOT NULL, + mentions _jsonb NOT NULL, + attachments _jsonb NOT NULL, + CONSTRAINT discord_posts_revisions_pkey PRIMARY KEY (revision_id) +); + +CREATE INDEX discord_channels_server_id_idx ON discord_channels USING btree(server_id); + +CREATE INDEX discord_channels_parent_channel_id_idx ON discord_channels USING btree(parent_channel_id); + +CREATE INDEX discord_id_idx ON discord_posts USING HASH ("id"); + +CREATE INDEX server_idx ON discord_posts USING HASH ("server"); + +CREATE INDEX discord_posts_server_channel_idx ON discord_posts USING btree(SERVER, channel); + +CREATE INDEX discord_posts_channel_published_idx ON discord_posts USING btree(channel, published); + +CREATE INDEX discord_posts_revisions_id_idx ON public.discord_posts_revisions USING btree(id); diff --git a/db/schema/public/posts/fanbox.sql b/db/schema/public/posts/fanbox.sql new file mode 100644 index 0000000..dd0ea3e --- /dev/null +++ b/db/schema/public/posts/fanbox.sql @@ -0,0 +1,62 @@ +CREATE TABLE fanbox_newsletters_temp_old( + id varchar NOT NULL, + user_id varchar NOT NULL, + content text NOT NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + published timestamp, + PRIMARY KEY (id) +); + +CREATE TABLE fanbox_newsletters( + user_id varchar NOT NULL, + hash varchar NOT NULL, + "content" varchar NOT NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + published timestamp NOT NULL, + CONSTRAINT fanbox_newsletters_pkey PRIMARY KEY (user_id, hash) +); + +CREATE TABLE fanbox_embeds( + id varchar NOT NULL, + user_id varchar NOT NULL, + post_id varchar NOT NULL, + type varchar NOT NULL, + json varchar NOT NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + processed varchar, + "iframely_key" varchar(255) NULL, + "iframely_data" jsonb NULL, + "iframely_url" text NULL, + PRIMARY KEY (id) +); + +CREATE TABLE fanbox_fancards( + id serial PRIMARY KEY, + user_id varchar NOT NULL, + file_id int REFERENCES files(id), + last_checked_at timestamp DEFAULT CURRENT_TIMESTAMP, + price text NOT NULL DEFAULT '', + CONSTRAINT fanbox_fancards_user_id_file_id_price_unique_idx UNIQUE (user_id, file_id, price) +); + +CREATE INDEX fanbox_newsletters_temp_old_user_id_idx ON fantia_newsletters USING btree(user_id); + +CREATE INDEX fanbox_newsletters_temp_old_added_idx ON fantia_newsletters USING btree(added); + +CREATE INDEX fanbox_newsletters_temp_old_published_idx ON fantia_newsletters USING btree(published); + +CREATE INDEX fanbox_newsletters_user_id_published_idx ON public.fanbox_newsletters_temp_new USING btree(user_id, published); + +CREATE INDEX fanbox_embeds_user_id_idx ON fanbox_embeds USING btree(user_id); + +CREATE INDEX fanbox_embeds_post_id_idx ON fanbox_embeds USING btree(post_id); + +CREATE INDEX fanbox_embeds_added_idx ON fanbox_embeds USING btree(added); + +CREATE INDEX fanbox_embeds_type_idx ON fanbox_embeds USING btree(type); + +CREATE INDEX fanbox_fancards_user_id_idx ON fanbox_fancards USING btree(user_id); + +CREATE UNIQUE INDEX fanbox_fancards_null_file_id_user_id_price_unique_idx ON fanbox_fancards(user_id, price) +WHERE + file_id IS NULL; diff --git a/db/schema/public/posts/posts.sql b/db/schema/public/posts/posts.sql new file mode 100644 index 0000000..02e18e4 --- /dev/null +++ b/db/schema/public/posts/posts.sql @@ -0,0 +1,115 @@ +-- Posts +CREATE TABLE posts( + "id" varchar(255) NOT NULL, + "user" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "title" text NOT NULL DEFAULT '', + "content" text NOT NULL DEFAULT '', + "embed" jsonb NOT NULL DEFAULT '{}', + "shared_file" boolean NOT NULL DEFAULT '0', + "added" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" timestamp, + "edited" timestamp, + "file" jsonb NOT NULL, + "attachments" jsonb[] NOT NULL, + poll jsonb, + captions jsonb, + -- wtf is this type? + tags _CITEXT, + PRIMARY KEY (id, service) +); + +CREATE TABLE revisions( + "revision_id" serial PRIMARY KEY, + "id" varchar(255) NOT NULL, + "user" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "title" text NOT NULL DEFAULT '', + "content" text NOT NULL DEFAULT '', + "embed" jsonb NOT NULL DEFAULT '{}', + "shared_file" boolean NOT NULL DEFAULT '0', + "added" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "published" timestamp, + "edited" timestamp, + "file" jsonb NOT NULL, + "attachments" jsonb[] NOT NULL, + poll jsonb, + -- wtf is this type? + tags _CITEXT, + captions jsonb +); + +CREATE TABLE introductory_messages( + service varchar NOT NULL, + user_id varchar NOT NULL, + hash varchar NOT NULL, + content varchar NOT NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (service, user_id, hash) +); + +CREATE TABLE posts_incomplete_rewards( + id varchar(255) NOT NULL, + service varchar(20) NOT NULL, + last_checked_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + incomplete_attachments_info jsonb NOT NULL DEFAULT '{}' ::jsonb, + "user" varchar(255) NULL, + CONSTRAINT posts_incomplete_rewards_pkey PRIMARY KEY (id, service) +); + +CREATE TABLE posts_added_max( + "user" varchar NOT NULL, + service varchar NOT NULL, + added timestamp NOT NULL, + CONSTRAINT posts_added_max_pkey PRIMARY KEY ("user", service) +); + +CREATE TRIGGER posts_added_max + AFTER INSERT OR UPDATE ON posts + FOR EACH ROW + EXECUTE FUNCTION posts_added_max(); + +CREATE TABLE public_posts( + post_id text NOT NULL, + creator_id text NOT NULL, + service text NOT NULL, + title text NOT NULL, + body text NOT NULL, + tier_price_required text, + tier_required text[], + published_at timestamp without time zone, + edited_at timestamp without time zone, + deleted_at timestamp without time zone, + tags text[], + like_count integer, + comment_count integer, + is_public boolean, + is_nsfw boolean, + refreshed_at timestamp without time zone, + buy_price text, + CONSTRAINT public_posts_pkey PRIMARY KEY (post_id, service) +); + +CREATE INDEX id_idx ON posts USING HASH ("id"); + +CREATE INDEX service_idx ON posts USING btree("service"); + +CREATE INDEX added_idx ON posts USING btree("added"); + +CREATE INDEX published_idx ON posts USING btree("published"); + +CREATE INDEX user_idx ON posts USING btree("user"); + +CREATE INDEX updated_idx ON posts USING btree("user", "service", "added"); + +CREATE INDEX posts_tags_idx ON public.posts USING gin(tags); + +CREATE INDEX posts_user_published_id_idx ON posts USING btree("user", published, id); + +CREATE INDEX posts_incomplete_rewards_service_user_idx ON posts_incomplete_rewards USING btree(service, "user"); + +CREATE INDEX revisions_id_idx ON revisions USING HASH (id); + +CREATE INDEX introductory_messages_user_id_added_idx ON introductory_messages USING btree(user_id, added); + +CREATE INDEX public_posts_creator_id_service_idx ON public_posts USING btree(service, creator_id); diff --git a/db/schema/public/posts/triggers.sql b/db/schema/public/posts/triggers.sql new file mode 100644 index 0000000..dbcfac7 --- /dev/null +++ b/db/schema/public/posts/triggers.sql @@ -0,0 +1,27 @@ +CREATE FUNCTION posts_added_max() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO posts_added_max AS pam("user", service, added) + SELECT + "user", + service, + max(added) AS added + FROM + posts + WHERE + posts.service = NEW.service + AND posts."user" = NEW."user" + GROUP BY + "user", + service + ON CONFLICT(service, + "user") + DO UPDATE SET + added = EXCLUDED.added + WHERE + EXCLUDED.added > pam.added; + RETURN NULL; +END; +$$; diff --git a/db/schema/public/schema.sql b/db/schema/public/schema.sql new file mode 100644 index 0000000..8414400 --- /dev/null +++ b/db/schema/public/schema.sql @@ -0,0 +1,157 @@ +-- Booru bans +CREATE TABLE dnp( + "id" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + "import" boolean NOT NULL DEFAULT TRUE +); + +-- Flags +CREATE TABLE booru_flags( + "id" varchar(255) NOT NULL, + "user" varchar(255) NOT NULL, + "service" varchar(20) NOT NULL, + PRIMARY KEY (id, "user", service) +); + +-- Board +CREATE TABLE board_replies( + "reply" integer NOT NULL, + "in" integer NOT NULL +); + +-- Requests +CREATE TYPE request_status AS ENUM( + 'open', + 'fulfilled', + 'closed' +); + +CREATE TABLE requests( + "id" serial PRIMARY KEY, + "service" varchar(20) NOT NULL, + "user" varchar(255) NOT NULL, + "post_id" varchar(255), + "title" text NOT NULL, + "description" text NOT NULL DEFAULT '', + "created" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "image" text, + "price" numeric NOT NULL, + "votes" integer NOT NULL DEFAULT 1, + "ips" text[] NOT NULL, + "status" request_status NOT NULL DEFAULT 'open' +); + +CREATE request_title_idx ON requests +USING btree( + "title" +); + +CREATE request_service_idx ON requests +USING btree( + "service" +); + +CREATE request_votes_idx ON requests +USING btree( + "votes" +); + +CREATE request_created_idx ON requests +USING btree( + "created" +); + +CREATE request_price_idx ON requests +USING btree( + "price" +); + +CREATE request_status_idx ON requests +USING btree( + "status" +); + +-- Request Subscriptions +CREATE TABLE request_subscriptions( + "request_id" numeric NOT NULL, + "endpoint" text NOT NULL, + "expirationTime" numeric, + "keys" jsonb NOT NULL +); + +CREATE INDEX request_id_idx ON request_subscriptions USING btree("request_id"); + +CREATE TABLE saved_session_keys( + id serial PRIMARY KEY, + service varchar NOT NULL, + discord_channel_ids varchar, + encrypted_key varchar NOT NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + dead boolean NOT NULL DEFAULT FALSE, + contributor_id int REFERENCES account(id), + UNIQUE (service, encrypted_key) +); + +CREATE TABLE saved_session_keys_with_hashes( + id serial PRIMARY KEY, + service varchar NOT NULL, + discord_channel_ids varchar, + encrypted_key varchar NOT NULL, + hash varchar NOT NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + dead boolean NOT NULL DEFAULT FALSE, + dead_at timestamp, + contributor_id int REFERENCES account(id), + remote_user_id_hash varchar, + UNIQUE (service, hash) +); + +CREATE TABLE saved_session_key_import_ids( + key_id int NOT NULL, + import_id varchar NOT NULL, + UNIQUE (key_id, import_id) +); + +CREATE TABLE complete_imports( + user_id varchar(255) NOT NULL, + service varchar(20) NOT NULL, + subscription varchar(255) NOT NULL, + last_successful_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + info jsonb NOT NULL DEFAULT '{}' ::jsonb, + CONSTRAINT complete_imports_pkey PRIMARY KEY (user_id, service, subscription) +); + +CREATE INDEX saved_session_keys_contributor_idx ON saved_session_keys USING btree("contributor_id"); + +CREATE INDEX saved_session_keys_with_hashes_contributor_idx ON saved_session_keys_with_hashes USING btree("contributor_id"); + +CREATE INDEX saved_session_keys_with_hashes_dead_idx ON saved_session_keys_with_hashes USING btree("dead"); + +CREATE INDEX saved_session_keys_dead_idx ON saved_session_keys USING btree("dead"); + +CREATE TABLE jobs( + job_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + queue_name text NOT NULL, + priority integer NOT NULL, + consumer_id text, + pids integer[], + started_at timestamp, + last_heartbeat_at timestamp, + job_input jsonb NOT NULL, + job_status jsonb DEFAULT '{}' ::jsonb, + finished_at timestamp, + resuming_at timestamp, + error text +); + +CREATE INDEX jobs_finished_at_queue_name_job_input_key_idx ON jobs(finished_at, queue_name,(job_input ->> 'key')); + +CREATE TABLE posts_forced_reimports( + creator_id text NOT NULL, + service text NOT NULL, + post_id text NOT NULL, + reason text NULL, + created_at timestamp DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT posts_forced_reimports_pkey PRIMARY KEY (creator_id, service, post_id) +); diff --git a/db/schema/public/shares.sql b/db/schema/public/shares.sql new file mode 100644 index 0000000..8038fdd --- /dev/null +++ b/db/schema/public/shares.sql @@ -0,0 +1,33 @@ +CREATE TABLE shares( + id serial4 NOT NULL, + "name" varchar NOT NULL, + description varchar NOT NULL, + uploader int4 NULL, + added timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT shares_pkey PRIMARY KEY (id), + CONSTRAINT shares_uploader_fkey FOREIGN KEY (uploader) REFERENCES account(id) +); + +CREATE INDEX shares_added_idx ON shares USING btree(added); + +CREATE INDEX shares_uploader_idx ON shares USING btree(uploader); + +CREATE TABLE lookup_share_relationships( + share_id int4 NOT NULL, + service varchar NOT NULL, + user_id varchar NOT NULL, + CONSTRAINT lookup_share_relationships_pkey PRIMARY KEY (share_id, service, user_id), + CONSTRAINT lookup_share_relationships_service_user_id_fkey FOREIGN KEY (service, user_id) REFERENCES lookup(service, id), + CONSTRAINT lookup_share_relationships_share_id_fkey FOREIGN KEY (share_id) REFERENCES shares(id) +); + +CREATE TABLE file_share_relationships( + share_id int4 NOT NULL, + upload_url varchar NOT NULL, + upload_id varchar NOT NULL, + file_id int4 NULL, + filename varchar NOT NULL, + CONSTRAINT file_share_relationships_pkey PRIMARY KEY (share_id, upload_id), + CONSTRAINT file_share_relationships_file_id_fkey FOREIGN KEY (file_id) REFERENCES files(id), + CONSTRAINT file_share_relationships_share_id_fkey FOREIGN KEY (share_id) REFERENCES shares(id) +); diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..1a80656 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,79 @@ +services: + postgres: + image: groonga/pgroonga:3.1.6-alpine-16-slim + restart: unless-stopped + environment: + - POSTGRES_DB=kemono + - POSTGRES_USER=kemono + - POSTGRES_PASSWORD=kemono + volumes: + - ./storage/postgres:/var/lib/postgresql/data + command: ["postgres", "-c", "log_statement=all"] + ports: + - '15432:5432' + + redis: + image: redis:7-alpine + restart: always + ports: + - '16379:6379' + + api: + build: + context: . + args: + GIT_COMMIT_HASH: "custom" + BUILD_DATE: "1970.01.01" + restart: unless-stopped + depends_on: + - postgres + - redis + environment: + - FLASK_ENV=development + - KEMONO_SITE=http://localhost:5000 + - UPLOAD_LIMIT=2000000000 + - ARCHIVERHOST=kemono-archiver + - ARCHIVERPORT=80 + - PYTHONUNBUFFERED=1 + - KEMONO_CONFIG=config.example.json + - PYTHONPATH=/app + volumes: + - ./:/app + - ./storage/files:/storage + sysctls: + net.core.somaxconn: 2000 + healthcheck: + test: [ "CMD", "curl", "-f", "http://localhost:80" ] + interval: 60s + timeout: 2m + retries: 3 + start_period: 30s + + command: + [ "python", "-m", "src", "run" ] + + web: + build: + context: . + dockerfile: Dockerfile-client + args: + GIT_COMMIT_HASH: "custom" + BUILD_DATE: "1970.01.01" + restart: unless-stopped + depends_on: + - api + volumes: + - ./:/app + command: + [ "npm", "run", "dev" ] + + nginx: + image: nginx + depends_on: + - web + ports: + - '5000:80' + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + - ./storage/files:/storage + - ./:/app diff --git a/docs/FAQ.md b/docs/FAQ.md index 109074b..81d3206 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,142 +1,9 @@ # Frequently Asked Questions -
    - -### My dump doesn't migrate. - -_This assumes a running setup._ - -
    - -1. Enter into database container: - - ```sh - docker exec \ - --interactive \ - --username=nano \ - --tty kemono-db psql \ - kemonodb - ``` - -
    - -2. Check the contents of the  `posts`  table. - - ```sql - SELECT * FROM posts; - ``` - - _Most likely it has  `0`  rows._ - -
    - -3. Move contents of  `booru_posts`  ➞  `posts` - - ```sql - INSERT INTO posts SELECT * FROM booru_posts ON CONFLICT DO NOTHING; - ``` - -
    - -4. Restart the archiver. - - ```sh - docker restart kemono-archiver - ``` - - If you see a bunch of log entries from  `kemono-db` ,
    - then this indicates that the archiver is doing it's job. - -
    - -5. In case the frontend still doesn't show
    - the artists / posts, clear the redis cache. - - ```sh - docker exec \ - kemono-redis \ - redis-cli \ - FLUSHALL - ``` - -
    -
    - -### How do I git modules? - -_This assumes you haven't cloned the repository recursively._ - -
    - -1. Initiate the submodules - - ```sh - git submodule init - git submodule update \ - --recursive \ - --init - ``` - -
    - -2. Switch to the archiver folder and
    - add your fork to the remotes list. - - ```sh - cd archiver - git remote add - ``` - -
    - -3. Now you can interact with Kitsune repo the same
    - way you do as if it was outside of project folder. - -
    -
    - -### How do I import from db dump? - -
    - -1. Retrieve a database dump. - -
    - -2. Run the following in the folder of said dump. - - ```sh - cat db-filename.dump \ - | gunzip \ - | docker exec \ - --interactive kemono-db psql \ - --username=nano kemonodb - ``` - -
    - -3. Restart the archiver to trigger migrations. - - ```sh - docker restart kemono-archiver - ``` - -
    - - If that didn't start the migrations, refer
    - to  [`My Dump Doesn't Migrate`]  section. - -
    -
    - -### How do I put files into nginx container? - -
    +## How do I put files into nginx container? 1. Retrieve the files in required folder structure. -
    - 2. Copy them into nginx image. ```sh @@ -144,8 +11,6 @@ _This assumes you haven't cloned the repository recursively._ cp ./ kemono-nginx:/storage ``` -
    - 3. Add required permissions to that folder. ```sh @@ -155,8 +20,118 @@ _This assumes you haven't cloned the repository recursively._ nginx /storage ``` -
    +## How Do I Install Python 3.12 on Ubuntu 22? +Through a PPA (Personal Package Archives). - +1. Install required tooling and add the PPA: -[`My Dump Doesn't Migrate`]: #my-dump-doesnt-migrate + ```sh + sudo apt install --assume-yes software-properties-common + sudo add-apt-repository ppa:deadsnakes/ppa + ``` + +2. Update local `apt` listing and install required python dependencies: + + ```sh + sudo apt update + sudo apt install \ + python3.12 \ + python3.12-dev \ + python3.12-distutils + ``` + +3. Confirm python 3.12 is installed + ```sh + which python3.12 + ``` + +## How Do I Install Node.js 22.14? + +### For Linux/macOS: + +1. Install NVM: + + ```sh + # Using curl + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + + # OR using wget + wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash + ``` + +2. Add NVM to your shell configuration: + + The installer should automatically add the necessary configuration to your shell profile + (`.bashrc`, `.zshrc`, etc.), but if it doesn't, add these lines manually: + + ```sh + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm + [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion + ``` + +3. Either restart your terminal or source your profile: + + ```sh + # For bash + source ~/.bashrc + + # For zsh + source ~/.zshrc + ``` + +4. Verify NVM installation: + + ```sh + nvm --version + ``` + +5. Install Node.js 22.14: + + ```sh + nvm install 22.14 + ``` + +6. Set it as the default (optional): + + ```sh + nvm alias default 22.14 + ``` + +7. Verify the installation: + + ```sh + node --version # Should output v22.14.0 + npm --version # NPM is included with Node.js 10.9.2+ + ``` + +8. If you need to switch between Node.js versions: + + ```sh + nvm ls + + nvm use 22.14 + + nvm use default + ``` + +### For Windows: + +Windows users can install Node.js directly using the official installer: + +1. Go to the Node.js downloads page: + https://nodejs.org/en/download/current + +2. Download the Windows installer for v22.14.0: + - Look for `node-v22.14.X-x64.msi` (64-bit installer) + - The "X" represents the patch version which may change + +3. Run the downloaded MSI file and follow the installation wizard. + +4. Verify the installation by opening Command Prompt or PowerShell and running: + ``` + node --version + npm --version + ``` + +5. The installer includes npm, so no separate installation is needed. npm version should be 10.9.2 or newer. \ No newline at end of file diff --git a/docs/code-style.md b/docs/code-style.md new file mode 100644 index 0000000..f5a27c7 --- /dev/null +++ b/docs/code-style.md @@ -0,0 +1,15 @@ +# Code Style + +## General + +- Prepend all control flow keywords with an empty line. + +## SQL + +- Always declare aliases with `AS` construct. +- No star selects allowed. + +## Python + +- Prefix all `TypedDict` declarations with `TD`.
    + Otherwise it's easy to mix them up wtih actual classes and then do `isinstance()` accidentally. diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..6a40317 --- /dev/null +++ b/docs/database.md @@ -0,0 +1,37 @@ +# Database + +## Schema reference + +The folder `/db/schema/` holds the information about relevant symbols for the codebase in ``.
    +This information is plain SQL files with only symbol declarations without any data-modifying queries.
    + +The main usecase is to be able to look into database schema without reving up the docker stack/setting up database.
    +The other perk coming from this is schema reference data is bound to commits, so it allows to diff schema changes
    +before trying to make sense of migrations. + +**From this point on (2024-07-03T00:00:0.000Z) any commit which includes schema-changing migrations
    +must also include changes in the schema reference.** + +## File structure + +There is no particular structure beyond having files in a specific schema folder.
    +But in general small enough declarations without any category go into `schema.sql`,
    +otherwise they go into separate files. If a separate file becomes too big, then it goes into
    +a separate folder split into separate files. + +## Migrations already include this information +They do, but as of time of writing (2024-07-03T00:00:0.000Z) there 75 migration files with a ton of data-changing boilerplate. + +## Why like this? +It's quick, located alongside migrations and (eventually) queries and doesn't require to learn some goofy DSL
    +which will eventually fall behind the new features of Postgresql. + +The alternative considered was relying on [`pg_dump` with `--schema-only` argument](https://www.postgresql.org/docs/current/app-pgdump.html)
    +but it has pretty big drawbacks: +- it requires a functioning and running database to work, which isn't going to always happen during development. +- The output SQL is not a clean one, i.e. it includes system-specific data, which is irrelevant to the repo code.
    + That means it has to be parsed and transformed which is not gonna happen. +- Included symbols make sense in context of the command, but not in the context of repo. I.e. the repo doesn't need to know the user ownership of symbols. +That means the repo has to store all symbol names somewhere in the first place. And that list has to be updated manually anyway. +- The output SQL is written in a desugarized form, which is a dealbreaker since migrations are written in sugar
    +and this disparity only makes it harder to understand them and the whole schema. diff --git a/docs/develop-docker.md b/docs/develop-docker.md new file mode 100644 index 0000000..a5936f0 --- /dev/null +++ b/docs/develop-docker.md @@ -0,0 +1,42 @@ +# Docker Universal Setup + +The quickest way to get started with development is using Docker. This approach works on all operating systems and requires minimal setup. + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) +- [Docker Compose](https://docs.docker.com/compose/install/) + +## Setup Steps + +1. Clone the repository (if you haven't already) + +2. Copy the example configuration file: + + ```sh + cp config.json.example config.json + ``` + +3. Start the containers using Docker Compose: + + ```sh + docker compose up + ``` + +4. Once the containers are running, access the application at: + + [http://localhost:5000](http://localhost:5000) + +## Stop the Environment + +To stop the containers, press `Ctrl+C` in the terminal where Docker Compose is running, or run: + +```sh +docker compose down +``` + +## Notes + +- No local dependencies are required with this method +- This setup is ideal for testing or quick development +- All services (backend, frontend, database) run in containers diff --git a/docs/develop-linux.md b/docs/develop-linux.md new file mode 100644 index 0000000..f2dcd6d --- /dev/null +++ b/docs/develop-linux.md @@ -0,0 +1,147 @@ +# Develop + +For now Docker is a primary way of working on the repo. +However dependencies are still needed to installed locally for +the IDE setup. + +## Requirements: + +Python: 3.12+ +NodeJS: 22.14+ +PostgreSQL: 16+ +Redis: 6+ + +## Installation + +1. Check if node 22.14 or 22.13 is installed in the system: + ``` + node --version + ``` + If fails, follow [installation instructions](./FAQ.md#how-do-i-install-nodejs-2214-using-nvm) + + +2. Check if python 3.12 is installed in the system: + + ```sh + which python 3.12 + ``` + + If no path returned, follow [installation instructions](./FAQ.md#how-do-i-install-python-312-on-ubuntu-22) + +3. Install `virtualenv` package if it's not installed. + + ```sh + pip install --user virtualenv + ``` + +4. Create a virtual environment: + + ```sh + virtualenv python=3.12 venv + ``` + +5. Activate the virtual environment. + + ```sh + # Windows ➞ venv\Scripts\activate + source venv/bin/activate + ``` + +6. Install python packages. + + ```sh + pip install --requirement requirements.txt + ``` + +7. Install `pre-commit` hooks. + + ```sh + pre-commit install --install-hooks + ``` + +## Database Setup + +1. Install PostgreSQL if not already installed. + +2. Create a database and user for the application. + +3. Write in the config.json the credentials + + +## Redis Setup + +1. Install Redis if not already installed. + +2. Verify Redis is running. + +3. Write in the config.json the credentials + + +## File Paths Configuration + +The application requires several directories for storing and serving files: + +1. Make sure you have the directories with files, /data, /thumbnails, /icons: + +2. Serve them with nginx or what is of your choice: + +3. Configure the base url to serve from that file server by setting the following env variables: + + ```env + ICONS_PREPEND + BANNERS_PREPEND + THUMBNAILS_PREPEND + ``` + + +## RUN + +1. Run the API dev server: + + ```sh + python -m src web + ``` +2. Run frontend dev server: + + ```sh + python -m src webpack + ``` + +## Git + +Configure `git` to store credentials: + +```sh +git config credential.helper store +``` + +After the next time creds are accepted, they will be saved on hard drive +as per rules listed in `man git-credential-store`and won't be asked again. + +Alternatively they can be stored temporarily in memory: + +```sh +git config credential.helper cache +``` + +The creds are stored as per rules in `man git-credential-cache`. + +## IDE + +_IDE specific instructions._ + +### VSCode + +1. Copy `.code-workspace` file. + + ```sh + cp \ + configs/workspace.code-workspace.example \ + kemono-2.code-workspace + ``` + +2. Install the recommended extensions. + +[`http://localhost:5000/development`]: http://localhost:5000/development +[`http://localhost:5000/`]: http://localhost:5000/ +[`http://localhost:8000/`]: http://localhost:8000/ \ No newline at end of file diff --git a/docs/develop-windows-client.md b/docs/develop-windows-client.md new file mode 100644 index 0000000..d6e13c1 --- /dev/null +++ b/docs/develop-windows-client.md @@ -0,0 +1,37 @@ +# Windows Client Only Setup with Reverse Proxy + +This guide covers setting up a frontend-only development environment on Windows with a reverse proxy to connect to a remote backend. + +## Requirements + +- NodeJS: 22.14+ +- Git +- Docker + +## Installation + +1. Open your console on the root of the project and check if Node.js 22.14+ is installed: + ``` + node --version + ``` + If not installed or version is older, follow the [Node.js installation instructions](./FAQ.md#how-do-i-install-nodejs-2214) + +2. Clone the repository (if you haven't already) + +3. To setup the configuration, navigate to the client directory and install the necessary packages run the following: + ``` + cp config.example.production.json config.json + cd client + npm i + ``` + +4. Finally to run the development server run: + ``` + npm run dev + ``` + +4. Access the application at: + [http://localhost:5000/](http://localhost:5000/) + + +5. Make sure the images and api calls are being reverse proxied. diff --git a/docs/develop.md b/docs/develop.md index 18f547c..ccf15c0 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -1,121 +1,28 @@ -# Develop +# Development Guide -For now Docker is a primary way of working on the repo. +This guide provides several ways to set up your development environment based on your needs and operating system. -## Installation +## Setup Options -1. Install `virtualenv` package if it's not installed. +### [Docker Universal](./develop-docker.md) +The quickest way to get started with minimal setup: +- Works on all operating systems +- Uses Docker Compose +- No need to install dependencies locally - ```sh - pip install --user virtualenv - ``` -2. Create a virtual environment: +### [Windows Client Only](./develop-windows-client.md) +Frontend development on Windows with reverse proxy: +- Only frontend dependencies required +- Uses reverse proxy to connect to remote backend +- Lightweight setup - ```sh - virtualenv venv - ``` +### [Linux Setup](./develop-linux.md) +Complete development environment for Linux: +- Local dependency installation +- Full development stack +- IDE configuration -2. Activate the virtual environment. +## Getting Help - ```sh - # Windows ➞ venv\Scripts\activate - source venv/bin/activate - ``` - -3. Install python packages. - - ```sh - pip install --requirement requirements.txt - ``` - -4. Install  `pre-commit`  hooks. - - ```sh - pre-commit install --install-hooks - ``` - -### Database - -1. Register an account. - -2. Visit  [`http://localhost:5000/development`] - -3. Click either seeded or random generation. - - _This will start a mock import process,_
    - _which will also populate the database._ - -### Build - -```sh -docker-compose build -docker-compose up --detach -``` - -In a browser, visit  [`http://localhost:8000/`] - -## Manual - -> **TODO** : Write installation and setup instructions - -This assumes you have  `Python 3.8+`  &  `Node 12+`  installed
    -as well as a running **PostgreSQL** server with **Pgroonga**. - -```sh -# Make sure your database is initialized -# cd to kemono directory - -pip install virtualenv -virtualenv venv - -# Windows ➞ venv\Scripts\activate -source venv/bin/activate - -pip install \ - --requirement requirements.txt - -cd client \ - && npm install \ - && npm run build \ - && cd .. -``` - -## Git - -Configure `git` to store credentials: - -```sh -git config credential.helper store -``` - -After the next time creds are accepted, they will be saved on hard drive -as per rules listed in `man git-credential-store`and won't be asked again. - -Alternatively they can be stored temporarily in memory: - -```sh -git config credential.helper cache -``` - -The creds are stored as per rules in `man git-credential-cache`. - -## IDE - -_IDE specific instructions._ - -### VSCode - -1. Copy  `.code-workspace`  file. - - ```sh - cp \ - configs/workspace.code-workspace.example \ - kemono-2.code-workspace - ``` - -2. Install the recommended extensions. - -[`http://localhost:5000/development`]: http://localhost:5000/development -[`http://localhost:5000/`]: http://localhost:5000/ -[`http://localhost:8000/`]: http://localhost:8000/ +For additional assistance, check the [FAQ](./FAQ.md). \ No newline at end of file diff --git a/docs/features/accounts.md b/docs/features/accounts.md index 8d0d4fc..9d347b2 100644 --- a/docs/features/accounts.md +++ b/docs/features/accounts.md @@ -3,7 +3,6 @@ ## Table of contents - [General Description](#general-description) -- [Issues](#issues) ## General Description @@ -26,5 +25,3 @@ The first registered account of the instance gets this role. Administrator can c #### Session Key Management Accounts can access the page which lists all keys set up for autoimport and revoke any of them. - -## Issues diff --git a/docs/projects/_index.md b/docs/projects/_index.md deleted file mode 100644 index d9efcad..0000000 --- a/docs/projects/_index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Projects - -- [Moderation system](./moderation-system.md) diff --git a/docs/projects/favorites1dot5.md b/docs/projects/favorites1dot5.md deleted file mode 100644 index afea33d..0000000 --- a/docs/projects/favorites1dot5.md +++ /dev/null @@ -1,18 +0,0 @@ -# Favorites V1.5 - -## Table of contents - -- [General Description](#general-description) -- [Interfaces](#interfaces) -- [Technical Description](#technical-description) -- [Issues](#issues) - -## General Description - -## Interfaces - -### SQL - -## Technical Description - -## Issues diff --git a/docs/projects/file-upload.md b/docs/projects/file-upload.md deleted file mode 100644 index 40119b9..0000000 --- a/docs/projects/file-upload.md +++ /dev/null @@ -1,30 +0,0 @@ -# File Upload - -## Table of contents - -- [Interfaces](#interfaces) -- [Core information](#core-information) -- [Process](#process) -- [Issues](#issues) - -## Interfaces - -```typescript -interface ManualUpload {} -``` - -## Core information - -## Process - -1. The account goes to `/posts/upload`. -2. Uploads the file. -3. The file gets processed. -4. The file gets sent for review to a moderator. -5. The moderator then decides to discard the upload or approve for public view. -6. ... - -## Issues - -- What happens when one mod approves a file while the other one discards it? -- Unlike DMs the file verification can take very variable amount of time, so there should be separate states for a given upload, like `"pending"`,`"approved"`,`"rejected"`. diff --git a/docs/projects/moderation-system.md b/docs/projects/moderation-system.md deleted file mode 100644 index 7a23482..0000000 --- a/docs/projects/moderation-system.md +++ /dev/null @@ -1,42 +0,0 @@ -# Moderation System - -## Table of contents - -- [General Description](#general-description) -- [Interfaces](#interfaces) -- [Technical Description](#technical-description) -- [Process](#process) -- [Issues](#issues) - -## General Description - -The moderation system allows certain users ("moderators") chosen by the administrator user to perform various tasks. - -## Interfaces - -```typescript -interface Action { - id: string; - account_id: string; - type: string; - categories: string[]; - /** - * A list of resource `id`s affected by the action. - */ - entity_ids: string[]; - status: "completed" | "failed" | "reverted"; - created_at: Date; -} -``` - -## Technical Description - -## Process - -### Moderator - -1. When the role of an account changes to `moderator`, the account gets notified of this. -1. The account then can access `/mod` endpoint, which leads to the moderator dashboard. On this page the mod can see various stats, among them is the list of various `tasks`. -1. Each performed `task` results in an `action`. - -## Issues diff --git a/docs/templates/feature-template.md b/docs/templates/feature-template.md deleted file mode 100644 index 7ff85ef..0000000 --- a/docs/templates/feature-template.md +++ /dev/null @@ -1,10 +0,0 @@ -# Title - -## Table of contents - -- [General Description](#general-description) -- [Issues](#issues) - -## General Description - -## Issues diff --git a/docs/templates/project-template.md b/docs/templates/project-template.md deleted file mode 100644 index 8cba19d..0000000 --- a/docs/templates/project-template.md +++ /dev/null @@ -1,16 +0,0 @@ -# Title - -## Table of contents - -- [General Description](#general-description) -- [Interfaces](#interfaces) -- [Technical Description](#technical-description) -- [Issues](#issues) - -## General Description - -## Interfaces - -## Technical Description - -## Issues diff --git a/docs/todos.md b/docs/todos.md deleted file mode 100644 index 972d0ca..0000000 --- a/docs/todos.md +++ /dev/null @@ -1,31 +0,0 @@ -# TODOs - -## Server - -## Archiver - -- Make dev file import work - -## Client - -### Webpack - -- SASS uses its own module name resolution mechanism which differs from the current webpack setup. Specifically `config.resolve.alias` rules will not apply to filenames in `@use "";` expression. -- Figure out how to pass env variables to stylesheets. -- Figure out how to set up source maps for production. - -### HTML/Templates - -- Find a way to nest macro calls. - -#### `user.html` - -- AJAX search. - -#### `import` pages - -- consolidate them into a single page, since most of them are just placeholders for AJAX scripts. - -### CSS - -### JS diff --git a/nginx.conf b/nginx.conf index 2e58f2c..4717115 100644 --- a/nginx.conf +++ b/nginx.conf @@ -32,18 +32,27 @@ http { root /storage; client_max_body_size 100m; + location @kemono { + proxy_pass http://web:3450; + } + + location @api { + proxy_pass http://api:3449; + } + location ~ "^/archive_files/([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{60})\.(.*)" { proxy_pass http://foobar:5000/extract/data/$1/$2/$1$2$3.$4$is_args$args; } + location /api { + try_files /dev/null @api; + } + location / { try_files $uri @kemono; if ($arg_f) { add_header Content-Disposition "inline; filename=$arg_f"; } } - location @kemono { - proxy_pass http://web; - } } } diff --git a/pyproject.toml b/pyproject.toml index 1d9ae3c..e19b7cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,4 +6,5 @@ target-version = ["py312"] line_length = 120 [tool.mypy] +python_version = "3.12" exclude = "storage" diff --git a/requirements.txt b/requirements.txt index 03f3786..edd66eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,9 @@ bcrypt==4.0.1 beautifulsoup4==4.12.2 nh3==0.2.15 cloudscraper==1.2.71 -dill==0.3.7 Flask==2.3.2 humanize==4.8.0 +jsonschema==4.23.0 murmurhash2==0.2.10 pre-commit==3.7.0 psycopg[binary,pool]>=3.1.10 @@ -16,9 +16,9 @@ requests==2.31.0 httpx[http2,socks]==0.25.2 retry==0.9.2 sentry-sdk[flask]==1.29.2 -orjson==3.9.9 -#uWSGI==2.0.22 -git+https://github.com/unbit/uwsgi.git@0486062811be6f4bbed28e61bcb0d33dfeb2045c#uWSGI +setuptools==75.8.0 +orjson==3.9.15 +uWSGI==2.0.24; platform_system != "Windows" pyyaml==6.0.1 yoyo-migrations==8.2.0 zstandard==0.21.0 diff --git a/schema/config.schema.json b/schema/config.schema.json new file mode 100644 index 0000000..d9c53f1 --- /dev/null +++ b/schema/config.schema.json @@ -0,0 +1,642 @@ +{ + "$id": "configuration", + "title": "Configuration", + "description": "Configuration for Kemono.", + "type": "object", + "additionalProperties": false, + "required": ["site", "development_mode", "automatic_migrations", "webserver"], + "properties": { + "site": { + "description": "Deprecated in favour of `#/webserver/site`.", + "$comment": "`$deprecated` keyword was in introduced in draft `2019-09`, so just using `description` field instead.", + "type": "string" + }, + "favicon": { + "description": "favicon.ico file path.", + "type": "string" + }, + "development_mode": { + "type": "boolean" + }, + "automatic_migrations": { + "type": "boolean" + }, + "sentry_dsn": { + "type": "string" + }, + "sentry_dsn_js": { + "type": "string" + }, + "open_telemetry_endpoint": { + "type": "null" + }, + "ban_url": { + "description": "Kemono3 `BAN` URL prefix for cache purging.", + "anyOf": [ + { "type": "string" }, + { "type": "array", "items": { "type": "string" } } + ] + }, + "enable_notifications": { + "type": "boolean" + }, + "cache_ttl_for_recent_posts": { + "type": "integer" + }, + "webserver": { + "$ref": "#/definitions/webserver" + }, + "database": { + "$ref": "#/definitions/database" + }, + "redis": { + "$ref": "#/definitions/redis" + }, + "archive_server": { + "$ref": "#/definitions/archive-server" + }, + "filehaus": { + "$ref": "#/definitions/filehaus" + } + }, + "definitions": { + "webserver": { + "description": "Configuration for the frontend server.", + "type": "object", + "additionalProperties": false, + "required": ["secret", "ui"], + "properties": { + "secret": { + "description": "Secret key used to encrypt sessions.", + "type": "string" + }, + "workers": { + "description": "Amount of workers to run at once.", + "type": "integer" + }, + "harakiri": { + "$comment": "This one isn't used by server code.", + "type": "integer" + }, + "threads": { + "description": "Amount of thread to run at once.", + "type": "integer" + }, + "ip_security": { + "description": "If you've dealt with how the trust of forwarding IPs works upstream, flip this off.", + "type": "boolean" + }, + "country_header_key": { + "description": "Header for user country iso, generater by DDOS-GUARD.", + "anyOf": [{ "type": "string" }, { "const": "DDG-Connecting-Country" }] + }, + "uwsgi_options": { + "$ref": "#/definitions/uwsgi-options" + }, + "logging": { + "description": "Logging mode.", + "enum": ["DEBUG", "ERROR"] + }, + "site": { + "description": "The URL at which the site is publicly accessible.", + "type": "string" + }, + "static_folder": { + "description": "The location of the resources that will be served.", + "type": "string" + }, + "template_folder": { + "type": "string" + }, + "jinja_bytecode_cache_path": { + "type": "null" + }, + "max_full_text_search_input_len": { + "type": "integer" + }, + "table_sample_bernoulli_sample_size": { + "type": "number" + }, + "extra_pages_to_load_on_posts": { + "type": "integer" + }, + "pages_in_popular": { + "type": "integer" + }, + "earliest_date_for_popular": { + "type": "string" + }, + "use_redis_by_lock_default_on_queries": { + "type": "boolean" + }, + "api": { + "type": "object", + "additionalProperties": false, + "properties": { + "creators_location": { + "type": "string" + } + } + }, + "base_url": { + "type": "string" + }, + "port": { + "description": "The port the site will be served on.", + "type": "integer" + }, + "ui": { + "$ref": "#/definitions/ui" + } + } + }, + "ui": { + "description": "Interface preferences and customization options.", + "type": "object", + "additionalProperties": false, + "required": ["home", "config"], + "properties": { + "home": { + "$ref": "#/definitions/ui-home" + }, + "config": { + "$ref": "#/definitions/ui-config" + }, + "fileservers": { + "type": "array", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { "type": "string" }, + { "anyOf": [{ "type": "integer" }, { "type": "string" }] } + ] + } + }, + "sidebar": { + "type": "object", + "additionalProperties": false, + "properties": { + "disable_filehaus": { + "type": "boolean" + }, + "disable_faq": { + "type": "boolean" + }, + "disable_dms": { + "type": "boolean" + } + } + }, + "sidebar_items": { + "description": "Add custom links to the bottom of the sidebar.", + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["text"], + "properties": { + "header": { "type": "boolean" }, + "text": { "type": "string" }, + "class_name": { "type": "string" }, + "link": { "type": "string" }, + "is_external": { "type": "boolean" }, + "color": { "type": "string" } + } + } + }, + "footer_items": { + "description": "Add custom HTML elements to the footer.", + "type": "array", + "items": { + "type": "string" + } + }, + "banner": { + "description": "Banner HTML. Each entry should be Base64-encoded.", + "type": "object", + "additionalProperties": false, + "properties": { + "global": { + "description": "B64-encoded html fragment.", + "type": "string" + }, + "announcement_global": { + "description": "B64-encoded html fragment.", + "type": "string" + }, + "welcome": { + "description": "B64-encoded html fragment.", + "type": "string" + } + } + }, + "ads": { + "$ref": "#/definitions/ui-ads" + }, + "matomo": { + "$ref": "#/definitions/matomo" + }, + "video_extensions": { + "description": "File extensions recognized as (browser-friendly) video. Will automatically be embedded in post pages.", + "type": "array", + "uniqueItems": true, + "items": { + "enum": [".mp4", ".webm", ".m4v", ".3gp", ".mov"] + } + }, + "files_url_prepend": { + "type": "object", + "additionalProperties": false, + "properties": { + "icons_base_url": { + "type": "string" + }, + "banners_base_url": { + "type": "string" + }, + "thumbnails_base_url": { + "type": "string" + } + } + } + } + }, + "ui-home": { + "type": "object", + "additionalProperties": false, + "properties": { + "site_name": { "type": "string" }, + "welcome_credits": { + "description": "B64-encoded html fragment.", + "type": "string" + }, + "home_background_image": { + "description": "A path to background image on server.", + "type": "string" + }, + "logo_path": { + "type": "string" + }, + "mascot_path": { + "type": "string" + }, + "announcements": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "title": { "type": "string" }, + "date": { "type": "string" }, + "content": { "type": "string" } + } + } + } + } + }, + "ui-config": { + "type": "object", + "additionalProperties": false, + "required": ["paysite_list"], + "properties": { + "paysite_list": { + "type": "array", + "uniqueItems": true, + "items": { + "enum": [ + "patreon", + "fanbox", + "afdian", + "discord", + "fantia", + "boosty", + "gumroad", + "subscribestar", + "dlsite", + "candfans", + "fansly", + "onlyfans" + ] + } + }, + "artists_or_creators": { + "type": "string" + } + } + }, + "ui-ads": { + "description": "Ads preferences. Each spot should be Base64-encoded.", + "type": "object", + "additionalProperties": false, + "properties": { + "header": { + "description": "Base64-encoded html fragment.", + "type": "string" + }, + "middle": { + "description": "Base64-encoded html fragment.", + "type": "string" + }, + "footer": { + "description": "Base64-encoded html fragment.", + "type": "string" + }, + "slider": { + "description": "Base64-encoded html fragment.", + "type": "string" + }, + "video": { + "description": "Base64-encoded JSON list of objects.\nSee https://docs.fluidplayer.com/docs/configuration/ads/#adlist .", + "type": "string" + } + } + }, + "matomo": { + "description": "Matomo preferences.", + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "tracking_domain": { + "type": "string" + }, + "tracking_code": { + "type": "string" + }, + "site_id": { + "type": "integer" + }, + "plain_code": { + "description": "Base64-encoded html fragment.", + "type": "string" + } + } + }, + "database": { + "description": "Database configuration.", + "type": "object", + "additionalProperties": false, + "required": ["host", "user", "password", "database"], + "properties": { + "host": { "type": "string" }, + "port": { "type": "integer" }, + "user": { "type": "string" }, + "password": { "type": "string" }, + "database": { "type": "string" }, + "application_name": { "type": "string" } + } + }, + "redis": { + "type": "object", + "additionalProperties": false, + "required": ["defaults"], + "properties": { + "defaults": { "$ref": "#/definitions/redis-defaults" }, + "compression": { "type": "string" }, + "default_ttl": { "type": "string" }, + "node_options": { + "type": "object", + "additionalProperties": false, + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "db": { + "type": "integer" + }, + "password": { + "type": "string" + } + } + }, + "nodes": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { "type": "string" } + }, + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "port": { + "type": "integer" + }, + "db": { + "type": "integer" + } + } + } + } + ] + }, + "keyspaces": { + "type": "object", + "propertyNames": { + "$ref": "#/definitions/redis-keyspaces" + }, + "additionalProperties": { + "type": "integer" + } + } + } + }, + "redis-defaults": { + "type": "object", + "additionalProperties": false, + "required": ["host"], + "properties": { + "host": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "db": { + "type": "integer" + } + } + }, + "redis-keyspaces": { + "enum": [ + "account", + "saved_key_import_ids", + "saved_keys", + "random_artist_keys", + "top_artists", + "artists_by_update_time", + "artists_faved", + "artists_faved_count", + "artists_recently_faved_count", + "top_artists_recently", + "non_discord_artist_keys", + "non_discord_artists", + "artists_by_service", + "artist", + "artist_post_count", + "artist_post_offset", + "artist_last_updated", + "artist_favorited", + "dms", + "all_dms", + "all_dms_count", + "all_dms_by_query", + "all_dms_by_query_count", + "dms_count", + "unapproved_dms", + "favorite_artists", + "favorite_posts", + "notifications_for_account", + "random_post_keys", + "post", + "next_post", + "previous_post", + "posts_incomplete_rewards", + "post_favorited", + "posts_by_artist", + "posts_by_artists", + "posts_by_favorited_artists", + "comments", + "artist_posts_offset", + "is_post_flagged", + "importer_logs", + "ratelimit", + "all_posts", + "all_post_keys", + "all_shares", + "all_shares_count", + "all_posts_for_query", + "global_post_count", + "global_post_count_for_query", + "lock", + "lock-signal", + "imports", + "share_files", + "account_notifications", + "new_notifications", + "share", + "artist_shares", + "post_revisions", + "post_revision", + "files", + "post_by_id", + "fancards", + "announcements", + "announcement_count", + "artist_share_count", + "discord_channels_for_server", + "discord_posts", + "popular_posts", + "tagged_posts", + "tags", + "file", + "archive_files", + "linked_accounts", + "running_imports", + "importer_configuration" + ] + }, + "archive-server": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "api_url": { + "type": "string" + }, + "serve_files": { + "type": "boolean" + }, + "file_serving_enabled": { + "type": "boolean" + } + } + }, + "filehaus": { + "description": "Filehaus configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "requires_account": { + "description": "If true, an account will be required for uploading.", + "type": "boolean" + }, + "required_roles": { + "description": "Required account roles for uploading. If set to an empty list, no permissions will be required.", + "type": "array", + "uniqueItems": true, + "items": { + "enum": ["administrator", "moderator", "uploader"] + } + }, + "tus": { + "description": "`tusd` configuration.", + "type": "object", + "additionalProperties": false, + "properties": { + "manage": { + "description": "Automatically manage `tusd` on port 1080. If you intend to store uploads on a different server from Kemono3, set a custom URL instead...", + "type": "boolean" + }, + "url": { + "description": "...right here. Note that if you do not allow automatic management and did not set a custom URL, uploads will be blackholed to a demo instance and Filehaus sharing will not function correctly.\nDo note that using `tusd` instances to enable Filehaus functionality requires the `post-create` hook to be pointed at Kemono's `/shares/tus` endpoint.\n`tusd --hooks-enabled-events post-create -hooks-http \"http://127.0.0.1:6942/shares/tus\" -upload-dir=./data`", + "type": "string" + } + } + } + } + }, + "uwsgi-options": { + "description": "Set additional uWSGI options if you want. Overrides any of the other options.", + "type": "object", + "additionalProperties": false, + "properties": { + "cheaper-algo": { + "enum": ["busyness"] + }, + "cheaper": { + "type": "integer" + }, + "cheaper-initial": { + "type": "integer" + }, + "cheaper-overload": { + "type": "integer" + }, + "cheaper-step": { + "type": "integer" + }, + "cheaper-busyness-multiplier": { + "type": "integer" + }, + "cheaper-busyness-min": { + "type": "integer" + }, + "cheaper-busyness-max": { + "type": "integer" + }, + "cheaper-busyness-backlog-alert": { + "type": "integer" + }, + "cheaper-busyness-backlog-step": { + "type": "integer" + }, + "disable-logging": { + "type": "boolean" + } + } + } + } +} diff --git a/schema/config.ts b/schema/config.ts new file mode 100644 index 0000000..f53a04c --- /dev/null +++ b/schema/config.ts @@ -0,0 +1,103 @@ +export interface IConfiguration { + site: string; + sentry_dsn_js?: string; + development_mode: boolean; + automatic_migrations: boolean; + webserver: IServerConfig; + archive_server?: IArchiveServerConfig; +} + +interface IServerConfig { + ui: IUIConfig; + port?: number; + base_url?: string; +} + +interface IUIConfig { + home: IHomeConfig; + favicon?: string; + config: { paysite_list: string[]; artists_or_creators: string }; + matomo?: IMatomoConfig; + sidebar?: ISidebarConfig; + sidebar_items: unknown[]; + footer_items?: unknown[]; + banner?: IBannerConfig; + ads?: IAdsConfig; + files_url_prepend?: { + icons_base_url?: string; + banners_base_url?: string; + thumbnails_base_url?: string; + }; +} + +interface IMatomoConfig { + enabled: boolean; + /** + * b64-encoded string + */ + plain_code: string; + tracking_domain: string; + tracking_code: string; + site_id: string; +} + +interface ISidebarConfig { + disable_dms?: boolean; + disable_faq?: boolean; + disable_filehaus?: boolean; +} + +interface IBannerConfig { + /** + * b64-encoded string + */ + global?: string; + /** + * b64-encoded string + */ + announcement_global?: string; + /** + * b64-encoded string + */ + welcome?: string; +} + +interface IHomeConfig { + site_name?: string; + mascot_path?: string; + logo_path?: string; + /** + * b64-encoded string + */ + welcome_credits?: string; + home_background_image?: string; + announcements?: { title: string; date: string; content: string }[]; +} + +interface IAdsConfig { + /** + * b64-encoded string + */ + header: string; + /** + * b64-encoded string + */ + middle: string; + /** + * b64-encoded string + */ + footer: string; + /** + * b64-encoded string + */ + slider: string; + /** + * Base64-encoded JSON list of objects. + */ + video: string; +} + +interface IArchiveServerConfig { + enabled?: boolean; + file_serving_enabled?: boolean +} diff --git a/schema/public/api.yaml b/schema/public/api.yaml new file mode 100644 index 0000000..87fd959 --- /dev/null +++ b/schema/public/api.yaml @@ -0,0 +1,3329 @@ +openapi: 3.1.0 +info: + title: Kemono API + version: 1.3.0 + contact: + email: contact@kemono.party +servers: + - url: https://kemono.su/api + - url: https://coomer.su/api +tags: + - name: Posts + description: Version one + - name: Creators + - name: Comments + - name: Post Flagging + description: Flag post for re-import + - name: Discord + - name: Favorites + - name: File Search + - name: Misc +paths: + /v2/file/{file_hash}: + get: + description: Overview of the file. + parameters: + - name: file_hash + in: path + description: Hash of the file. + required: true + schema: + $ref: "#/components/schemas/hash-sha256" + responses: + '200': + description: Successfully retrieved file details. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + $ref: "#/components/schemas/archive-info" + '400': + description: There are errors in parameters. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '404': + description: File does not exist. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + patch: + description: Add password to a file if needed + parameters: + - name: file_hash + in: path + description: Hash of the file. + required: true + schema: + $ref: "#/components/schemas/hash-sha256" + requestBody: + required: true + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/request-body-with-data" + - type: object + properties: + data: + type: object + required: + - password + additionalPropeties: false + properties: + password: + type: string + minLength: 1 + responses: + '200': + description: Successfully added a correct password. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + description: Hash of the file which got updated. + $ref: "#/components/schemas/hash-sha256" + '400': + description: There are errors in parameters or the body. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + /v2/account/flags/post: + put: + description: Flag the post for reimport. + security: + - cookieAuth: [ ] + requestBody: + required: true + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/request-body-with-data" + - type: object + properties: + data: + type: object + required: + - service + - profile_id + - post_id + additionalPropeties: false + properties: + service: + type: string + profile_id: + type: string + post_id: + type: string + responses: + '201': + description: Successfully flagged the post. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + type: object + description: Data of flagged post. + required: + - service + - profile_id + - post_id + properties: + service: + type: string + profile_id: + type: string + post_id: + type: string + '400': + description: Request body has errors. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '404': + description: Post doesn't exist. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '409': + description: Post is already flagged. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + /v2/account/administrator/accounts: + get: + description: Get account count. + security: + - cookieAuth: [ ] + parameters: + - name: name + in: query + description: Filter by name + schema: + type: string + - name: role + in: query + description: Filter by role + schema: + type: string + responses: + '200': + description: Successfully counted accounts. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + $ref: "#/components/schemas/non-negative-integer" + '401': + description: User not logged in. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '404': + description: User is not administrator. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + /v2/account/administrator/accounts/{page}: + get: + description: Get accounts at page. + security: + - cookieAuth: [ ] + parameters: + - $ref: "#/components/parameters/path-page" + - name: name + in: query + description: Filter by name + schema: + type: string + - name: role + in: query + description: Filter by role + schema: + type: string + responses: + '200': + description: Successfully gotten accounts at page. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/account" + '400': + description: There are errors in parameters. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '401': + description: User not logged in. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '404': + description: User is not administrator. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + /v2/account/administrator/account/{account_id}: + get: + description: Overview of target account. + security: + - cookieAuth: [ ] + parameters: + - name: account_id + in: path + description: ID of the account. + required: true + schema: + $ref: "#/components/schemas/positive-integer" + responses: + '200': + description: Successfully retrieved target account details. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + $ref: "#/components/schemas/account" + '400': + description: There are errors in parameters. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '401': + description: User not logged in. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '404': + description: User is not administrator or account doesn't exist. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + patch: + description: Change target account details. + security: + - cookieAuth: [ ] + parameters: + - name: account_id + in: path + description: ID of the account. + required: true + schema: + $ref: "#/components/schemas/positive-integer" + requestBody: + required: true + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/request-body-with-data" + - type: object + properties: + data: + type: object + required: + - role + additionalPropeties: false + properties: + role: + type: string + responses: + '200': + description: Successfully changed target account details. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/response-body-success" + - type: object + properties: + data: + description: ID of account which got updated. + $ref: "#/components/schemas/positive-integer" + '400': + description: There are errors in parameters or in the body. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '401': + description: Admin account is not logged in. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '404': + description: Admin account is not administrator or target account doesn't exist. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + '409': + description: Failed to update target account due to a conflict. + content: + application/json: + schema: + $ref: "#/components/schemas/response-body-error" + /v1/creators.txt: + get: + tags: + - Posts + summary: List All Creators + description: List all creators with details. I blame DDG for .txt. + responses: + '200': + description: List of all creators + content: + application/json: + schema: + type: array + items: + type: object + properties: + favorited: + type: integer + description: The number of times this creator has been favorited + id: + type: string + description: The ID of the creator + indexed: + type: number + description: Timestamp when the creator was indexed, Unix time as integer + name: + type: string + description: The name of the creator + service: + type: string + description: The service for the creator + updated: + type: number + description: Timestamp when the creator was last updated, Unix time as integer + example: + - favorited: 1 + id: '21101760' + indexed: 1672534800 + name: RAIGYO + service: fanbox + updated: 1672534800 + /v1/posts: + get: + tags: + - Posts + summary: List recent posts + description: List of recently imported posts + parameters: + - name: q + in: query + description: Search query + schema: + type: string + minLength: 3 + - name: o + in: query + description: Result offset, stepping of 50 is enforced + schema: + type: integer + - name: tag + in: query + description: A list of tags to filter by + schema: + type: array + items: + type: string + responses: + '200': + description: List of recently added posts + content: + application/json: + schema: + type: object + properties: + count: + type: integer + true_count: + type: integer + posts: + type: array + items: + type: object + properties: + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + type: boolean + added: + type: string + format: date-time + published: + type: string + format: date-time + edited: + type: string + format: date-time + file: + type: object + properties: + name: + type: string + path: + type: string + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + example: + - id: '1836570' + user: '6570768' + service: fanbox + title: 今日はFANBOXを始まりました! + content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    + embed: { } + shared_file: false + added: '2021-03-30T18:00:05.973913' + published: '2021-01-24T17:54:38' + edited: '2021-01-24T18:46:15' + file: + name: a99d9674-5490-400e-acca-4bed99590699.jpg + path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg + attachments: [ ] + - id: '1836649' + user: '6570768' + service: fanbox + title: 忍ちゃん 脇コキ差分 + content: '' + embed: { } + shared_file: false + added: '2021-03-30T17:59:57.815397' + published: '2021-01-24T18:23:12' + edited: '2023-01-04T14:45:19' + file: + name: 4c5615f9-be74-4fa7-b88d-168fd37a2824.jpg + path: /d0/3c/d03c893927521536646619f5fb33426aa4b82dc12869865d6d666932755d9acd.jpg + attachments: + - name: 9cc982e4-1d94-4a1a-ac62-3dddd29f881c.png + path: /d7/4d/d74d1727f2c3fcf7a7cc2d244d677d93b4cc562a56904765e4e708523b34fb4c.png + - name: ab0e17d7-52e5-42c2-925b-5cfdb451df0c.png + path: /1b/67/1b677a8c0525e386bf2b2f013e36e29e4033feb2308798e4e5e3780da6c0e815.png + /v1/posts/random: + get: + description: Get a random post + responses: + '200': + description: A random post. + content: + application/json: + schema: + type: object + properties: + service: + type: string + artist_id: + type: string + post_id: + type: string + '404': + description: Not random psot found. + content: + application/json: + schema: + $ref: "#/components/schemas/error" + /v1/posts/popular: + get: + description: Get popular posts + parameters: + - name: date + in: query + description: Base date of the list + required: true + schema: + type: string + - name: period + in: query + description: Period scale of the list + required: true + schema: + enum: + - recent + - day + - week + - month + - $ref: "#/components/parameters/query-o" + + responses: + '200': + description: A list of popular posts. + content: + application/json: + schema: + type: object + properties: + info: + type: object + properties: + date: + type: string + min_date: + type: string + max_date: + type: string + navigation_dates: + type: object + propertyNames: + enum: + - recent + - day + - week + - month + additionalProperties: + type: array + prefixItems: + - type: string + - type: string + - type: string + range_desc: + type: string + scale: + enum: + - recent + - day + - week + - month + props: + type: object + properties: + currentPage: + const: popular_posts + today: + type: string + earliest_date_for_popular: + type: string + limit: + type: integer + count: + type: integer + results: + type: array + items: + $ref: "#/components/schemas/post-with-fav-count" + base: + type: object + additionalProperties: + type: string + result_previews: + type: array + items: + anyOf: + - type: object + properties: + type: + const: thumbnail + server: + type: string + name: + type: string + path: + type: string + - type: object + properties: + type: + const: embed + url: + type: string + subject: + type: string + description: + type: string + result_attachments: + type: array + items: + type: object + properties: + server: + type: string + name: + type: string + path: + type: string + result_is_image: + type: array + items: + type: boolean + /v1/posts/tags: + get: + description: Get tags + responses: + '200': + description: A list of post tags. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentpage: + const: "tags" + tags: + type: array + items: + $ref: "#/components/schemas/tag" + /v1/{service}/post/{post_id}: + get: + description: Get a post by ID + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-post-id" + responses: + '200': + description: Post data. + content: + application/json: + schema: + type: object + properties: + service: + type: string + artist_id: + type: string + post_id: + type: string + '404': + description: No post found + content: + application/json: + schema: + $ref: "#/components/schemas/error" + /v1/{service}/user/{creator_id}/profile: + get: + summary: Get a creator + tags: + - Creators + parameters: + - name: service + in: path + description: The service where the creator is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + responses: + '200': + description: Creator details retrieved successfully + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: The ID of the creator + public_id: + type: + - string + - null + description: The public ID of the creator + service: + type: string + description: The service where the creator is located + name: + type: string + description: The creator's display name + indexed: + type: string + format: date-time + description: The time the creator was last indexed + updated: + type: string + format: date-time + description: The time the creator was last updated + '404': + description: The creator could not be found + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: The error message + enum: ["Creator not found."] + /v1/{service}/user/{creator_id}: + get: + summary: Get a list of creator posts + tags: + - Posts + parameters: + - name: service + in: path + description: The service where the post is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + - name: q + in: query + description: Search query + schema: + type: string + minLength: 3 + - name: o + in: query + description: Result offset, stepping of 50 is enforced + schema: + type: integer + responses: + '200': + description: Post details retrieved successfully + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + type: boolean + added: + type: string + format: date-time + published: + type: string + format: date-time + edited: + type: string + format: date-time + file: + type: object + properties: + name: + type: string + path: + type: string + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + example: + - id: '1836570' + user: '6570768' + service: fanbox + title: 今日はFANBOXを始まりました! + content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    + embed: { } + shared_file: false + added: '2021-03-30T18:00:05.973913' + published: '2021-01-24T17:54:38' + edited: '2021-01-24T18:46:15' + file: + name: a99d9674-5490-400e-acca-4bed99590699.jpg + path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg + attachments: [ ] + - id: '1836649' + user: '6570768' + service: fanbox + title: 忍ちゃん 脇コキ差分 + content: '' + embed: { } + shared_file: false + added: '2021-03-30T17:59:57.815397' + published: '2021-01-24T18:23:12' + edited: '2023-01-04T14:45:19' + file: + name: 4c5615f9-be74-4fa7-b88d-168fd37a2824.jpg + path: /d0/3c/d03c893927521536646619f5fb33426aa4b82dc12869865d6d666932755d9acd.jpg + attachments: + - name: 9cc982e4-1d94-4a1a-ac62-3dddd29f881c.png + path: /d7/4d/d74d1727f2c3fcf7a7cc2d244d677d93b4cc562a56904765e4e708523b34fb4c.png + - name: ab0e17d7-52e5-42c2-925b-5cfdb451df0c.png + path: /1b/67/1b677a8c0525e386bf2b2f013e36e29e4033feb2308798e4e5e3780da6c0e815.png + '400': + description: Offset provided which is not a multiple of 50 + '404': + description: The creator could not be found + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: The error message + enum: ["Creator not found."] + /v1/{service}/user/{creator_id}/announcements: + get: + summary: Get creator announcements + tags: + - Posts + parameters: + - name: service + in: path + required: true + description: The service name + schema: + type: string + - name: creator_id + in: path + required: true + description: The creator's ID + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + service: + type: string + user_id: + type: string + hash: + type: string + description: sha256 + content: + type: string + added: + type: string + format: date-time + description: isoformat UTC + example: + - service: patreon + user_id: '8693043' + hash: 820b7397c7f75efb13c4a8aa5d4aacfbb200749f3e1cec16e9f2951d158be8c2 + content: Hey guys, thank you so much for your support, that means a lot to me! + added: '2023-01-31T05:16:15.462035' + '404': + description: Artist not found + /v1/{service}/user/{creator_id}/fancards: + get: + summary: Get fancards by creator, fanbox only + tags: + - Posts + parameters: + - name: service + in: path + required: true + description: The service name, has to be "fanbox" + schema: + type: string + - name: creator_id + in: path + required: true + description: The creator's ID + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + user_id: + type: string + file_id: + type: integer + hash: + type: string + mtime: + type: string + format: date-time + ctime: + type: string + format: date-time + mime: + type: string + ext: + type: string + added: + type: string + format: date-time + size: + type: integer + ihash: + type: string + path: + type: string + server: + type: string + example: + - id: 108058645 + user_id: '3316400' + file_id: 108058645 + hash: 727bf3f0d774a98c80cf6c76c3fb0e049522b88eb7f02c8d3fc59bae20439fcf + mtime: '2023-05-23T15:09:43.941195' + ctime: '2023-05-23T15:09:43.941195' + mime: image/jpeg + ext: .jpg + added: '2023-05-23T15:09:43.960578' + size: 339710 + ihash: null + - id: 103286760 + user_id: '3316400' + file_id: 103286760 + hash: 8b0d0f1be38efab9306b32c7b14b74ddd92a2513026c859a280fe737980a467d + mtime: '2023-04-26T14:16:53.205183' + ctime: '2023-04-26T14:16:53.205183' + mime: image/jpeg + ext: .jpg + added: '2023-04-26T14:16:53.289143' + size: 339764 + ihash: null + '404': + description: Artist not found + /v1/{service}/user/{creator_id}/links: + get: + summary: Get a creator's linked accounts + tags: + - Creators + parameters: + - name: service + in: path + description: The service where the creator is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + responses: + '200': + description: Linked accounts retrieved successfully + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + description: The ID of the creator + public_id: + type: + - string + - null + description: The public ID of the creator + service: + type: string + description: The service where the creator is located + name: + type: string + description: The creator's display name + indexed: + type: string + format: date-time + description: The time the creator was last indexed + updated: + type: string + format: date-time + description: The time the creator was last updated + '404': + description: The creator could not be found + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: The error message + enum: ["Creator not found."] + delete: + description: Remove artist from linked accounts. Requires admin privilegies. + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + responses: + '204': + description: Artist's link was successfuly removed. + content: + plain/text: + schema: + const: "" + '404': + description: Insufficient privilegies. + content: + plain/text: + schema: + const: "" + /v1/{service}/user/{creator_id}/links/new: + get: + description: Add links to the artist + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + responses: + '200': + description: The data for the new link. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + id: + type: string + service: + type: string + artist: + $ref: "#/components/schemas/artist" + share_count: + $ref: "#/components/schemas/non-negative-integer" + dm_count: + $ref: "#/components/schemas/non-negative-integer" + has_links: + enum: + - ✔️ + - "0" + display_data: + type: object + properties: + service: + type: string + href: + type: string + base: + type: object + properties: + service: + type: string + artist_id: + type: string + post: + description: Add links to the artist + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - service + - artist_id + properties: + service: + type: string + artist_id: + type: string + reason: + type: string + maxLength: 140 + responses: + '200': + description: The link request added to moderation queue. + content: + application/json: + schema: + type: object + properties: + message: + type: string + props: + type: object + properties: + id: + type: string + service: + type: string + artist: + $ref: "#/components/schemas/artist" + share_count: + $ref: "#/components/schemas/non-negative-integer" + has_links: + enum: + - ✔️ + - "0" + display_data: + type: object + properties: + service: + type: string + href: + type: string + '400': + description: Failed to added the new link due to input errors. + content: + application/json: + schema: + type: object + properties: + error: + type: text + /v1/{service}/user/{creator_id}/tags: + get: + description: Tags of profile + tags: + - Creators + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + responses: + '200': + description: Found the tags for the profile + content: + application/json: + schema: + type: object + properties: + props: + display_data: + type: object + properties: + service: string + href: string + artist: + $ref: "#/components/schemas/artist" + service: + type: string + id: + type: string + share_count: + type: integer + dm_count: + type: integer + has_links: + # gr8 API design + enum: + - ✔️ + - "0" + tags: + type: array + items: + $ref: "#/components/schemas/tag" + service: + type: string + artist: + $ref: "#/components/schemas/artist" + /v1/{service}/user/{creator_id}/shares: + get: + description: Shares of the artist + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + - $ref: "#/components/parameters/query-o" + responses: + '200': + description: Found the shares for the artist + content: + application/json: + schema: + type: object + properties: + results: + type: array + items: + $ref: "#/components/schemas/share" + props: + display_data: + type: object + properties: + service: + type: string + href: + type: string + service: + type: string + artist: + $ref: "#/components/schemas/artist" + id: + type: string + dm_count: + type: integer + share_count: + type: integer + has_links: + enum: + - ✔️ + - "0" + base: + type: object + properties: + service: + type: string + artist_id: + type: string + /v1/{service}/user/{creator_id}/dms: + get: + description: Direct messages of profile + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + responses: + '200': + description: Found direct messages for the profile + content: + application/json: + schema: + type: object + properties: + props: + id: + type: string + service: + type: string + artist: + $ref: "#/components/schemas/artist" + display_data: + type: object + properties: + service: + type: string + href: + type: string + share_count: + type: integer + dm_count: + type: integer + dms: + type: array + items: + $ref: "#/components/schemas/approved-dm" + has_links: + enum: + - ✔️ + - "0" + /v1/{service}/user/{creator_id}/posts-legacy: + get: + description: A duct-tape endpoint which also returns count for pagination component. + parameters: + - name: service + in: path + required: true + description: The service name + schema: + type: string + - name: creator_id + in: path + required: true + description: The profiles's ID + schema: + type: string + - name: tag + in: query + description: A list of post tags + schema: + type: array + responses: + '200': + description: Found posts of the profile + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: posts + id: + type: string + service: + type: string + name: + type: string + count: + type: integer + limit: + type: integer + artist: + $ref: "#/components/schemas/artist" + display_data: + type: object + properties: + service: + type: string + href: + type: string + dm_count: + type: integer + share_count: + type: integer + has_links: + type: string + base: + type: object + results: + type: array + items: + $ref: "#/components/schemas/post" + result_previews: + type: array + items: + type: object + result_attachments: + type: array + items: + type: object + result_is_image: + type: array + items: + type: boolean + disable_service_icons: + const: true + /v1/{service}/user/{creator_id}/post/{post_id}: + get: + summary: Get a specific post + tags: + - Posts + parameters: + - name: service + in: path + required: true + description: The service name + schema: + type: string + - name: creator_id + in: path + required: true + description: The creator's ID + schema: + type: string + - name: post_id + in: path + required: true + description: The post ID + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + post: + type: object + properties: + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + type: boolean + added: + type: string + format: date-time + published: + type: string + format: date-time + edited: + type: string + format: date-time + file: + type: object + properties: + name: + type: string + path: + type: string + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + next: + type: string + prev: + type: string + attachments: + type: array + previews: + type: array + videos: + type: array + props: + type: object + properties: + service: + type: string + flagged: + type: integer + revisions: + type: array + items: + $ref: "#/components/schemas/post-revision" + example: + post: + id: '1836570' + user: '6570768' + service: fanbox + title: 今日はFANBOXを始まりました! + content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    + embed: { } + shared_file: false + added: '2021-03-30T18:00:05.973913' + published: '2021-01-24T17:54:38' + edited: '2021-01-24T18:46:15' + file: + name: a99d9674-5490-400e-acca-4bed99590699.jpg + path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg + attachments: [ ] + next: null + prev: '1836649' + '404': + description: Post not found + /v1/{service}/user/{creator_id}/post/{post_id}/revision/{revision_id}: + get: + description: Get revision of a post + parameters: + - $ref: "#/components/parameters/path-service" + - $ref: "#/components/parameters/path-creator-id" + - $ref: "#/components/parameters/path-post-id" + - name: revision_id + in: path + description: ID of the revision + required: true + schema: + type: string + responses: + '200': + description: A revision of the post. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: revisions + service: + type: string + artist: + $ref: "#/components/schemas/artist" + flagged: + $ref: "#/components/schemas/non-negative-integer" + revisions: + type: array + items: + $ref: "#/components/schemas/post-revision" + post: + $ref: "#/components/schemas/post-revision" + comments: + type: array + items: + $ref: "#/components/schemas/comment" + result_previews: + type: array + result_attachments: + type: array + videos: + type: array + archives_enabled: + type: boolean + '404': + description: Failed to find the revision of the post. + content: + application/json: + schema: + $ref: "#/components/schemas/error" + /v1/discord/channel/{channel_id}: + get: + tags: + - Discord + summary: Get Discord channel posts by offset + parameters: + - name: channel_id + in: path + description: ID of the Discord channel + required: true + schema: + type: string + - name: o + in: query + description: Result offset, stepping of 150 is enforced + schema: + type: integer + responses: + '200': + description: Discord channel found + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + author: + type: object + properties: + id: + type: string + avatar: + type: string + username: + type: string + public_flags: + type: integer + discriminator: + type: string + server: + type: string + channel: + type: string + content: + type: string + added: + type: string + format: date-time + published: + type: string + format: date-time + edited: + type: string + format: date-time + embeds: + type: array + items: { } + mentions: + type: array + items: { } + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + example: + - id: '942909658610413578' + author: + id: '421590382300889088' + avatar: 0956f3dc18eba7da9daedc4e50fb96d0 + username: Merry + public_flags: 0 + discriminator: '7849' + server: '455285536341491714' + channel: '455287420959850496' + content: '@everyone Happy Valentine’s Day! 💜✨' + added: '2022-02-15T01:26:12.708959' + published: '2022-02-14T22:26:21.027000' + edited: null + embeds: [ ] + mentions: [ ] + attachments: [ ] + - id: '942909571947712594' + author: + id: '421590382300889088' + avatar: 0956f3dc18eba7da9daedc4e50fb96d0 + username: Merry + public_flags: 0 + discriminator: '7849' + server: '455285536341491714' + channel: '455287420959850496' + content: '' + added: '2022-02-15T01:26:13.006228' + published: '2022-02-14T22:26:00.365000' + edited: null + embeds: [ ] + mentions: [ ] + attachments: + - name: sofa_03.png + path: /3b/4e/3b4ed5aabdd85b26fbbc3ee9b0e5649df69167efe26b5abc24cc2a1159f446d4.png + '404': + description: Discord channel not found + /v1/discord/channel/lookup/{discord_server}: + get: + tags: + - Discord + summary: Lookup Discord channels + parameters: + - name: discord_server + in: path + description: Discord Server ID + required: true + schema: + type: string + responses: + '200': + description: Discord channels found + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + example: + - id: '455285536341491716' + name: news + - id: '455287420959850496' + name: nyarla-lewds + '404': + description: Discord server not found + /v1/authentication/register: + post: + description: Register an account + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + password: + type: string + confirm_password: + type: string + favorites_json: + type: string + responses: + '200': + description: Successfully registered. + content: + application/json: + schema: + const: true + '400': + description: Failed to register due to user errors. + content: + application/json: + schema: + $ref: "#/components/schemas/error" + /v1/authentication/login: + post: + description: Sign in to account + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + password: + type: string + responses: + '200': + description: Succefully logged in. + content: + application/json: + schema: + $ref: "#/components/schemas/account" + '400': + description: Failed to log in due to user errors. + content: + application/json: + schema: + $ref: "#/components/schemas/error" + '409': + description: Already logged in. + content: + application/json: + schema: + $ref: "#/components/schemas/error" + /v1/authentication/logout: + post: + description: Logout from account + responses: + '200': + description: Succefuuly logged out from account. + content: + application/json: + schema: + const: true + /v1/account: + get: + description: Get account data + security: + - cookieAuth: [ ] + responses: + '200': + description: Account data. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: account + title: + const: Your account page + account: + $ref: "#/components/schemas/account" + notifications_count: + $ref: "#/components/schemas/non-negative-integer" + /v1/account/change_password: + post: + description: Change account password + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - current-password + - new-password + - new-password-confirmation + properties: + current-password: + type: string + new-password: + type: string + new-password-confirmation: + type: string + responses: + '200': + description: Successfully changed account password. + content: + application/json: + schema: + const: true + /v1/account/notifications: + get: + description: Get account notifications + security: + - cookieAuth: [ ] + responses: + '200': + description: A list of account notifications. + content: + application/json: + schema: + type: object + properties: + currentPage: + const: account + notifications: + type: array + items: + $ref: "#/components/schemas/notification" + /v1/account/keys: + get: + description: Get account autoimport keys + security: + - cookieAuth: [ ] + responses: + '200': + description: A list of account keys. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: account + title: + const: Your service keys + service_keys: + type: array + items: + $ref: "#/components/schemas/service-key" + import_ids: + type: array + items: + type: object + properties: + key_id: + type: string + import_id: + type: string + post: + security: + - cookieAuth: [ ] + description: Revoke account autoimport keys + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + revoke: + type: array + items: + $ref: "#/components/schemas/positive-integer" + responses: + '200': + description: Account import keys revoked. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: account + redirect: + const: /account/keys + message: + const: "Success!" + /v1/account/favorites: + get: + tags: + - Favorites + security: + - cookieAuth: [ ] + summary: List Account Favorites + description: List account favorites (posts or creators) for the authenticated user (cookie session) + parameters: + - name: type + in: query + description: Type of favorites to list (post or creator (artist) ) + schema: + type: string + enum: + - post + - artist + responses: + '200': + description: List of account favorites + content: + application/json: + schema: + type: array + items: + type: object + properties: + faved_seq: + type: integer + description: The sequence number of the favorite + id: + type: string + description: The ID of the favorite (post or creator) + indexed: + type: string + description: Timestamp when the creator was indexed isoformat + last_imported: + type: string + description: Timestamp when the creator was last imported + name: + type: string + description: The name of the creator + service: + type: string + description: The service where the creator is located + updated: + type: string + description: Timestamp when the creator was last updated + '401': + $ref: '#/components/schemas/401' + /v1/account/posts/upload: + get: + description: Upload posts. + security: + - cookieAuth: [ ] + responses: + '200': + description: Upload posts maybe??? + content: + application/json: + schema: + type: object + properties: + currentPage: + const: posts + /v1/account/review_dms: + get: + description: Get DMs for review. + security: + - cookieAuth: [ ] + parameters: + - name: status + in: query + description: Status of the DM. + schema: + enum: + - ignored + - pending + responses: + '200': + description: A list of unapproved DMs. + content: + application/json: + schema: + type: object + properties: + currentPage: + const: import + account_id: + $ref: "#/components/schemas/positive-integer" + dms: + type: array + items: + $ref: "#/components/schemas/unapproved-dm" + status: + enum: + - ignored + - pending + post: + description: Approve DMs. + security: + - cookieAuth: [ ] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + approved_hashes: + type: array + items: + type: string + delete_ignored: + type: boolean + responses: + '200': + description: Approved DMs. + content: + application/json: + schema: + const: true + /v1/account/moderator/tasks/creator_links: + get: + security: + - cookieAuth: [ ] + description: Get a list of pending artist link requests + responses: + '200': + description: A list of pending artist link requests. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/unapproved-link" + /v1/account/moderator/creator_link_requests/{request_id}/approve: + post: + security: + - cookieAuth: [ ] + description: Approve a new artist link. + responses: + '200': + description: Successfully approved a new artist link. + content: + application/json: + schema: + type: object + properties: + response: + const: approved + /v1/account/moderator/creator_link_requests/{request_id}/reject: + post: + security: + - cookieAuth: [ ] + description: Reject a new artist link. + responses: + '200': + description: Successfully rejected a new artist link. + content: + application/json: + schema: + type: object + properties: + response: + const: rejected + /v1/favorites/post/{service}/{creator_id}/{post_id}: + post: + tags: + - Favorites + security: + - cookieAuth: [ ] + summary: Add Favorite Post + description: Add a post to the user's favorite posts + parameters: + - name: service + in: path + description: Service of the post + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + - name: post_id + in: path + description: The ID of the post + required: true + schema: + type: string + responses: + '200': + description: Favorite post added successfully + content: { } + '302': + description: Redirect to login if not authenticated + content: { } + '401': + $ref: '#/components/schemas/401' + delete: + tags: + - Favorites + security: + - cookieAuth: [ ] + summary: Remove Favorite Post + description: Remove a post from the user's favorite posts + parameters: + - name: service + in: path + description: The service where the post is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + - name: post_id + in: path + description: The ID of the post + required: true + schema: + type: string + responses: + '200': + description: Unfavorite post removed successfully + content: { } + '302': + description: Redirect to login if not authenticated + content: { } + '401': + $ref: '#/components/schemas/401' + /v1/favorites/creator/{service}/{creator_id}: + post: + tags: + - Favorites + security: + - cookieAuth: [ ] + summary: Add Favorite creator + description: Add an creator to the user's favorite creators + parameters: + - name: service + in: path + description: The service where the creator is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + responses: + '200': + description: Favorite creator added successfully + content: { } + '302': + description: Redirect to login if not authenticated + content: { } + '401': + $ref: '#/components/schemas/401' + delete: + tags: + - Favorites + security: + - cookieAuth: [ ] + summary: Remove Favorite Creator + description: Remove an creator from the user's favorite creators + parameters: + - name: service + in: path + description: The service where the creator is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + responses: + '200': + description: Favorite creator removed successfully + content: { } + '302': + description: Redirect to login if not authenticated + content: { } + '401': + $ref: '#/components/schemas/401' + /v1/search_hash/{file_hash}: + get: + tags: + - File Search + summary: Lookup file by hash + parameters: + - name: file_hash + in: path + required: true + description: SHA-2 / SHA-256 + schema: + type: string + format: hex + minLength: 64 + maxLength: 64 + responses: + '200': + description: File found + content: + application/json: + schema: + type: object + properties: + id: + type: integer + hash: + type: string + mtime: + type: string + format: date-time + ctime: + type: string + format: date-time + mime: + type: string + ext: + type: string + added: + type: string + format: date-time + size: + type: integer + ihash: + type: string + posts: + type: array + items: + type: object + properties: + file_id: + type: integer + id: + type: string + user: + type: string + service: + type: string + title: + type: string + substring: + type: string + published: + type: string + format: date-time + file: + type: object + properties: + name: + type: string + path: + type: string + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + discord_posts: + type: array + items: + type: object + properties: + file_id: + type: integer + id: + type: string + server: + type: string + channel: + type: string + substring: + type: string + published: + type: string + format: date-time + embeds: + type: array + items: { } + mentions: + type: array + items: { } + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + example: + id: 40694581 + hash: b926020cf035af45a1351e0a7e2c983ebcc93b4c751998321a6593a98277cdeb + mtime: '2021-12-04T07:16:09.385539' + ctime: '2021-12-04T07:16:09.385539' + mime: image/png + ext: .png + added: '2021-12-04T07:16:09.443016' + size: 10869921 + ihash: null + posts: + - file_id: 108400151 + id: '5956097' + user: '21101760' + service: fanbox + title: Loli Bae + substring: |- + Thank you for your continued support! + いつも支援ありがとうご + published: '2023-05-14T00:00:00' + file: + name: 8f183dac-470d-4587-9657-23efe8890a7b.jpg + path: /e5/1f/e51fc831dfdac7a21cc650ad46af59340e35e2a051aed8c1e65633592f4dc11c.jpg + attachments: + - name: b644eb9c-cffa-400e-9bd6-40cccb2331ba.png + path: /5e/b3/5eb3197668ac23bd7c473d3c750334eb206b060c610e4ac5fa1a9370fd1314d9.png + - name: 17f295ba-a9f2-4034-aafc-bf74904ec144.png + path: /88/ad/88ad2ba77c89e4d7a9dbe1f9531ba3e3077a82aee2b61efa29fda122ebe1b516.png + discord_posts: + - file_id: 40694581 + id: '769704201495904286' + server: '455285536341491714' + channel: '769703874356445216' + substring: '' + published: '2020-10-24T23:29:42.049' + embeds: [ ] + mentions: [ ] + attachments: + - name: 3.png + path: /b9/26/b926020cf035af45a1351e0a7e2c983ebcc93b4c751998321a6593a98277cdeb.png + '404': + description: File not found + /v1/{service}/user/{creator_id}/post/{post}/flag: + post: + tags: + - Post Flagging + summary: Flag a post + parameters: + - name: service + in: path + required: true + schema: + type: string + - name: creator_id + in: path + required: true + schema: + type: string + - name: post + in: path + required: true + schema: + type: string + responses: + '201': + description: Flagged successfully + content: + application/json: + schema: + const: true + '409': + description: Already flagged + content: + application/json: + schema: + const: true + get: + tags: + - Post Flagging + summary: Check if a Post is flagged + description: Check if a Post is flagged + parameters: + - name: service + in: path + description: The service where the post is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The creator of the post + required: true + schema: + type: string + - name: post + in: path + description: The ID of the post to flag + required: true + schema: + type: string + responses: + '200': + description: The post is flagged + content: { } + '404': + description: The post has no flag + content: { } + /v1/{service}/user/{creator_id}/post/{post_id}/revisions: + get: + tags: + - Posts + summary: List a Post's Revisions + description: List revisions of a specific post by service, creator_id, and post_id + parameters: + - name: service + in: path + description: The service where the post is located + required: true + schema: + type: string + - name: creator_id + in: path + description: The ID of the creator + required: true + schema: + type: string + - name: post_id + in: path + description: The ID of the post + required: true + schema: + type: string + responses: + '200': + description: List of post revisions + content: + application/json: + schema: + type: array + items: + type: object + properties: + revision_id: + type: integer + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + type: boolean + added: + type: string + format: date-time + published: + type: string + format: date-time + edited: + type: string + format: date-time + file: + type: object + properties: + name: + type: string + path: + type: string + attachments: + type: array + items: + type: object + properties: + name: + type: string + path: + type: string + example: + - revision_id: 8059287 + id: '1836570' + user: '6570768' + service: fanbox + title: 今日はFANBOXを始まりました! + content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    + embed: { } + shared_file: false + added: '2023-09-19T13:19:57.416086' + published: '2021-01-24T17:54:38' + edited: '2021-01-24T18:46:15' + file: + name: 8c2be0fd-a130-4afb-9314-80f2501d94f7.jpg + path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg + attachments: + - name: attachment1.jpg + path: /attachments/attachment1.jpg + - name: attachment2.jpg + path: /attachments/attachment2.jpg + - revision_id: 6770513 + id: '1836570' + user: '6570768' + service: fanbox + title: 今日はFANBOXを始まりました! + content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    + embed: { } + shared_file: false + added: '2023-07-28T23:51:25.477291' + published: '2021-01-24T17:54:38' + edited: '2021-01-24T18:46:15' + file: + name: 0d133e49-a2d4-4733-9044-dd57e25b1fce.jpg + path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg + attachments: + - name: attachment3.jpg + path: /attachments/attachment3.jpg + - name: attachment4.jpg + path: /attachments/attachment4.jpg + '404': + description: Post not found + /v1/{service}/user/{creator_id}/post/{post_id}/comments: + get: + tags: + - Comments + summary: List a post's comments + description: List comments for a specific post by service, creator_id, and post_id. + parameters: + - name: service + in: path + description: The post's service. + required: true + schema: + type: string + - name: creator_id + in: path + description: The service ID of the post's creator. + required: true + schema: + type: string + - name: post_id + in: path + description: The service ID of the post. + required: true + schema: + type: string + responses: + '200': + description: List of post comments. + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + parent_id: + type: string + - string + - null + commenter: + type: string + content: + type: string + published: + type: string + format: date-time + revisions: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + added: + type: string + format: date-time + example: + - id: "121508687" + parent_id: null + commenter: "84534108" + content: "YOU DREW MORE YAYYYY" + published: "2023-11-05T20:17:47.635000" + revisions: + - id: 1 + content: "YOU DREW MORE YAYYYY2222222" + added: "2023-11-14T03:09:12.275975" + '404': + description: No comments found. + /v1/artists/random: + get: + description: Get a random artist + responses: + '200': + description: A random artist. + content: + application/json: + schema: + type: object + properties: + service: + type: string + artist_id: + type: string + '404': + description: No random artst exists. + content: + application/json: + schema: + $ref: "#/components/schemas/error" + /v1/shares: + get: + description: Get a list of shares + parameters: + - name: o + in: query + description: List's offset + schema: + $ref: "#/components/schemas/non-negative-integer" + responses: + '200': + description: A list of shares. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: shares + count: + $ref: "#/components/schemas/non-negative-integer" + shares: + type: array + items: + $ref: "#/components/schemas/share" + limit: + $ref: "#/components/schemas/non-negative-integer" + base: + type: object + /v1/share/{share_id}: + get: + description: Get details of the share. + parameters: + - name: share_id + in: path + description: ID of the share. + required: true + schema: + type: string + responses: + '200': + description: Details of the share. + content: + application/json: + schema: + type: object + properties: + share_files: + type: array + share: + $ref: "#/components/schemas/share" + base: + type: object + /v1/dms: + get: + description: Get a list of DMs. + parameters: + - name: o + in: query + description: List's offset + schema: + $ref: "#/components/schemas/non-negative-integer" + - name: q + in: query + description: Search query + schema: + type: string + responses: + '200': + description: A list of DMs. + content: + application/json: + schema: + type: object + properties: + props: + type: object + properties: + currentPage: + const: artists + count: + $ref: "#/components/schemas/non-negative-integer" + limit: + $ref: "#/components/schemas/non-negative-integer" + dms: + type: array + items: + $ref: "#/components/schemas/approved-dm" + base: + type: object + properties: + q: + type: string + /v1/has_pending_dms: + get: + description: Check if there are pending DMs. + responses: + '200': + description: There are pending DMs. + content: + application/json: + schema: + type: boolean + /v1/app_version: + get: + tags: + - Misc + summary: Git Commit Hash + description: Show current App commit hash + responses: + '200': + description: Commit Hash + content: + text/plain: + schema: + type: string + format: hex + minLength: 40 + maxLength: 40 + examples: + - 3b9cd5fab1d35316436968fe85c90ff2de0cdca0 + /v1/importer/submit: + post: + description: Create a site import + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + session_key: + type: string + auto_import: + type: string + save_session_key: + type: string + save_dms: + type: string + channel_ids: + type: string + x-bc: + type: string + auth_id: + type: string + user_agent: + type: string + responses: + '200': + description: Succesfully added new import + content: + application/json: + schema: + type: object + properties: + import_id: + type: string + /v1/importer/logs/{import_id}: + get: + responses: + '200': + description: Get import logs + content: + application/json: + schema: + type: array +components: + parameters: + path-service: + name: service + in: path + description: The service where the creator is located + required: true + schema: + type: string + path-creator-id: + name: creator_id + in: path + description: ID of the creator + required: true + schema: + type: string + path-post-id: + name: post_id + in: path + description: ID of the post + required: true + schema: + type: string + path-page: + name: page + in: path + description: Page of the collection. + required: true + schema: + $ref: "#/components/schemas/positive-integer" + query-q: + name: q + in: query + description: Search query + schema: + type: string + minLength: 3 + query-o: + name: o + in: query + description: Result offset, stepping of 50 is enforced + schema: + type: integer + securitySchemes: + cookieAuth: + description: Session key that can be found in cookies after a successful login + type: apiKey + in: cookie + name: session + schemas: + request-body: + title: RequestBody + description: Body of the request to V2 API. Must always be present on methods which allow a body. + type: object + additionalProperties: false + required: + - type + properties: + type: + const: Sneed's Feed & Seed (formerly Chuck's) + request-body-with-data: + title: RequestBodyWithData + description: Body of the request with extra data to V2 API. + type: object + additionalProperties: false + required: + - type + - data + properties: + type: + const: Sneed's Feed & Seed (formerly Chuck's) + data: true + response-body-success: + title: ResponseBodySuccess + description: Body of the successful response from V2 API. + type: object + additionalProperties: false + required: + - type + - data + properties: + type: + const: Chuck's Fuck & Suck (formerly Boyle's) + data: true + response-body-error: + title: ResponseBodyError + description: Body of the error response from V2 API. + type: object + additionalProperties: false + required: + - type + - data + properties: + type: + const: Boyle's Foil & Soil (formerly Sneed's) + error: + type: object + additionalProperties: false + required: + - type + properties: + type: + type: string + message: + type: string + error: + title: Error + description: Error message + type: object + properties: + error: + type: string + '401': + title: Unauthorized + description: Unauthorized Access + non-negative-integer: + title: NonNegativeInteger + description: Integer which cannot be below zero. + type: integer + minimum: 0 + positive-integer: + title: PositiveInteger + description: Integer which is always above zero. + type: integer + minimum: 1 + hash-sha256: + title: HashSHA256 + type: string + minLength: 64 + maxLength: 64 + artist: + title: Artist + type: object + properties: + id: + type: string + name: + type: string + service: + type: string + indexed: + type: string + updated: + type: string + public_id: + type: string + relation_id: + type: integer + tag: + title: Tag + type: object + properties: + tag: + type: string + post_count: + type: integer + share: + title: Share + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + uploader: + type: integer + added: + type: string + approved-dm: + title: ApprovedDM + description: The public visible DM. + type: object + required: + - hash + - user + - service + - content + - embed + - file + - added + - published + properties: + hash: + type: string + user: + type: string + service: + type: string + content: + type: string + embed: + type: object + file: + type: object + added: + type: string + published: + type: string + artist: + $ref: "#/components/schemas/artist" + unapproved-dm: + title: UnapprovedDM + description: The DM which is shown to the importing user. + type: object + properties: + hash: + type: string + user: + type: string + artist: + type: object + import_id: + type: string + contributor_id: + type: string + service: + type: string + content: + type: string + embed: + type: object + file: + type: object + added: + type: string + published: + type: string + post: + title: Post + type: object + properties: + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + type: boolean + added: + type: string + published: + type: string + edited: + type: string + file: + type: object + attachments: + type: array + items: + type: object + post-with-fav-count: + title: Post + type: object + properties: + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + type: boolean + added: + type: string + published: + type: string + edited: + type: string + file: + type: object + attachments: + type: array + items: + type: object + fav_count: + type: integer + post-revision: + title: PostRevision + type: object + properties: + revision_id: + $ref: "#/components/schemas/positive-integer" + id: + type: string + user: + type: string + service: + type: string + title: + type: string + content: + type: string + embed: + type: object + shared_file: + anyOf: + - type: boolean + - const: "0" + added: + type: string + published: + type: string + edited: + type: string + file: + type: object + attachments: + type: array + items: + type: object + size: + $ref: "#/components/schemas/non-negative-integer" + ihash: + type: string + poll: + type: object + tags: + type: array + items: + type: string + captions: + type: object + comment: + type: object + properties: + id: + type: string + post_id: + type: string + parent_id: + type: string + commenter: + type: string + service: + type: string + content: + type: string + added: + type: string + published: + type: string + deleted_at: + type: string + commenter_name: + type: string + file: + title: File + type: object + properties: + id: + type: integer + hash: + type: string + mtime: + type: string + ctime: + type: string + mime: + type: string + ext: + type: string + added: + type: string + size: + type: integer + ihash: + type: string + archive-info: + title: ArchiveInfo + type: object + required: + - file + - file_list + properties: + file: + $ref: "#/components/schemas/file" + file_list: + type: array + items: + type: string + password: + type: string + account: + type: object + properties: + id: + $ref: "#/components/schemas/positive-integer" + username: + type: string + created_at: + type: string + role: + enum: + - consumer + - moderator + - administrator + notification: + type: object + properties: + id: + $ref: "#/components/schemas/positive-integer" + created_at: + type: string + account_id: + $ref: "#/components/schemas/positive-integer" + is_seen: + type: boolean + type: + type: string + extra_info: + type: object + service-key: + type: object + properties: + id: + $ref: "#/components/schemas/positive-integer" + service: + type: string + added: + type: string + dead: + type: boolean + contributor_id: + $ref: "#/components/schemas/positive-integer" + encrypted_key: + type: string + discord_channel_ids: + type: string + pagination: + title: Pagination + description: Pagination info of a collection. + type: object + properties: + current_page: + $ref: "#/components/schemas/positive-integer" + limit: + $ref: "#/components/schemas/positive-integer" + base: + type: object + offset: + $ref: "#/components/schemas/non-negative-integer" + count: + $ref: "#/components/schemas/non-negative-integer" + current_count: + $ref: "#/components/schemas/non-negative-integer" + total_pages: + $ref: "#/components/schemas/positive-integer" + unapproved-link: + type: object + properties: + id: + $ref: "#/components/schemas/positive-integer" + from_service: + type: string + from_id: + type: string + to_service: + type: string + to_id: + type: string + reason: + type: string + requester_id: + $ref: "#/components/schemas/positive-integer" + status: + const: pending + from_creator: + type: object + to_creator: + type: object + requester: + type: object diff --git a/src/__main__.py b/src/__main__.py index 5746012..dea9e29 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -3,4 +3,4 @@ import sys from src import cmd if __name__ == "__main__": - cmd.main(sys.argv[1:]) \ No newline at end of file + cmd.main(sys.argv[1:]) diff --git a/src/cmd/__init__.py b/src/cmd/__init__.py index 871fc1c..a025ec0 100644 --- a/src/cmd/__init__.py +++ b/src/cmd/__init__.py @@ -2,7 +2,7 @@ from src.cmd.daemon import run_daemon from src.cmd.web import run_web from src.cmd.webpack import run_webpack -from src.config import Configuration +from src.config import Configuration, parse_config from src.internals.tracing.custom_psycopg_instrumentor import instrument_psycopg @@ -37,5 +37,8 @@ def main(args: list[str]): instrument_psycopg() __try(run_daemon()) + case ["validate"]: + __try(Configuration) + case _: - print(f"usage: python -m kemono [web|webpack|daemon]") + print(f"usage: python -m src [run|web|webpack|daemon]") diff --git a/src/cmd/daemon.py b/src/cmd/daemon.py index 5404eb9..9c1991e 100644 --- a/src/cmd/daemon.py +++ b/src/cmd/daemon.py @@ -9,21 +9,10 @@ from src.internals.database import database def run_daemon(): - """Bugs to fix at a later time:""" - """ - Pages can get stuck with an older version of their """ - """ HTML, even after disabling anything and everything """ - """ related to cache. The only resolution as of now is """ - """ a restart of the entire webserver. """ - environment_vars = { **os.environ.copy(), "FLASK_DEBUG": "development" if Configuration().development_mode else "false", "NODE_ENV": "development" if Configuration().development_mode else "production", - "KEMONO_SITE": Configuration().webserver["site"], - "ICONS_PREPEND": Configuration().webserver["ui"]["files_url_prepend"]["icons_base_url"], - "BANNERS_PREPEND": Configuration().webserver["ui"]["files_url_prepend"]["banners_base_url"], - "THUMBNAILS_PREPEND": Configuration().webserver["ui"]["files_url_prepend"]["thumbnails_base_url"], - "CREATORS_LOCATION": Configuration().webserver["api"]["creators_location"], } if Configuration().sentry_dsn: @@ -46,44 +35,6 @@ def run_daemon(): send_default_pii = True, ) - """ Install client dependencies. """ - if not os.path.isdir("./client/node_modules"): - subprocess.run( - ["npm", "ci", "--also=dev", "--legacy-peer-deps"], - check=True, - cwd="client", - env=environment_vars, - ) - - """ Build or run client development server depending on config. """ - if Configuration().development_mode: - subprocess.Popen( - ["npm", "run", "dev"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - cwd="client", - env=environment_vars, - ) - else: - subprocess.run(["npm", "run", "build"], check=True, cwd="client", env=environment_vars) - - """ Run `tusd`. """ - if Configuration().filehaus["tus"]["manage"]: - subprocess.Popen( - [ - "tusd", - "-upload-dir=./storage/uploads", - "--hooks-enabled-events", - "post-create", - "-hooks-http", - "http://127.0.0.1:3343" - # 'http://127.0.0.1:6942/shares/tus' - ], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - env=environment_vars, - ) - database.init() try: if Configuration().automatic_migrations: @@ -98,6 +49,8 @@ def run_daemon(): migrations = read_migrations("./db/migrations") with backend.lock(): backend.apply_migrations(backend.to_apply(migrations)) + backend.connection.close() + del backend """ Initialize Pgroonga if needed. """ with database.pool.getconn() as conn: try: diff --git a/src/cmd/web.py b/src/cmd/web.py index f36e6ef..3abd220 100644 --- a/src/cmd/web.py +++ b/src/cmd/web.py @@ -37,8 +37,11 @@ def run_web(): migrations = read_migrations("./db/migrations") with backend.lock(): backend.apply_migrations(backend.to_apply(migrations)) + backend.connection.close() + del backend """ Initialize Pgroonga if needed. """ with database.pool.getconn() as conn: + # TODO: make it an actual migration file try: with conn.cursor() as db: db.execute("CREATE EXTENSION IF NOT EXISTS pgroonga") @@ -47,8 +50,11 @@ def run_web(): ) db.execute("CREATE INDEX IF NOT EXISTS pgroonga_dms_idx ON dms USING pgroonga (content)") conn.commit() - except Exception: - pass + except Exception as error: + if (Configuration().development_mode): + raise Exception("Failed to install PGRoonga.") from error + else: + pass if Configuration().development_mode: with conn.cursor() as db: db.execute(open("db/seed.sql", "r").read()) @@ -58,4 +64,4 @@ def run_web(): database.close_pool() from src import server - server.app.run("0.0.0.0", port=80) + server.app.run("0.0.0.0", port=Configuration().webserver['port']) diff --git a/src/config.py b/src/config.py index 2ad457f..e97cfa1 100644 --- a/src/config.py +++ b/src/config.py @@ -5,42 +5,19 @@ import os import random import string from base64 import b64encode +from os import path import orjson - - -def merge_dict(dict_base, dict_that_overrides): - if isinstance(dict_base, dict) and isinstance(dict_that_overrides, dict): - return {**dict_base, **{k: merge_dict(dict_base.get(k, {}), v) for k, v in dict_that_overrides.items()}} - else: - return dict_that_overrides +from jsonschema import validate, Draft7Validator +from jsonschema.exceptions import ValidationError class BuildConfiguration: def __init__(self): - config_file = os.environ.get("KEMONO_CONFIG") or "config.json" - config_location = os.path.join("./", config_file) - config = {} - - if os.path.exists(config_location): - with open(config_location) as f: - config.update(orjson.loads(f.read())) - - override_config_file = os.environ.get("KEMONO_OVERRIDE_CONFIG") or "config.override.json" - override_config_location = os.path.join("./", override_config_file) - override_config = {} - - if os.path.exists(config_location): - with open(config_location) as f: - config.update(orjson.loads(f.read())) - - if os.path.exists(override_config_location): - with open(override_config_location) as f: - override_config.update(orjson.loads(f.read())) - - config = merge_dict(config, override_config) + config = parse_config() self.sentry_dsn = config.get("sentry_dsn", None) + self.sentry_dsn_js = config.get("sentry_dsn_js", None) self.open_telemetry_endpoint = config.get("open_telemetry_endpoint", None) self.development_mode = config.get("development_mode", True) self.automatic_migrations = config.get("automatic_migrations", True) @@ -326,3 +303,64 @@ def Configuration(): return global_config # todo use watcher to reload config object based on json changes + + +def parse_config() -> dict: + config_file_name = os.environ.get("KEMONO_CONFIG") or "config.json" + override_config_file_name = os.environ.get("KEMONO_OVERRIDE_CONFIG") or "config.override.json" + config_file_path = os.path.join("./", config_file_name) + override_config_path = os.path.join("./", override_config_file_name) + config_schema_file_path = path.join("./", "schema", "config.schema.json") + config_schema = {} + config = {} + override_config = {} + + if not path.exists(config_schema_file_path): + raise Exception(f'''Config schema file does not exist at path "{config_schema_file_path}".''') + else: + with open(config_schema_file_path) as f: + config_schema.update(orjson.loads(f.read())) + + if not path.exists(config_file_path): + raise Exception(f'''Config file does not exist at path \"{config_file_path}\".''') + else: + with open(config_file_path) as f: + config.update(orjson.loads(f.read())) + + if path.exists(override_config_path): + with open(override_config_path) as f: + override_config.update(orjson.loads(f.read())) + + config = merge_dict(config, override_config) + + try: + Draft7Validator.check_schema(config_schema) + validate(instance=config, schema=config_schema, cls=Draft7Validator) + except ValidationError as error: + schema_json_path_segments = '/'.join([ + str(segment) + for segment + in error.absolute_schema_path + ]) + schema_json_path = f"#/{schema_json_path_segments}" + error_message = f"Configuration validation failed as per schema in \"{schema_json_path}\"." + + raise Exception(error_message) + + return config + + +def merge_dict(dict_base, dict_that_overrides): + if isinstance(dict_base, dict) and isinstance(dict_that_overrides, dict): + value = { + **dict_base, + **{ + k: merge_dict(dict_base.get(k, {}), v) + for k, v + in dict_that_overrides.items() + } + } + + return value + else: + return dict_that_overrides diff --git a/src/internals/database/database.py b/src/internals/database/database.py index f9f308c..5842ea2 100644 --- a/src/internals/database/database.py +++ b/src/internals/database/database.py @@ -8,6 +8,7 @@ from flask import g from psycopg.abc import Query from psycopg.connection import Connection from psycopg.cursor import Cursor +from psycopg.client_cursor import ClientCursor from psycopg.errors import QueryCanceled from psycopg.rows import dict_row from psycopg.types.string import TextLoader @@ -78,7 +79,7 @@ def get_cursor() -> Cursor: return g.cursor -def get_client_binding_cursor() -> Cursor: +def get_client_binding_cursor() -> ClientCursor: if "client_binding_cursor" not in g: if "connection" not in g: g.connection = get_pool().getconn() @@ -124,7 +125,7 @@ def get_from_cache(redis: Any, cache_key: str, cache_store_method: str): def cached_query( query: str, cache_key: str, - params: tuple = (), + params: tuple | dict = (), serialize_fn=safe_dumper, deserialize_fn=safe_loader, reload: bool = False, @@ -268,7 +269,7 @@ def cached_count( return result -def query_db(query: Query, params: tuple = ()) -> list[dict]: +def query_db(query: Query, params: tuple | dict = ()) -> list[dict]: cursor = get_cursor() cursor.execute(query, params) result = cursor.fetchall() @@ -277,7 +278,7 @@ def query_db(query: Query, params: tuple = ()) -> list[dict]: def query_one_db( query: Query, - params: tuple = (), + params: tuple | dict = (), ) -> Optional[dict]: cursor = get_cursor() cursor.execute(query, params) diff --git a/src/internals/serializers/post.py b/src/internals/serializers/post.py index 9671443..5688dd3 100644 --- a/src/internals/serializers/post.py +++ b/src/internals/serializers/post.py @@ -36,7 +36,7 @@ def deserialize_posts_incomplete_rewards(posts_incomplete_rewards_str, loader=un def rebuild_post_fields(post): if post is None: return - if "added" in post: + if post.get("added"): post["added"] = datetime.datetime.fromisoformat(post["added"]) if post.get("published"): post["published"] = datetime.datetime.fromisoformat(post["published"]) diff --git a/src/lib/account.py b/src/lib/account.py index 3f5d784..a373a8f 100644 --- a/src/lib/account.py +++ b/src/lib/account.py @@ -1,9 +1,9 @@ import base64 import hashlib -from typing import Optional +from typing import Optional, TypedDict import bcrypt -from flask import current_app, flash, session +from flask import current_app, session from nh3 import nh3 from src.internals.cache.redis import get_conn @@ -23,23 +23,40 @@ def load_account(account_id: Optional[str] = None, reload: bool = False): return None key = f"account:{account_id}" + params = dict(account_id=account_id) query = """ - SELECT id, username, created_at, role - FROM account - WHERE id = %s + SELECT + id, + username, + created_at, + role + FROM + account + WHERE + id = %(account_id)s """ - account_dict = cached_query(query, key, (account_id,), serialize_account, deserialize_account, reload, True) + account_dict = cached_query(query, key, params, serialize_account, deserialize_account, reload, True) return Account.init_from_dict(account_dict) -def get_saved_key_import_ids(key_id, reload=False): +class TDSessionKeyImportID(TypedDict): + key_id: int + import_id: str + + +def get_saved_key_import_ids(key_id, reload=False) -> list[TDSessionKeyImportID]: key = f"saved_key_import_ids:{key_id}" + params = dict(key_id=key_id) query = """ - SELECT * - FROM saved_session_key_import_ids - WHERE key_id = %s + SELECT + key_id, + import_id + FROM + saved_session_key_import_ids + WHERE + key_id = %(key_id)s """ - return cached_query(query, key, (key_id,), reload=reload) + return cached_query(query, key, params, reload=reload) def get_saved_keys(account_id: int, reload: bool = False): @@ -82,8 +99,21 @@ def revoke_saved_keys(key_ids: list[int], account_id: int): redis.delete(key) -def get_login_info_for_username(username): - query = "SELECT id, password_hash FROM account WHERE username = %s" +class TDLoginInfo(TypedDict): + id: str + password_hash: str + + +def get_login_info_for_username(username) -> TDLoginInfo | None: + query = """ + SELECT + id, + password_hash + FROM + account + WHERE + username = %s + """ return query_one_db(query, (username,)) @@ -152,28 +182,27 @@ def change_password(user_id: int, current_password: str, new_password: str) -> b return True -def attempt_login(username: str, password: str) -> Optional[Account]: +def attempt_login(username: str, password: str) -> tuple[Account, None] | tuple[None, str]: if not username or not password: - return None + return (None, "Username and password must be provided.") account_info = get_login_info_for_username(username) + if account_info is None: - flash("Username or password is incorrect") - return None + return (None, "Username or password is incorrect.") if current_app.config.get("ENABLE_LOGIN_RATE_LIMITING") and is_login_rate_limited(str(account_info["id"])): - flash("You're doing that too much. Try again in a little bit.") - return None + return (None, "You're doing that too much. Try again in a little bit.") if bcrypt.checkpw(get_base_password_hash(password), account_info["password_hash"].encode("utf-8")): if account := load_account(account_info["id"], True): session["account_id"] = account.id - return account + + return (account, None) else: raise Exception("Error loading account") - flash("Username or password is incorrect") - return None + return (None, "Username or password is incorrect") def get_base_password_hash(password: str): diff --git a/src/lib/administrator.py b/src/lib/administrator.py index 1f7961f..f831790 100644 --- a/src/lib/administrator.py +++ b/src/lib/administrator.py @@ -1,9 +1,9 @@ -from typing import Dict, List +from typing import List from src.internals.database.database import query_db, query_one_db, query_rowcount_db -from src.lib.notification import send_notifications -from src.lib.pagination import Pagination -from src.types.account import Account, AccountRoleChange, NotificationTypes +from src.lib.notification import send_notifications, TDNotificationInit +from src.lib.pagination import TDPagination +from src.types.account import Account, AccountRoleChange, NotificationTypes, visible_roles def get_account(account_id: str) -> Account: @@ -16,61 +16,102 @@ def get_account(account_id: str) -> Account: (account_id,), ) account = Account.init_from_dict(account) + return account -def count_accounts(queries: Dict[str, str]) -> int: - role = queries["role"] - params: tuple[str, ...] - if queries.get("name"): - params = (role, queries["name"]) - else: - params = (role,) - result = query_one_db( - f""" - SELECT COUNT(*) AS total_number_of_accounts - FROM account +def count_accounts(role: str | None = None, name: str | None = None) -> int: + roles = [role] if role else visible_roles + params = dict( + roles=roles, + name=f"%%{name}%%" if name else None + ) + query = """ + SELECT + COUNT(*) + FROM + account WHERE - role = ANY(%s) - {"AND username LIKE %s" if len(params) > 1 else ""} - """, + role = ANY(%(roles)s::varchar[]) + AND + ( + %(name)s::varchar IS NULL + OR + username LIKE %(name)s::varchar + ) + """ + result = query_one_db( + query, params, ) - return result["total_number_of_accounts"] if result else 0 + count = result["count"] if result else 0 + + return count -def get_accounts(pagination: Pagination, queries: Dict[str, str]) -> List[Account]: - params = (queries["role"], pagination.offset, pagination.limit) - if queries.get("name"): - params = (queries["role"], f"%%{queries["name"]}%%", pagination.offset, pagination.limit) - - accounts = query_db( - f""" - SELECT id, username, created_at, role - FROM account +def get_accounts(pagination: TDPagination, role: str | None = None, name: str | None = None) -> List[Account]: + roles = [role] if role else visible_roles + params = dict( + roles=roles, + name=f"%%{name}%%" if name else None, + offset=pagination["offset"], + limit=pagination["limit"] + ) + query = """ + SELECT + id, + username, + created_at, + role + FROM + account WHERE - role = ANY(%s) - {"AND username LIKE %s" if len(params) > 3 else ""} + role = ANY(%(roles)s::varchar[]) + AND + ( + %(name)s::varchar IS NULL + OR + username LIKE %(name)s::varchar + ) ORDER BY - created_at DESC, - username - OFFSET %s - LIMIT %s - """, + id ASC + OFFSET %(offset)s + LIMIT %(limit)s + """ + accounts = query_db( + query, params, ) acc_list = [Account.init_from_dict(acc) for acc in accounts] - count = count_accounts(queries) - pagination.add_count(count) + return acc_list def change_account_role(account_ids: List[str], extra_info: AccountRoleChange): change_role_query = """ - UPDATE account - SET role = %s - WHERE id = ANY (%s) + UPDATE + account + SET + role = %s + WHERE + id = ANY (%s) """ query_rowcount_db(change_role_query, (extra_info["new_role"], account_ids)) - send_notifications(account_ids, NotificationTypes.ACCOUNT_ROLE_CHANGE, extra_info) + + notification_inits: list[TDNotificationInit] = [ + TDNotificationInit( + account_id=account_id, + type=NotificationTypes.ACCOUNT_ROLE_CHANGE, + # resort to this retardation because + # psycopg chokes json'ing typed dicts + extra_info=dict( + old_role=extra_info["old_role"], + new_role=extra_info["new_role"] + ) + ) + for account_id + in account_ids + ] + send_notifications(notification_inits) + return True diff --git a/src/lib/announcements.py b/src/lib/announcements.py index 756461a..8ee5b7c 100644 --- a/src/lib/announcements.py +++ b/src/lib/announcements.py @@ -1,11 +1,19 @@ -from typing import List +from typing import TypedDict, Optional from src.internals.database.database import cached_count, cached_query, get_cursor +class TDAnnouncement(TypedDict): + service: str + user_id: str + hash: str + content: str + added: str + published: Optional[str] + def get_artist_announcements( service: str, artist_id: str, query: str | None = None, reload: bool = False -) -> List[dict]: +) -> list[TDAnnouncement]: key = f"announcements:{service}:{artist_id}:{hash(query) if query else ""}" params: tuple[str, ...] @@ -17,18 +25,38 @@ def get_artist_announcements( if service == "fanbox": query = f""" - SELECT *, 'fanbox' AS service - FROM fanbox_newsletters - WHERE user_id = %s {ts_query} - ORDER BY published DESC + SELECT + user_id, + hash, + content, + added, + published, + 'fanbox' AS service + FROM + fanbox_newsletters + WHERE + user_id = %s {ts_query} + ORDER BY + published DESC """ params = (artist_id,) else: query = f""" - SELECT * - FROM introductory_messages - WHERE service = %s AND user_id = %s {ts_query} - ORDER BY added DESC + SELECT + user_id, + hash, + content, + added, + NULL as published, + service + FROM + introductory_messages + WHERE + service = %s + AND + user_id = %s {ts_query} + ORDER BY + added DESC """ params = (service, artist_id) diff --git a/src/lib/api/__init__.py b/src/lib/api/__init__.py new file mode 100644 index 0000000..114daef --- /dev/null +++ b/src/lib/api/__init__.py @@ -0,0 +1,14 @@ +from .v1 import create_client_error_response, create_not_found_error_response +from .v2 import ( + create_api_v2_response, + create_api_v2_error_response, + create_api_v2_client_error_response, + create_api_v2_invalid_body_error_response, + create_api_v2_not_found_error_response, + get_api_v2_request_data, + TDAPIError, + TDAPIRequestBody, + APIV2_REQUEST_BODY_TYPE, + APIV2_RESPONSE_SUCCESS_TYPE, + APIV2_RESPONSE_ERROR_TYPE, +) diff --git a/src/lib/api/v1.py b/src/lib/api/v1.py new file mode 100644 index 0000000..9011bca --- /dev/null +++ b/src/lib/api/v1.py @@ -0,0 +1,18 @@ +from flask import make_response, jsonify + + +def create_client_error_response(message: str, status_code=400): + + if status_code < 400 or status_code > 499: + message = 'The value of status code "{status_code}" is not within range of 400...499.' + raise ValueError(message) + + response = make_response(jsonify(error=message), status_code) + + return response + + +def create_not_found_error_response(message: str = "Not Found"): + response = create_client_error_response(message, 404) + + return response diff --git a/src/lib/api/v2.py b/src/lib/api/v2.py new file mode 100644 index 0000000..5b1b6b8 --- /dev/null +++ b/src/lib/api/v2.py @@ -0,0 +1,69 @@ +from flask import make_response, jsonify, Request +from typing import Any, Literal, NotRequired, TypedDict + +APIV2_REQUEST_BODY_TYPE = "Sneed's Feed & Seed (formerly Chuck's)" +APIV2_RESPONSE_SUCCESS_TYPE = "Chuck's Fuck & Suck (formerly Boyle's)" +APIV2_RESPONSE_ERROR_TYPE = "Boyle's Foil & Soil (formerly Sneed's)" + + +class TDAPIError(TypedDict): + type: str + message: NotRequired[str] + + +class TDAPIRequestBody(TypedDict): + """ + Only required for requests with body. + """ + + type: Literal["Sneed's Feed & Seed (formerly Chuck's)"] + data: NotRequired[Any] + + +class TDAPIResponseSuccess(TypedDict): + type: Literal["Chuck's Fuck & Suck (formerly Boyle's)"] + data: Any + + +class TDAPIResponseError(TypedDict): + type: Literal["Boyle's Foil & Soil (formerly Sneed's)"] + error: TDAPIError + + +def get_api_v2_request_data(request: Request) -> Any: + body: TDAPIRequestBody = request.get_json() + + return body.get("data", None) + + +def create_api_v2_response(data: Any, status_code=200): + response_body = TDAPIResponseSuccess(type="Chuck's Fuck & Suck (formerly Boyle's)", data=data) + + response = make_response(jsonify(response_body), status_code) + + return response + + +def create_api_v2_error_response(error: TDAPIError, status_code=500): + response_body = TDAPIResponseError(type="Boyle's Foil & Soil (formerly Sneed's)", error=error) + + response = make_response(jsonify(response_body), status_code) + + return response + + +def create_api_v2_client_error_response(error: TDAPIError, status_code=400): + return create_api_v2_error_response(error, status_code) + + +def create_api_v2_invalid_body_error_response(): + return create_api_v2_error_response(TDAPIError(type="api_invalid_body_data", message="Invalid body data."), 400) + + +def create_api_v2_not_found_error_response(cache_age: int | None = None): + response = create_api_v2_error_response(TDAPIError(type="api_not_found", message="Not Found."), 404) + + if cache_age: + response.headers["Cache-Control"] = f"s-maxage={str(cache_age)}" + + return response diff --git a/src/lib/artist.py b/src/lib/artist.py index 186e5c4..febdb75 100644 --- a/src/lib/artist.py +++ b/src/lib/artist.py @@ -1,5 +1,5 @@ import datetime -from typing import Optional +from typing import Optional, TypedDict, Literal, Dict from src.config import Configuration from src.internals.cache.redis import get_conn @@ -13,25 +13,59 @@ from src.internals.serializers.artist import ( ) from src.utils.utils import clear_web_cache_for_creator_links +class TDArtist(TypedDict): + id: str + name: str + service: str + indexed: str + updated: str + public_id: str + relation_id: int -def get_top_artists_by_faves(offset, count, reload=False): + +class TDArtistWithFavs(TDArtist): + count: int + + +def get_top_artists_by_faves(offset, count, reload=False) -> list[TDArtistWithFavs]: key = f"top_artists:{offset}:{count}" + params = dict(offset=offset, limit=count) query = """ - SELECT l.*, count(*) - FROM lookup l - INNER JOIN account_artist_favorite aaf - ON l.id = aaf.artist_id AND l.service = aaf.service + SELECT + artists.id, + artists.name, + artists.service, + artists.indexed, + artists.updated, + artists.public_id, + artists.relation_id, + fc.favorite_count AS count + FROM + lookup AS artists + INNER JOIN + favorite_counts AS fc + ON + artists.id = fc.artist_id + AND + artists.service = fc.service WHERE - (l.id, l.service) NOT IN (SELECT id, service from dnp) - GROUP BY (l.id, l.service) - ORDER BY count(*) DESC - OFFSET %s - LIMIT %s + (artists.id, artists.service) NOT IN ( + SELECT + id, + service + FROM + dnp + ) + ORDER BY + count DESC + OFFSET %(offset)s + LIMIT %(limit)s """ + return cached_query( query, key, - (offset, count), + params, serialize_artists, deserialize_artists, reload, @@ -46,21 +80,34 @@ def get_random_artist_keys(count, reload=False): return cached_query(query, key, (count,), unsafe_dumper, unsafe_loader, reload, lock_enabled=True) -def get_artist(service: str, artist_id: str, reload: bool = False) -> dict: +def get_artist(service: str, artist_id: str, reload: bool = False) -> TDArtist: key = f"artist:{service}:{artist_id}" - if service == "onlyfans": - id_filter = "(id = %s or public_id = %s)" - params = (artist_id, artist_id, service) - else: - id_filter = "id = %s" - params = (artist_id, service) + params = dict(artist_id=artist_id, service=service) + id_filter = ( + "(id = %(artist_id)s or public_id = %(artist_id)s)" + if service in ("onlyfans", "fansly", "candfans", "patreon", "fanbox", "boosty") + else "id = %(artist_id)s" + ) query = f""" - SELECT * - FROM lookup + SELECT + id, + name, + service, + indexed, + updated, + public_id, + relation_id + FROM + lookup WHERE {id_filter} - AND service = %s - AND (id, service) NOT IN (SELECT id, service from dnp); + AND service = %(service)s + AND (id, service) NOT IN ( + SELECT + id, + service + FROM dnp + ); """ return cached_query( query, @@ -75,18 +122,35 @@ def get_artist(service: str, artist_id: str, reload: bool = False) -> dict: ) -def get_artists_by_update_time(offset, limit, reload=False): +def get_artists_by_update_time(offset, limit, reload=False) -> list[TDArtist]: key = f"artists_by_update_time:{offset}:{limit}" + params = dict(offset=offset, limit=limit) query = """ - SELECT * - FROM lookup + SELECT + id, + name, + service, + indexed, + updated, + public_id, + relation_id + FROM + lookup WHERE - (id, service) NOT IN (SELECT id, service from dnp) - ORDER BY updated desc - OFFSET %s - LIMIT %s + (id, service) NOT IN ( + SELECT + id, + service + FROM + dnp + ) + ORDER BY + updated DESC + OFFSET %(offset)s + LIMIT %(limit)s """ - return cached_query(query, key, (offset, limit), serialize_artists, deserialize_artists, reload) + + return cached_query(query, key, params, serialize_artists, deserialize_artists, reload) def get_fancards_by_artist(artist_id, reload=False): @@ -102,22 +166,61 @@ def create_unapproved_link_request(from_artist, to_artist, user_id, reason: Opti ON CONFLICT DO NOTHING """ cur = get_cursor() - cur.execute(query, (from_artist["service"], from_artist["id"], to_artist["service"], to_artist["id"], user_id, reason or None)) + cur.execute( + query, + (from_artist["service"], from_artist["id"], to_artist["service"], to_artist["id"], user_id, reason or None), + ) -def get_unapproved_links_with_artists(): +class TDUnapprovedLink(TypedDict): + id: int + from_service: str + from_id: str + to_service: str + to_id: str + reason: str + requester_id: int + status: Literal["pending", "approved", "rejected"] + from_creator: Dict + to_creator: Dict + requester: Dict + + +def get_unapproved_links_with_artists() -> list[TDUnapprovedLink]: query = """ SELECT - unapproved_link_requests.* - , row_to_json(from_creator.*) as from_creator - , row_to_json(to_creator.*) as to_creator - , row_to_json(requester.*) as requester - FROM unapproved_link_requests - JOIN lookup from_creator ON from_service = from_creator.service AND from_id = from_creator.id - JOIN lookup to_creator ON to_service = to_creator.service AND to_id = to_creator.id - JOIN account requester ON requester_id = requester.id - WHERE status = 'pending' - ORDER BY unapproved_link_requests.id ASC + unapproved_link_requests.* , + row_to_json( + from_creator.* + ) AS from_creator, + row_to_json( + to_creator.* + ) AS to_creator, + row_to_json( + requester.* + ) AS requester + FROM + unapproved_link_requests + JOIN + lookup AS from_creator + ON + from_service = from_creator.service + AND + from_id = from_creator.id + JOIN + lookup AS to_creator + ON + to_service = to_creator.service + AND + to_id = to_creator.id + JOIN + account AS requester + ON + requester_id = requester.id + WHERE + status = 'pending' + ORDER BY + unapproved_link_requests.id ASC """ cur = get_cursor() cur.execute(query) @@ -176,8 +279,8 @@ def approve_unapproved_link_request(request_id: int): redis.delete(f"linked_accounts:{update_result['to_service']}:{update_result['to_id']}") redis.delete(f"artist:{update_result['from_service']}:{update_result['from_id']}") redis.delete(f"artist:{update_result['to_service']}:{update_result['to_id']}") - clear_web_cache_for_creator_links(update_result['from_service'], update_result['from_id']) - clear_web_cache_for_creator_links(update_result['to_service'], update_result['to_id']) + clear_web_cache_for_creator_links(update_result["from_service"], update_result["from_id"]) + clear_web_cache_for_creator_links(update_result["to_service"], update_result["to_id"]) def delete_creator_link(service: str, creator_id: str): diff --git a/src/lib/favorites.py b/src/lib/favorites.py index 09c344e..03134c8 100644 --- a/src/lib/favorites.py +++ b/src/lib/favorites.py @@ -1,4 +1,5 @@ import logging +from typing import TypedDict from src.internals.cache.redis import get_conn from src.internals.database.database import cached_query, query_rowcount_db @@ -10,10 +11,21 @@ from src.lib.post import get_post_multiple def get_favorite_artists(account_id, reload=False): key = f"favorite_artists:{account_id}" query = """ - SELECT aaf.id, aaf.service, aaf.artist_id, pam.added as updated - FROM account_artist_favorite aaf - LEFT JOIN posts_added_max pam on pam.service = aaf.service and pam."user" = aaf.artist_id - WHERE account_id = %s + SELECT + aaf.id, + aaf.service, + aaf.artist_id, + pam.added AS updated + FROM + account_artist_favorite AS aaf + LEFT JOIN + posts_added_max AS pam + ON + pam.service = aaf.service + AND + pam."user" = aaf.artist_id + WHERE + account_id = %s """ user_favorite_artists = { (fav["service"], fav["artist_id"]): fav @@ -29,49 +41,97 @@ def get_favorite_artists(account_id, reload=False): # mget artists to prevent n+1, todo better mget with query integrated like posts keys = [f"artist:{service}:{artist_id}" for service, artist_id in user_favorite_artists.keys()] redis = get_conn() + if keys: cache_result = ( - deserialize_artist(artist) for artist in (artist_str for artist_str in redis.mget(keys) if artist_str) + deserialize_artist(artist) + for artist + in (artist_str + for artist_str + in redis.mget(keys) + if artist_str + ) ) else: cache_result = [] + in_cache = {(artist["service"], artist["id"]): artist for artist in cache_result if artist} artists = [] + for favorite_artist in user_favorite_artists.values(): artist = in_cache.get((favorite_artist["service"], favorite_artist["artist_id"])) + if not artist: artist = get_artist(favorite_artist["service"], favorite_artist["artist_id"]) + if artist: artist["faved_seq"] = favorite_artist["id"] artist["last_imported"] = artist["updated"] artist["updated"] = favorite_artist["updated"] artists.append(artist) + return artists +class TDFavoritePostData(TypedDict): + id: str + service: str + artist_id: str + post_id: str + + def get_favorite_posts(account_id, reload=False): key = f"favorite_posts:{account_id}" - query = "select id, service, artist_id, post_id from account_post_favorite where account_id = %s" - favorites = cached_query(query, key, (account_id,), reload=reload) + query = """ + SELECT + id, + service, + artist_id, + post_id + FROM + account_post_favorite + WHERE + account_id = %s + """ + favorites: list[TDFavoritePostData]= cached_query(query, key, (account_id,), reload=reload) posts_to_loads = {(f["service"], f["artist_id"], f["post_id"]): f for f in favorites} return_value = get_post_multiple(posts_to_loads) log_flag = False + for post in return_value: if not post: log_flag = True continue + post["faved_seq"] = posts_to_loads[(post["service"], post["user"], post["id"])]["id"] + if log_flag: logging.exception("Fav post not found for account faves", extra={"account_id": account_id}) + return return_value def add_favorite_artist(account_id, service, artist_id): query_rowcount_db( - "insert into account_artist_favorite (account_id, service, artist_id) values (%s, %s, %s) ON CONFLICT (account_id, service, artist_id) DO NOTHING", + """ + INSERT INTO account_artist_favorite + ( + account_id, + service, + artist_id + ) + VALUES + ( + %s, + %s, + %s + ) + ON CONFLICT + (account_id, service, artist_id) DO NOTHING + """, (account_id, service, artist_id), ) # g.connection.commit() we needed this before because we were in a transaction diff --git a/src/lib/filehaus.py b/src/lib/filehaus.py index 22b474f..bad45ea 100644 --- a/src/lib/filehaus.py +++ b/src/lib/filehaus.py @@ -1,43 +1,80 @@ +from typing import TypedDict from src.internals.database.database import cached_count, cached_query -def get_share(share_id: int, reload=False): +class TDShare(TypedDict): + id: int + name: str + description: str + uploader: int + added: str + + +def get_share(share_id: int, reload=False) -> TDShare: key = f"share:{share_id}" + params = dict(share_id=share_id) query = """ - SELECT * - FROM shares - WHERE id = %s + SELECT + id, + name, + description, + uploader, + added + FROM + shares + WHERE + id = %(share_id)s """ - return cached_query(query, key, (share_id,), reload=reload, single=True) + return cached_query(query, key, params, reload=reload, single=True) -def get_shares(offset: int, limit: int = 50, reload=False): +def get_shares(offset: int, limit: int = 50, reload=False) -> list[TDShare]: key = f"all_shares:{limit}:{offset}:" + params = dict(offset=offset, limit=limit) query = """ - SELECT * - FROM shares - ORDER BY id DESC - OFFSET %s - LIMIT %s + SELECT + id, + name, + description, + uploader, + added + FROM + shares + ORDER BY + id DESC + OFFSET %(offset)s + LIMIT %(limit)s """ - return cached_query(query, key, (offset, limit), reload=reload, lock_enabled=True) + return cached_query(query, key, params, reload=reload, lock_enabled=True) def get_all_shares_count(reload: bool = False) -> int: return cached_count("SELECT COUNT(*) FROM shares", "all_shares_count", lock_enabled=True) -def get_artist_shares(artist_id, service, reload=False): +def get_artist_shares(artist_id, service, reload=False) -> list[TDShare]: key = f"artist_shares:{service}:{artist_id}" + params = dict(artist_id=artist_id, service=service) query = """ - SELECT * - FROM shares s - INNER JOIN lookup_share_relationships lsr on s.id = lsr.share_id - WHERE lsr.user_id = %s AND lsr.service = %s - ORDER BY s.id DESC + SELECT + shares.id, + shares.name, + shares.description, + shares.uploader, + shares.added + FROM + shares + INNER JOIN + lookup_share_relationships AS lsr + ON + shares.id = lsr.share_id + WHERE + lsr.user_id = %(artist_id)s AND lsr.service = %(service)s + ORDER BY + shares.id DESC """ # todo CONSTRAINT lookup_share_relationships_pkey PRIMARY KEY (share_id, service, user_id) should be user_id, service, share_id or we have other index - return cached_query(query, key, (artist_id, service), reload=reload) + return cached_query(query, key, params, reload=reload) def get_artist_share_count(service: str, artist_id: str, reload=False): @@ -51,14 +88,46 @@ def get_artist_share_count(service: str, artist_id: str, reload=False): return cached_count(query, key, (service, artist_id), reload) -def get_files_for_share(share_id: int, reload=False): +class TDShareFile(TypedDict): + share_id: int + upload_url: str + upload_id: str + file_id: int + filename: str + id: int + hash: str + mtime: str + ctime: str + mime: str + ext: str + added: str + + +def get_files_for_share(share_id: int, reload=False) -> list[TDShareFile]: key = f"share_files:{share_id}" query = """ - SELECT * - FROM file_share_relationships fsr - LEFT JOIN files f - ON fsr.file_id = f.id - WHERE share_id = %s - ORDER frs.file_id DESC + SELECT + fsr.share_id, + fsr.upload_url, + fsr.upload_id, + fsr.file_id, + fsr.filename, + files.id, + files.hash, + files.mtime, + files.ctime, + files.mime, + files.ext, + files.added + FROM + file_share_relationships AS fsr + LEFT JOIN + files + ON + fsr.file_id = files.id + WHERE + share_id = %s + ORDER + frs.file_id DESC """ return cached_query(query, key, (share_id,), reload=reload) diff --git a/src/lib/files.py b/src/lib/files.py index dc31db7..05ac1aa 100644 --- a/src/lib/files.py +++ b/src/lib/files.py @@ -3,7 +3,7 @@ import logging import re from dataclasses import dataclass from datetime import datetime -from typing import Optional +from typing import Optional, TypedDict import requests @@ -37,7 +37,36 @@ class File: ihash: Optional[str] -def get_file_relationships(file_hash: str, reload: bool = False): +class TDPost(TypedDict): + file_id: int + id: str + user: str + service: str + title: str + content: str + published: str + file: dict + attachments: list + + +class TDDiscordPost(TypedDict): + file_id: int + id: str + server: str + channel: str + content: str + published: str + embeds: list + mentions: list + attachments: list + + +class TDFileRelationships(TypedDict): + posts: list[TDPost] + discord_posts: list[TDDiscordPost] + + +def get_file_relationships(file_hash: str, reload: bool = False) -> TDFileRelationships: key = f"files:by_hash:{file_hash}" query = """ SELECT @@ -55,9 +84,18 @@ def get_file_relationships(file_hash: str, reload: bool = False): posts.published, posts.file, posts.attachments - FROM file_post_relationships post_files - LEFT JOIN posts ON post_files.post = posts.id AND post_files.service = posts.service - WHERE files.id = post_files.file_id AND posts.id is not NULL + FROM + file_post_relationships AS post_files + LEFT JOIN + posts + ON + post_files.post = posts.id + AND + post_files.service = posts.service + WHERE + files.id = post_files.file_id + AND + posts.id is not NULL LIMIT 1000 ) AS posts ) AS posts, @@ -74,9 +112,14 @@ def get_file_relationships(file_hash: str, reload: bool = False): discord_posts.embeds, discord_posts.mentions, discord_posts.attachments - FROM file_discord_message_relationships discord_files - LEFT JOIN discord_posts ON discord_files.id = discord_posts.id - WHERE files.id = discord_files.file_id + FROM + file_discord_message_relationships AS discord_files + LEFT JOIN + discord_posts + ON + discord_files.id = discord_posts.id + WHERE + files.id = discord_files.file_id LIMIT 1000 ) AS discord_posts ) AS discord_posts @@ -114,13 +157,30 @@ archive_server_session = requests.Session() def get_archive_files(file_hash: str) -> Optional[ArchiveInfo]: if not Configuration().archive_server["enabled"]: return None + arc_data = get_archive(file_hash) + if not arc_data: return None + file, ext = arc_data key = f"archive_files:{file.hash}" - query = "SELECT * FROM archive_files LEFT JOIN files ON archive_files.file_id = files.id WHERE files.hash = %s" + query = """ + SELECT + archive_files.file_id, + archive_files.files, + archive_files.password + FROM + archive_files + LEFT JOIN + files + ON + archive_files.file_id = files.id + WHERE + files.hash = %s + """ result = cached_query(query, key, (file.hash,), single=True) + if result: return ArchiveInfo( file, @@ -132,6 +192,7 @@ def get_archive_files(file_hash: str) -> Optional[ArchiveInfo]: files_api_call = archive_server_session.get( f"{Configuration().archive_server["api_url"]}/list/data/{file_hash[0:2]}/{file_hash[2:4]}/{file_hash}{ext}" ) + if files_api_call.status_code == 401: files: list[str] = [] else: @@ -141,13 +202,29 @@ def get_archive_files(file_hash: str) -> Optional[ArchiveInfo]: f"{Configuration().archive_server["api_url"]}/needs_password/data/{file_hash[0:2]}/{file_hash[2:4]}/{file_hash}{ext}" ) needs_pass_api_call.raise_for_status() + assert needs_pass_api_call.text in ("true", "false") + needs_pass = needs_pass_api_call.text == "true" + except Exception as e: logging.exception("Failed to call archive server", exc_info=True) + return None + password = "" if needs_pass else None - query_rowcount_db("INSERT INTO archive_files (file_id, files, password) VALUES (%s, %s, %s) ON CONFLICT (file_id) DO NOTHING", (file.id, files, password)) + query_rowcount_db( + """ + INSERT INTO archive_files + (file_id, files, password) + VALUES + (%s, %s, %s) + ON CONFLICT + (file_id) DO NOTHING + """, + (file.id, files, password) + ) + return ArchiveInfo(file, files, password) @@ -168,15 +245,23 @@ def try_set_password(file_hash: str, passwords: list[str]) -> bool: if password: update_result = query_one_db( """ - UPDATE archive_files af - SET password = %s - FROM files - WHERE files.hash = %s AND af.file_id = files.id - RETURNING af.files + UPDATE + archive_files AS af + SET + password = %s + FROM + files + WHERE + files.hash = %s + AND + af.file_id = files.id + RETURNING + af.files """, (password, file_hash), ) files: list[str] | None = update_result["files"] if update_result else None + if not files: try: files_api_call = archive_server_session.get( @@ -194,11 +279,15 @@ def try_set_password(file_hash: str, passwords: list[str]) -> bool: """, (files, file_hash), ) + except Exception: logging.exception("Failed to update empty list of archives") + get_conn().delete(f"archive_files:{file_hash}") clear_web_cache_for_archive(file_hash) + return True + return False diff --git a/src/lib/notification.py b/src/lib/notification.py index 07007a0..8a5cdfb 100644 --- a/src/lib/notification.py +++ b/src/lib/notification.py @@ -1,4 +1,5 @@ -from typing import List, Optional +from typing import List, TypedDict +from psycopg.types.json import Json import orjson @@ -97,25 +98,50 @@ def get_account_notifications(account_id: int, reload: bool = False) -> List[Not return notifications -def send_notifications(account_ids: List[str], notification_type: int, extra_info: Optional[dict]) -> bool: +class TDNotificationInit(TypedDict): + account_id: str + type: int + extra_info: dict + + +def send_notifications(notification_inits: list[TDNotificationInit]) -> bool: cursor = get_cursor() - if not account_ids: - return False - if extra_info is not None: - extra_info = orjson.dumps(extra_info) - notification_values = f"(%s, {notification_type}, '{extra_info}')" - else: - notification_values = f"(%s, {notification_type}, NULL)" - - insert_queries_values_template = ",".join([notification_values] * len(account_ids)) - insert_query = f""" - INSERT INTO notifications (account_id, type, extra_info) - VALUES {insert_queries_values_template} - ; + params = dict( + notification_inits=Json(notification_inits) + ) + insert_query = """ + WITH notification_inits AS ( + SELECT + notification_init.account_id, + notification_init.type, + notification_init.extra_info + FROM + json_to_recordset(%(notification_inits)s) AS notification_init( + account_id int, + type smallint, + extra_info jsonb + ) + ) + INSERT INTO notifications + ( + account_id, + type, + extra_info + ) + SELECT + account_id, + type, + extra_info + FROM + notification_inits """ - cursor.execute(insert_query, account_ids) - + cursor.execute(insert_query, params) + account_ids = [ + init["account_id"] + for init + in notification_inits + ] for account_id in account_ids: redis = get_conn() redis.delete(f"account_notifications:{account_id}") diff --git a/src/lib/pagination.py b/src/lib/pagination.py index 5a4b0cd..0a5b62e 100644 --- a/src/lib/pagination.py +++ b/src/lib/pagination.py @@ -1,35 +1,26 @@ import math -from flask import Request, url_for +from typing import TypedDict -from src.utils.utils import limit_int, parse_int +PAGINATION_LIMIT = 50 -class Pagination: - def __init__(self, request: Request) -> None: - self.current_page: int = parse_int(request.args.get("page"), 1) - self.limit: int = limit_int(int(request.args.get("limit") or 25), 25) - self.offset: int = self.calculate_offset(self.current_page, self.limit) - self.base: dict[str, str] = request.args.to_dict() +class TDPagination(TypedDict): + total_count: int + limit: int + total_pages: int + current_page: int + offset: int - self.base.pop("page", None) - self.count: int | None = None - self.current_count: int | None = None - self.total_pages: int | None = None +def create_pagination(total_count: int, current_page: int | None = None) -> TDPagination: + limit = PAGINATION_LIMIT + total_pages = math.ceil(total_count / limit) + current = current_page or total_pages + offset = (current - 1) * limit - def add_count(self, count: int): - self.count = count - self.current_count = self.offset + self.limit if self.offset + self.limit < self.count else self.count - self.total_pages = math.ceil(self.count / self.limit) or 1 + pagination = TDPagination( + total_count=total_count, limit=limit, total_pages=total_pages, current_page=current, offset=offset + ) - def calculate_offset(self, current_page: int, limit: int): - if current_page > 1: - offset = (current_page - 1) * limit - else: - offset = 0 - - return offset - - def create_paged_url(self, request: Request, page_number: int): - return url_for(request.endpoint, page=page_number, **self.base) + return pagination diff --git a/src/lib/post.py b/src/lib/post.py index 0a0e896..8bf0039 100644 --- a/src/lib/post.py +++ b/src/lib/post.py @@ -1,6 +1,10 @@ +import hashlib import itertools import logging import random +import re +import uuid +from typing import TypedDict, Union, Sequence, Any, Literal, Optional from murmurhash2 import murmurhash2 @@ -17,6 +21,7 @@ from src.internals.serializers.post import ( serialize_post_list, serialize_posts_incomplete_rewards, ) +from src.lib.posts import Post, POST_FLAG_CUT_OFF, POST_FLAG_REASON_NUMBER_TO_SLUG from src.utils.utils import fixed_size_batches, images_pattern @@ -46,14 +51,57 @@ def get_random_post_key(table_fraction_percentage: float): def get_post(service, artist_id, post_id, reload=False): key = f"post:{service}:{artist_id}:{post_id}" + params = ( + service, + artist_id, + post_id, + service, + artist_id, + post_id, + service, + artist_id, + post_id, + ) query = """ WITH main_post AS ( - SELECT * - FROM posts - WHERE service = %s - AND "user" = %s - AND id = %s - AND ("user", service) NOT IN (SELECT id, service FROM dnp) + SELECT + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + captions, + tags + FROM + posts + WHERE + service = %s + AND + "user" = %s + AND + id = %s + AND + ("user", service) NOT IN ( + SELECT + id, + service + FROM + dnp + ) ) SELECT main_post.*, @@ -82,17 +130,7 @@ def get_post(service, artist_id, post_id, reload=False): return cached_query( query, key, - ( - service, - artist_id, - post_id, - service, - artist_id, - post_id, - service, - artist_id, - post_id, - ), + params, serialize_post, deserialize_post, reload, @@ -100,25 +138,43 @@ def get_post(service, artist_id, post_id, reload=False): ) -def get_post_multiple(input_, reload=False): +class TDPostData(TypedDict): + service: str + artist_id: str + post_id: str + + +def get_post_multiple(input_: dict[tuple[str, str, str], TDPostData], reload=False): if not input_: return [] key = "post:{service}:{artist_id}:{post_id}" redis = get_conn() keys = [ - key.format(service=service, artist_id=artist_id, post_id=post_id) for (service, artist_id, post_id) in input_ + key.format(service=service, artist_id=artist_id, post_id=post_id) + for (service, artist_id, post_id) + in input_ ] cache_results = redis.mget(keys) - missing_in_cache = [] + missing_in_cache: list[tuple[str, str, str]] = [] for input_el, cache_result in zip(input_, cache_results): if cache_result is None: missing_in_cache.append(input_el) if not missing_in_cache: - all_posts = [deserialize_post(el) for el in cache_results] - return [el for el in all_posts if el] + all_posts = [ + deserialize_post(el) + for el + in cache_results + ] + + return [ + el + for el + in all_posts + if el + ] query = """ WITH input_values (service, "user", id) AS ( @@ -129,12 +185,44 @@ def get_post_multiple(input_, reload=False): SELECT row_to_json(x) as post FROM ( WITH main_post AS ( - SELECT * - FROM posts - WHERE service = iv.service - AND "user" = iv."user" - AND id = iv.id - AND ("user", service) NOT IN (SELECT id, service FROM dnp) + SELECT + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + captions, + tags + FROM + posts + WHERE + service = iv.service + AND + "user" = iv."user" + AND + id = iv.id + AND + ("user", service) NOT IN ( + SELECT + id, + service + FROM + dnp + ) ) SELECT main_post.*, @@ -212,19 +300,37 @@ def get_post_multiple(input_, reload=False): return full_result -def get_post_by_id(post_id, service, reload=True): +def get_post_by_id(post_id: str, service: str, reload=True): key = f"post_by_id:{service}:{post_id}" - query = 'select id, service, "user" from posts where id = %s and service = %s' - return cached_query(query, key, (post_id, service), reload=reload, single=True) + params = dict( + post_id=post_id, + service=service + ) + query = """ + SELECT + id, + service, + "user" + FROM + posts + WHERE + id = %(post_id)s + AND + service = %(service)s + """ + + return cached_query(query, key, params, reload=reload, single=True) -def get_posts_incomplete_rewards(post_id, artist_id, service, reload=False): - key = f"posts_incomplete_rewards:{service}:{post_id}" # todo add artist if other service needs +def get_posts_incomplete_rewards(post_id: str, artist_id: str, service: str, reload=False): + # todo add artist if other service needs + key = f"posts_incomplete_rewards:{service}:{post_id}" query = """ SELECT * FROM posts_incomplete_rewards WHERE id = %s AND service = %s """ + return cached_query( query, key, @@ -236,12 +342,26 @@ def get_posts_incomplete_rewards(post_id, artist_id, service, reload=False): ) -def get_post_comments(post_id, service, reload=False): - if service not in ("fanbox", "patreon"): +class TDComment(TypedDict): + id: str + post_id: str + parent_id: Optional[str] + commenter: str + service: str + content: str + added: str + published: str + deleted_at: str + commenter_name: str + + +def get_post_comments(post_id, service, reload=False) -> list[TDComment]: + if service not in ("fanbox", "patreon", "boosty"): return [] key = f"comments:{service}:{post_id}" + # we select only used fields to save memory and be faster - if service in ("fanbox", "patreon"): + if service in ("fanbox", "patreon", "boosty"): revisions_select = """COALESCE(json_agg( json_build_object( 'id', comments_revisions.revision_id @@ -269,19 +389,51 @@ def get_post_comments(post_id, service, reload=False): WHERE comments.post_id = %s AND comments.service = %s GROUP BY comments.id, comments.parent_id, comments.commenter, comments.commenter_name, comments."content", comments.published """ + return cached_query(query, key, (post_id, service), serialize_post_list, deserialize_post_list, reload) def get_all_posts_by_artist(artist_id, service, reload=False): key = f"posts_by_artist:{service}:{artist_id}" query = """ - SELECT * - FROM posts + SELECT + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + captions, + tags + FROM + posts WHERE "user" = %s - AND service = %s - AND ("user", service) NOT IN (SELECT id, service from dnp); + AND + service = %s + AND + ("user", service) NOT IN ( + SELECT + id, + service + FROM + dnp + ); """ + return cached_query( query, key, (artist_id, service), serialize_post_list, deserialize_post_list, reload, lock_enabled=True ) @@ -290,15 +442,32 @@ def get_all_posts_by_artist(artist_id, service, reload=False): def get_artist_posts_summary(artist_id, service, offset, limit, sort="id", reload=False): """we need this to render html only so we reduce data size to half redis usage""" key = f"artist_posts_offset:{service}:{artist_id}:{offset}:{sort}:summary" - assert sort in ("id", "published DESC NULLS LAST") # extra careful building queries with strings + + # extra careful building queries with strings + assert sort in ("id", "published DESC NULLS LAST") + query = f""" - SELECT id, "user", service, title, substring("content", 1, 50), published, file, attachments - FROM posts - WHERE "user" = %s AND service = %s - ORDER BY {sort} + SELECT + id, + "user", + service, + title, + substring("content", 1, 50), + published, + file, + attachments + FROM + posts + WHERE + "user" = %s + AND + service = %s + ORDER BY + {sort} OFFSET %s LIMIT %s """ + return cached_query( query, key, @@ -312,15 +481,45 @@ def get_artist_posts_summary(artist_id, service, offset, limit, sort="id", reloa def get_artist_posts_full(artist_id, service, offset, limit, sort="id", reload=False): key = f"artist_posts_offset:{service}:{artist_id}:{offset}:{sort}:full" - assert sort in ("id", "published DESC NULLS LAST") # extra careful building queries with strings + + # extra careful building queries with strings + assert sort in ("id", "published DESC NULLS LAST") + query = f""" - SELECT* - FROM posts - WHERE "user" = %s AND service = %s - ORDER BY {sort} + SELECT + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + captions, + tags + FROM + posts + WHERE + "user" = %s + AND + service = %s + ORDER BY + {sort} OFFSET %s LIMIT %s """ + return cached_query( query, key, @@ -335,22 +534,99 @@ def get_artist_posts_full(artist_id, service, offset, limit, sort="id", reload=F def get_artist_post_count(service, artist_id, reload=False): key = f"artist_post_count:{service}:{artist_id}" query = 'SELECT count(*) as count FROM posts WHERE "user" = %s AND service = %s' + return cached_count(query, key, (artist_id, service), reload, lock_enabled=True) -def is_post_flagged(service, artist_id, post_id, reload=False): - key = f"is_post_flagged:{service}:{artist_id}:{post_id}" - query = 'SELECT COUNT(*) FROM booru_flags WHERE id = %s AND "user" = %s AND service = %s' - return cached_count(query, key, (post_id, artist_id, service), reload) +def flag_post(service, creator_id, post_id, reason, flagger_id, flagger_ip) -> str | None: + query = """ + INSERT INTO "post_flags" ("service", "creator_id", "post_id", "reason", "contributor_id", "flagger_ip_hash") + VALUES (%s, %s, %s, %s, %s, %s) + ON CONFLICT ("service", "creator_id", "post_id", "contributor_id") + DO UPDATE SET "reason" = EXCLUDED."reason"; + """ + + flagger_ip_hash = uuid.UUID(hashlib.sha256(flagger_ip.encode()).hexdigest()[:32]) + params = (service, creator_id, post_id, reason, flagger_id, flagger_ip_hash) + + cur = get_cursor() + cur.execute(query, params) + + return is_post_flagged(service, creator_id, post_id, reload=True) + +def is_post_flagged(service, creator_id, post_id, reload=False) -> str | None: + key = f"is_post_flagged:{service}:{creator_id}:{post_id}" + query = 'SELECT MAX(reason) FROM post_flags WHERE post_id = %s AND creator_id = %s AND service = %s' + reason_int = cached_query(query, key, (post_id, creator_id, service,), unsafe_dumper, unsafe_loader, reload, lock_enabled=False, single=True) + return POST_FLAG_REASON_NUMBER_TO_SLUG.get((reason_int or {}).get("max")) + +class TDPostRevision(TypedDict): + revision_id: int + id: str + user: str + service: str + title: str + content: str + embed: dict + shared_file: bool | Literal["0"] + added: str + published: str + edited: str + file: Any + attachments: list[Any] + size: int + ihash: str + poll: Any + tags: list[str] + captions: Any -def get_post_revisions(service, artist_id, post_id, reload=False): +def get_post_revisions(service: str, artist_id: str, post_id: str, reload=False) -> list[TDPostRevision]: key = f"post_revisions:{service}:{artist_id}:{post_id}" - query = 'SELECT * FROM revisions WHERE service = %s AND "user" = %s AND id = %s order by revision_id desc' + params = dict( + service=service, + artist_id=artist_id, + post_id=post_id + ) + query = """ + SELECT + revision_id, + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + tags, + captions + FROM + revisions + WHERE + service = %(service)s + AND + "user" = %(artist_id)s + AND + id = %(post_id)s + ORDER BY + revision_id DESC + """ return cached_query( query, key, - (service, artist_id, post_id), + params, serialize_post_list, deserialize_post_list, reload, @@ -381,14 +657,37 @@ def get_fileserver_for_value(value: str) -> str: return "" -def get_render_data_for_posts(posts): +type TDPreview = Union[TDPreviewThumbnail, TDPreviewEmbed] + + +class TDPreviewThumbnail(TypedDict): + type: Literal["thumbnail"] + server: str + name: str + path: str + + +class TDPreviewEmbed(TypedDict): + type: Literal["embed"] + url: str + subject: str + description: str + + +class TDAttachament(TypedDict): + server: str + name: str + path: str + + +def get_render_data_for_posts(posts: Sequence[Post]) -> tuple[list[TDPreview], list[TDAttachament], list[bool]]: result_previews = [] result_attachments = [] result_is_image = [] for post in posts: - previews = [] - attachments = [] + previews: list[TDPreview] = [] + attachments: list[TDAttachament] = [] if "path" in post["file"]: if images_pattern.search(post["file"]["path"]): result_is_image.append(True) @@ -444,3 +743,22 @@ def get_render_data_for_posts(posts): result_attachments.append(attachments) return result_previews, result_attachments, result_is_image + + +img_replace_patterns = re.compile(pattern=r']*?src="([^"]+)"[^>]*>') + + +def patch_inline_img(content): + return img_replace_patterns.sub(replace_img_tags, content) + + +def replace_img_tags(match): + src = match.group(1) + if not src.startswith("/data"): + src = "/data" + src + from src.config import Configuration + new_src = Configuration().webserver["ui"]["files_url_prepend"]["thumbnails_base_url"] + "/thumbnail" + src + server = get_fileserver_for_value(src.split("?")[0]) + # return match.group(0).replace(src, new_src) + + return f'' diff --git a/src/lib/posts.py b/src/lib/posts.py index 9358ed5..c1fb006 100644 --- a/src/lib/posts.py +++ b/src/lib/posts.py @@ -2,7 +2,7 @@ import base64 import itertools from dataclasses import dataclass from datetime import datetime, timedelta -from typing import Optional, TypedDict +from typing import Optional, TypedDict, Any from src.config import Configuration from src.internals.cache.redis import get_conn @@ -26,25 +26,49 @@ class Post(TypedDict): edited: datetime file: dict attachments: list[dict] + poll: dict + captions: Any + tags: list[str] + incomplete_rewards: Optional[str] class PostWithFavCount(Post): fav_count: int +POST_FLAG_REASON_NUMBER_TO_SLUG = { + -2: "delete-copyright", + -1: "delete-abuse", + 1: "missing-password", + 2: "offsite-expired", + 10: "post-changed", + 20: "corrupted-files", + 21: "missing-files", + 11: "stale-comments", + 12: "formatting-error", + 8: "reason-other", +} + +POST_FLAG_REASON_SLUG_TO_NUMBER = {v:k for k,v in POST_FLAG_REASON_NUMBER_TO_SLUG.items()} + +POST_FLAG_CUT_OFF = 0 def count_all_posts(reload=False) -> int: key = "global_post_count" query = 'SELECT COUNT(*) FROM posts WHERE ("user", service) NOT IN (SELECT id, service from dnp)' + return cached_count(query, key, reload=reload, ex=6000, lock_enabled=True) def count_all_posts_for_query(q: str, reload=False) -> int: q = " OR ".join(x.lower() for x in q.strip().split(" OR ")) + if q == "": return count_all_posts(reload=reload) + key = f"global_post_count_for_query:{base64.b64encode(q.encode()).decode()}" query = """ - SET random_page_cost = 0.0001; + BEGIN; + SET LOCAL random_page_cost = 0.0001; SET LOCAL statement_timeout = 10000; SELECT COUNT(*) FROM posts @@ -53,17 +77,22 @@ def count_all_posts_for_query(q: str, reload=False) -> int: SELECT id, service FROM dnp ); + COMMIT; """ - return cached_count(query, key, (q,), reload, prepare=False, client_bind=True, sets_to_fetch=[2], lock_enabled=True) + + return cached_count(query, key, (q,), reload, prepare=False, client_bind=True, sets_to_fetch=[3], lock_enabled=True) def count_all_posts_for_tag(tags: list[str], service: Optional[str] = None, artist_id: Optional[str] = None) -> int: b = base64.b64encode(f"==TAG==\0{tags}".encode()).decode() key = f"global_post_count_for_query:{b}" query = """ - SELECT COUNT(*) - FROM POSTS - WHERE "tags" @> %s::citext[] + SELECT + COUNT(*) + FROM + POSTS + WHERE + "tags" @> %s::citext[] """ params = (tags,) @@ -80,16 +109,31 @@ def get_all_posts_summary(offset: int, limit=50, reload=False, cache_ttl=None): # we need this version to reduce redis size and bandwidth in half key = f"all_posts:summary:{limit}:{offset}" query = """ - SELECT id, "user", service, title, substring("content", 1, 50), published, file, attachments - FROM posts - WHERE ("user", service) NOT IN (SELECT id, service from dnp) - ORDER BY added DESC + SELECT + id, + "user", + service, + title, + substring("content", 1, 50), + published, + file, + attachments + FROM + posts + WHERE + ("user", service) NOT IN ( + SELECT id, service from dnp + ) + ORDER BY + added DESC OFFSET %s LIMIT %s """ extra = {} + if cache_ttl: extra["ex"] = cache_ttl + return cached_query( query, key, (offset, limit), serialize_dict_list, deserialize_dict_list, reload, lock_enabled=True, **extra ) @@ -98,13 +142,44 @@ def get_all_posts_summary(offset: int, limit=50, reload=False, cache_ttl=None): def get_all_posts_full(offset: int, limit=50, reload=False): key = f"all_posts:full:{limit}:{offset}" query = """ - SELECT * - FROM posts - WHERE ("user", service) NOT IN (SELECT id, service from dnp) - ORDER BY added DESC + SELECT + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + captions, + tags + FROM + posts + WHERE + ("user", service) NOT IN ( + SELECT + id, + service + FROM + dnp + ) + ORDER BY + added DESC OFFSET %s LIMIT %s """ + return cached_query( query, key, (offset, limit), serialize_dict_list, deserialize_dict_list, reload, lock_enabled=True ) @@ -112,23 +187,40 @@ def get_all_posts_full(offset: int, limit=50, reload=False): def get_all_posts_for_query(q: str, offset: int, limit=50, reload=False): q = " OR ".join(x.lower() for x in q.strip().split(" OR ")) + if q == "": return get_all_posts_summary(0, limit, reload, cache_ttl=Configuration().cache_ttl_for_recent_posts) + key = f"all_posts_for_query:{base64.b64encode(q.encode()).decode()}:{limit}:{offset}" query = """ - SET random_page_cost = 0.0001; + BEGIN; + SET LOCAL random_page_cost = 0.0001; SET LOCAL statement_timeout = 10000; - SELECT id, "user", service, title, substring("content", 1, 50), published, file, attachments - FROM posts - WHERE (title || ' ' || content) &@~ %s - AND ("user", service) NOT IN ( - SELECT id, service - FROM dnp - ) - ORDER BY added DESC + SELECT + id, + "user", + service, + title, + substring("content", 1, 50), + published, + file, + attachments + FROM + posts + WHERE + (title || ' ' || content) &@~ %s + AND + ("user", service) NOT IN ( + SELECT id, service + FROM dnp + ) + ORDER BY + added DESC LIMIT %s OFFSET %s; + COMMIT; """ + return cached_query( query, key, @@ -138,7 +230,7 @@ def get_all_posts_for_query(q: str, offset: int, limit=50, reload=False): reload, prepare=False, client_bind=True, - sets_to_fetch=[2], + sets_to_fetch=[3], lock_enabled=True, ) @@ -146,6 +238,7 @@ def get_all_posts_for_query(q: str, offset: int, limit=50, reload=False): def get_all_channels_for_server(discord_server, reload=False): key = f"discord_channels_for_server:{discord_server}" query = "SELECT channel_id as id, name FROM discord_channels WHERE server_id = %s" + return cached_query(query, key, (discord_server,), reload=reload, ex_on_null=60, lock_enabled=True) @@ -163,6 +256,7 @@ def get_popular_posts_for_date_range( redis = get_conn() result = redis.lindex(key, page) + if result: parsed_result = deserialize_post_list(result) if parsed_result: @@ -177,6 +271,7 @@ def get_popular_posts_for_date_range( params = (start_date, end_date, pages_to_query * per_page) order_factor = "COUNT(*)" + if scale == "recent": order_factor = 'SUM((EXTRACT(EPOCH FROM ("created_at" - %s )) / EXTRACT(EPOCH FROM ( %s - %s )) ))::float' params = (start_date, end_date, start_date, *params) @@ -192,9 +287,24 @@ def get_popular_posts_for_date_range( ORDER BY fav_count DESC LIMIT %s ) - SELECT p.id, p."user", p.service, p.title, substring( p."content", 1, 50), p.published, p.file, p.attachments, tf."fav_count" - FROM "top_faves" tf - INNER JOIN "posts" p ON p."id" = tf."post_id" and p."service" = tf."service"; + SELECT + p.id, + p."user", + p.service, + p.title, + substring( p."content", 1, 50), + p.published, + p.file, + p.attachments, + tf."fav_count" + FROM + "top_faves" AS tf + INNER JOIN + "posts" AS p + ON + p."id" = tf."post_id" + AND + p."service" = tf."service"; """ result = cached_query( @@ -208,6 +318,7 @@ def get_popular_posts_for_date_range( cache_store_method="rpush", lock_enabled=True, ) + return (result or [])[(page * per_page) : ((page + 1) * per_page)] @@ -216,11 +327,35 @@ def get_tagged_posts( ) -> list[Post]: key = f"tagged_posts:{tags}:{service}:{artist_id}:{offset}" query = """ - SELECT * - FROM "posts" - WHERE "tags" @> %s::citext[] + SELECT + id, + "user", + service, + title, + content, + embed, + shared_file, + ( + CASE service + WHEN 'fanbox' + THEN NULL + ELSE added + END + ) AS added, + published, + edited, + file, + attachments, + poll, + captions, + tags + FROM + "posts" + WHERE + "tags" @> %s::citext[] """ params: tuple[...] = (tags,) + if service and artist_id: query += """ AND "service" = %s AND "user" = %s ORDER BY published DESC @@ -244,6 +379,7 @@ class Tag: def get_all_tags(service: Optional[str] = None, creator_id: Optional[str] = None) -> list[Tag]: if creator_id and not service: raise Exception("Must be used with both creator_id and service") + key = f"tags:{service or ""}:{creator_id or ""}" query = f""" SELECT {"tag" if creator_id else "lower(tag)"} as tag, COUNT(1) AS post_count @@ -262,4 +398,5 @@ def get_all_tags(service: Optional[str] = None, creator_id: Optional[str] = None LIMIT 2000 """ ex = int(timedelta(hours=(6 if creator_id else 24)).total_seconds()) + return cached_query(query, key, params, ex=ex) diff --git a/src/pages/account/__init__.py b/src/pages/account/__init__.py deleted file mode 100644 index 681decc..0000000 --- a/src/pages/account/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .blueprint import account_bp diff --git a/src/pages/account/administrator/__init__.py b/src/pages/account/administrator/__init__.py deleted file mode 100644 index 1ab7734..0000000 --- a/src/pages/account/administrator/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .blueprint import administrator diff --git a/src/pages/account/administrator/blueprint.py b/src/pages/account/administrator/blueprint.py deleted file mode 100644 index cb6aa3f..0000000 --- a/src/pages/account/administrator/blueprint.py +++ /dev/null @@ -1,157 +0,0 @@ -from flask import Blueprint, abort, g, make_response, render_template, request - -from src.lib.administrator import change_account_role, get_accounts -from src.lib.pagination import Pagination -from src.types.account import Account, AccountRoleChange, visible_roles - -from .types import Accounts, Dashboard, Role_Change - -# from datetime import datetime - -administrator = Blueprint( - "admin", - __name__, -) - - -@administrator.before_request -def check_credentials(): - account: Account = g.get("account") - if account.role != "administrator": - return abort(404) - - -@administrator.get("/administrator") -def get_admin(): - props = Dashboard() - - response = make_response( - render_template( - "account/administrator/dashboard.html", - props=props, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@administrator.get("/administrator/accounts") -def get_accounts_list(): - queries = request.args.to_dict() - queries["name"] = queries["name"] if queries.get("name") else None - - # transform `role` query into a list for db query - if queries.get("role") and queries["role"] != "all": - queries["role"] = [queries["role"]] - else: - queries["role"] = visible_roles - - pagination = Pagination(request) - accounts = get_accounts(pagination, queries) - props = Accounts( - accounts=accounts, - role_list=visible_roles, - pagination=pagination, - ) - - response = make_response( - render_template( - "account/administrator/accounts.html", - props=props, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@administrator.post("/administrator/accounts") -def change_account_roles(): - form_dict = request.form.to_dict(flat=False) - # convert ids to `int` - candidates = dict( - moderator=[int(id) for id in form_dict.get("moderator")] if form_dict.get("moderator") else [], - consumer=[int(id) for id in form_dict.get("consumer")] if form_dict.get("consumer") else [], - ) - - if candidates["moderator"]: - change_account_role( - candidates["moderator"], - AccountRoleChange( - old_role="consumer", - new_role="moderator", - ), - ) - if candidates["consumer"]: - change_account_role( - candidates["consumer"], - AccountRoleChange( - old_role="moderator", - new_role="consumer", - ), - ) - - props = Role_Change() - - response = make_response(render_template("success.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=0, private, must-revalidate" - - return response - - -# @admin.route('/admin/accounts/', methods= ['GET']) -# def get_account_info(account_id: str): -# """ -# Detailed account page. -# """ -# account = get_account(account_id) -# props = admin_props.Account( -# account= account -# ) - -# response = make_response(render_template( -# 'admin/account_info.html', -# props = props, -# ), 200) -# response.headers['Cache-Control'] = 's-maxage=60' -# return response - -# @admin.route('/admin/accounts/', methods= ['POST']) -# def change_account(): -# pass - -# @admin.route('/admin/accounts//files') -# def get_account_files(account_id: str): -# """ -# The lists of approved/rejected/queued files for the given account. -# """ -# files = [] -# account = {} - -# props = admin_props.Account_Files( -# account= account, -# files= files -# ) -# response = make_response(render_template( -# 'admin/account_files.html', -# props = props, -# ), 200) -# response.headers['Cache-Control'] = 's-maxage=60' -# return response - -# @admin.route('/admin/mods/actions', methods= ['GET']) -# def get_moderators_audits(): -# """ -# The list of moderator actions. -# """ -# actions = [] -# props = admin_props.ModeratorActions( -# actions= actions -# ) -# response = make_response(render_template( -# 'admin/mods_actions.html', -# props = props, -# ), 200) -# response.headers['Cache-Control'] = 's-maxage=60' -# return response diff --git a/src/pages/account/administrator/types.py b/src/pages/account/administrator/types.py deleted file mode 100644 index 214d68f..0000000 --- a/src/pages/account/administrator/types.py +++ /dev/null @@ -1,42 +0,0 @@ -from dataclasses import dataclass -from typing import List - -from src.internals.internal_types import PageProps -from src.lib.pagination import Pagination -from src.types.account import Account - - -@dataclass -class Dashboard(PageProps): - currentPage: str = "admin" - - -@dataclass -class Accounts(PageProps): - accounts: List[Account] - role_list: List[str] - pagination: Pagination - currentPage: str = "admin" - - -@dataclass -class Role_Change(PageProps): - redirect: str = "/account/administrator/accounts" - currentPage: str = "admin" - - -# @dataclass -# class Account_Props(PageProps): -# account: Account -# currentPage: str = 'admin' - -# @dataclass -# class Account_Files: -# account: Account -# files: List[Dict] -# currentPage: str = 'admin' - -# @dataclass -# class ModeratorsActions(): -# actions: List[Dict] -# currentPage: str = 'admin' diff --git a/src/pages/account/blueprint.py b/src/pages/account/blueprint.py deleted file mode 100644 index 19b4a15..0000000 --- a/src/pages/account/blueprint.py +++ /dev/null @@ -1,270 +0,0 @@ -import re -from json import JSONDecodeError - -import orjson -from flask import Blueprint, current_app, flash, g, make_response, redirect, render_template, request, session, url_for - -from src.config import Configuration -from src.lib.account import ( - attempt_login, - create_account, - get_saved_key_import_ids, - get_saved_keys, - revoke_saved_keys, - change_password as db_change_password, -) -from src.lib.notification import count_account_notifications, get_account_notifications, set_notifications_as_seen -from src.lib.security import is_password_compromised -from src.types.account import Account -from src.types.props import SuccessProps -from src.utils.utils import set_query_parameter -from src.utils.decorators import require_login - -from .administrator import administrator -from .moderator import moderator -from .types import AccountPageProps, NotificationsProps, ServiceKeysProps - -USERNAME_REGEX = re.compile(r"^[a-z0-9_@+.\-]{3,15}$") -account_bp = Blueprint("account", __name__) - - -@account_bp.get("/account") -def get_account(): - account: Account = g.get("account") - if not account: - return redirect(url_for("account.get_login")) - - if Configuration().enable_notifications: - notifications_count = count_account_notifications(account.id) - else: - notifications_count = 0 - props = AccountPageProps(account=account, notifications_count=notifications_count) - - return make_response(render_template("account/home.html", props=props), 200) - - -@account_bp.get("/account/notifications") -def get_notifications(): - account: Account = g.get("account") - if not account: - return redirect(url_for("account.get_login")) - - if Configuration().enable_notifications: - notifications = get_account_notifications(account.id) - else: - notifications = [] - props = NotificationsProps(notifications=notifications) - - seen_notif_ids = [notification.id for notification in notifications if not notification.is_seen] - set_notifications_as_seen(seen_notif_ids) - - return make_response(render_template("account/notifications.html", props=props), 200) - - -@account_bp.get("/account/keys") -def get_account_keys(): - account: Account = g.get("account") - if not account: - return redirect(url_for("account.get_login")) - - saved_keys = get_saved_keys(account.id) - props = ServiceKeysProps(service_keys=saved_keys) - - saved_session_key_import_ids = [] - for key in saved_keys: - saved_session_key_import_ids.append(get_saved_key_import_ids(key.id)) - - response = make_response( - render_template("account/keys.html", props=props, import_ids=saved_session_key_import_ids), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@account_bp.post("/account/keys") -def revoke_service_keys(): - account: Account = g.get("account") - if not account: - return redirect(url_for("account.get_login")) - - keys_dict = request.form.to_dict(flat=False) - keys_for_revocation = [int(key) for key in keys_dict["revoke"]] if keys_dict.get("revoke") else [] - - revoke_saved_keys(keys_for_revocation, account.id) - - props = SuccessProps(currentPage="account", redirect="/account/keys") - - response = make_response(render_template("success.html", props=props), 200) - return response - - -@account_bp.get("/account/login") -def get_login(): - props = {"currentPage": "login"} - location = request.form.get("location", request.args.get("location", url_for("artists.list"))) - - if account := g.get("account"): - return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role})) - - response = make_response( - render_template( - "account/login.html", - location=location, - props=props, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@account_bp.post("/account/login") -def post_login(): - location = request.form.get("location", request.args.get("location", url_for("artists.list"))) - - if account := g.get("account"): - return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role})) - - username = request.form.get("username", "").replace("\x00", "") - password = request.form.get("password", "") - - if account := attempt_login(username, password): - return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role})) - - return redirect(url_for("account.get_login", location=location)) - - - -@account_bp.route("/account/logout") -def logout(): - if "account_id" in session: - session.pop("account_id") - return redirect(url_for("artists.list")) - - -@account_bp.get("/account/register") -def get_register(): - props = { - "currentPage": "login", - "username_regex": USERNAME_REGEX.pattern, - } - location = request.form.get("location", request.args.get("location", url_for("artists.list"))) - - if g.get("account"): - return redirect(location) - - return make_response(render_template("account/register.html", props=props, location=location), 200) - - -@account_bp.post("/account/register") -def post_register(): - location = request.form.get("location", request.args.get("location", url_for("artists.list"))) - - username = request.form.get("username", "").replace("\x00", "").strip() - password = request.form.get("password", "").strip() - favorites_json = request.form.get("favorites", "[]") - confirm_password = request.form.get("confirm_password", "").strip() - - favorites = [] - if favorites_json != "": - try: - favorites = orjson.loads(favorites_json) - except JSONDecodeError: - pass - - errors = False - if username == "": - flash("Username cannot be empty") - errors = True - - if not USERNAME_REGEX.match(username): - flash("Invalid username") - errors = True - - if password == "": - flash("Password cannot be empty") - errors = True - - if password != confirm_password: - flash("Passwords do not match") - errors = True - - if current_app.config.get("ENABLE_PASSWORD_VALIDATOR") and is_password_compromised(password): - flash( - "We've detected that password was compromised in a data breach on another site. Please choose a different password." - ) - errors = True - - if not errors: - success = create_account(username, password, favorites) - if not success: - flash("Username already taken") - errors = True - - if not errors: - account = attempt_login(username, password) - if account is None: - current_app.logger.warning("Error logging into account immediately after creation") - flash("Account created successfully.") - return redirect(set_query_parameter(location, {"logged_in": "yes"})) - else: - flash("Account created successfully.") - return redirect(set_query_parameter(location, {"logged_in": "yes", "role": account.role})) - - return redirect(url_for("account.get_register", location=location)) - - -@account_bp.get("/account/change_password") -@require_login -def change_password(user: Account): - props = {"currentPage": "changepassword"} - - tmpl = render_template("account/change_password.html", props=props) - response = make_response(tmpl, 200) - response.headers["Cache-Control"] = "s-maxage=3600" - return response - - -@account_bp.post("/account/change_password") -@require_login -def post_change_password(user: Account): - current_password = request.form.get("current-password", "").strip() - new_password = request.form.get("new-password", "").strip() - new_password_conf = request.form.get("new-password-confirmation", "").strip() - - errors = False - - if not new_password: - flash("Password cannot be empty") - errors = True - - if new_password != new_password_conf: - flash("Passwords do not match") - errors = True - - if current_app.config.get("ENABLE_PASSWORD_VALIDATOR") and is_password_compromised(new_password): - flash( - "We've detected that password was compromised in a data breach on another site. Please choose a different password." - ) - errors = True - - if not errors: - if db_change_password(user.id, current_password, new_password): - flash("Password changed") - return redirect(url_for("account.get_account")) - else: - flash("Current password is invalid") - - return redirect(url_for("account.change_password")) - - -@account_bp.get("/.well-known/change-password") -def well_known_change_password(): - response = redirect(url_for("account.change_password")) - response.headers["Cache-Control"] = "s-maxage=604800" - return response - - -account_bp.register_blueprint(administrator, url_prefix="/account") -account_bp.register_blueprint(moderator, url_prefix="/account") diff --git a/src/pages/account/moderator/__init__.py b/src/pages/account/moderator/__init__.py deleted file mode 100644 index 1fe7f06..0000000 --- a/src/pages/account/moderator/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .blueprint import moderator diff --git a/src/pages/account/moderator/blueprint.py b/src/pages/account/moderator/blueprint.py deleted file mode 100644 index f037c65..0000000 --- a/src/pages/account/moderator/blueprint.py +++ /dev/null @@ -1,59 +0,0 @@ -from flask import Blueprint, abort, make_response, render_template, g - -from src.lib.artist import get_unapproved_links_with_artists - -from .types import mod_props - -moderator = Blueprint("mod", __name__) - - -@moderator.before_request -def check_credentials(): - account = g.get("account") - if not account or (account.role != "moderator" and account.role != "administrator"): - return abort(code=404) - - -@moderator.get("/moderator") -def get_dashboard(): - props = mod_props.Dashboard() - - response = make_response( - render_template( - "account/moderator/dashboard.html", - props=props, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@moderator.route("/moderator/tasks/creator_links") -def get_creator_links(): - links = get_unapproved_links_with_artists() - props = {"currentPage": "mod"} - - response = make_response( - render_template( - "account/moderator/creator_links.html", - links=links, - props=props, - ), - 200 - ) - return response - - -# @moderator.route("/mod/tasks/files") -# def get_files(): -# files = [] -# props = mod_props.Files( -# files= files -# ) -# response = make_response(render_template( -# 'moderator_files.html', -# props = props, -# ), 200) -# response.headers['Cache-Control'] = 's-maxage=60' -# return response diff --git a/src/pages/account/moderator/types.py b/src/pages/account/moderator/types.py deleted file mode 100644 index fd2c47f..0000000 --- a/src/pages/account/moderator/types.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Dict, List - - -class Dashboard: - def __init__(self) -> None: - self.current_page = "mod" - - -class Files: - def __init__(self, files: List[Dict]) -> None: - self.current_page = "mod" - self.files = files - - -class Moderator: - def __init__(self) -> None: - self.Dashboard = Dashboard - self.Files = Files - - -mod_props = Moderator() diff --git a/src/pages/api/__init__.py b/src/pages/api/__init__.py index 46e9682..49f2888 100644 --- a/src/pages/api/__init__.py +++ b/src/pages/api/__init__.py @@ -1,47 +1,21 @@ -import pathlib +from flask import Blueprint, Response -import orjson -import yaml -from flask import Blueprint, make_response, render_template, send_from_directory -from yaml import CLoader as Loader +from src.config import Configuration from src.pages.api.v1 import v1api_bp +from src.pages.api.v2 import v2api_bp api_bp = Blueprint("api", __name__, url_prefix="/api") api_bp.register_blueprint(v1api_bp) +api_bp.register_blueprint(v2api_bp) +# set up cors handler for development +if (Configuration().development_mode): + @api_bp.after_request + def aug_api_response(response: Response): + response.headers["Access-Control-Allow-Origin"] = Configuration().webserver["site"] + response.headers["Access-Control-Allow-Headers"] = "Content-Type" + response.headers["Access-Control-Allow-Credentials"] = "true" -@api_bp.get("/swagger_schema") -def swagger_schema(): - response = make_response( - render_template( - "swagger_schema.html", - props=dict( - json_spec=orjson.dumps( - yaml.load(open(pathlib.Path(__file__).parent / "schema.yaml", "r"), Loader=Loader) - ).decode() - ), - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@api_bp.get("/swagger_schema.yaml") -def swagger_schema_yaml(): - return send_from_directory(pathlib.Path(__file__).parent, "schema.yaml") - - -@api_bp.get("/schema") -def api_schema(): - response = make_response( - render_template( - "schema.html", - props=dict(), - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response + return response diff --git a/src/pages/api/schema.yaml b/src/pages/api/schema.yaml deleted file mode 100644 index a2692ab..0000000 --- a/src/pages/api/schema.yaml +++ /dev/null @@ -1,1378 +0,0 @@ -openapi: 3.0.1 -info: - title: Kemono API - version: 1.0.0 -contact: - email: contact@kemono.party -servers: - - url: https://kemono.su/api/v1 - - url: https://coomer.su/api/v1 -tags: - - name: Posts - description: Version one - - name: Creators - - name: Comments - - name: Post Flagging - description: Flag post for re-import - - name: Discord - - name: Favorites - - name: File Search - - name: Misc -paths: - /creators.txt: - get: - tags: - - Posts - summary: List All Creators - description: List all creators with details. I blame DDG for .txt. - responses: - '200': - description: List of all creators - content: - application/json: - schema: - type: array - items: - type: object - properties: - favorited: - type: integer - description: The number of times this creator has been favorited - id: - type: string - description: The ID of the creator - indexed: - type: number - description: Timestamp when the creator was indexed, Unix time as integer - name: - type: string - description: The name of the creator - service: - type: string - description: The service for the creator - updated: - type: number - description: Timestamp when the creator was last updated, Unix time as integer - example: - - favorited: 1 - id: '21101760' - indexed: 1672534800 - name: RAIGYO - service: fanbox - updated: 1672534800 - /posts: - get: - tags: - - Posts - summary: List recent posts - description: List of recently imported posts - parameters: - - name: q - in: query - description: Search query - schema: - type: string - minLength: 3 - - name: o - in: query - description: Result offset, stepping of 50 is enforced - schema: - type: integer - responses: - '200': - description: List of recently added posts - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: string - user: - type: string - service: - type: string - title: - type: string - content: - type: string - embed: - type: object - shared_file: - type: boolean - added: - type: string - format: date-time - published: - type: string - format: date-time - edited: - type: string - format: date-time - file: - type: object - properties: - name: - type: string - path: - type: string - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - example: - - id: '1836570' - user: '6570768' - service: fanbox - title: 今日はFANBOXを始まりました! - content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    - embed: { } - shared_file: false - added: '2021-03-30T18:00:05.973913' - published: '2021-01-24T17:54:38' - edited: '2021-01-24T18:46:15' - file: - name: a99d9674-5490-400e-acca-4bed99590699.jpg - path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg - attachments: [ ] - - id: '1836649' - user: '6570768' - service: fanbox - title: 忍ちゃん 脇コキ差分 - content: '' - embed: { } - shared_file: false - added: '2021-03-30T17:59:57.815397' - published: '2021-01-24T18:23:12' - edited: '2023-01-04T14:45:19' - file: - name: 4c5615f9-be74-4fa7-b88d-168fd37a2824.jpg - path: /d0/3c/d03c893927521536646619f5fb33426aa4b82dc12869865d6d666932755d9acd.jpg - attachments: - - name: 9cc982e4-1d94-4a1a-ac62-3dddd29f881c.png - path: /d7/4d/d74d1727f2c3fcf7a7cc2d244d677d93b4cc562a56904765e4e708523b34fb4c.png - - name: ab0e17d7-52e5-42c2-925b-5cfdb451df0c.png - path: /1b/67/1b677a8c0525e386bf2b2f013e36e29e4033feb2308798e4e5e3780da6c0e815.png - /{service}/user/{creator_id}/profile: - get: - summary: Get a creator - tags: - - Creators - parameters: - - name: service - in: path - description: The service where the creator is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - responses: - '200': - description: Creator details retrieved successfully - content: - application/json: - schema: - type: object - properties: - id: - type: string - description: The ID of the creator - public_id: - type: string - nullable: true - description: The public ID of the creator - service: - type: string - description: The service where the creator is located - name: - type: string - description: The creator's display name - indexed: - type: string - format: date-time - description: The time the creator was last indexed - updated: - type: string - format: date-time - description: The time the creator was last updated - '404': - description: The creator could not be found - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: The error message - enum: ["Creator not found."] - /{service}/user/{creator_id}: - get: - summary: Get a list of creator posts - tags: - - Posts - parameters: - - name: service - in: path - description: The service where the post is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - - name: q - in: query - description: Search query - schema: - type: string - minLength: 3 - - name: o - in: query - description: Result offset, stepping of 50 is enforced - schema: - type: integer - responses: - '200': - description: Post details retrieved successfully - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: string - user: - type: string - service: - type: string - title: - type: string - content: - type: string - embed: - type: object - shared_file: - type: boolean - added: - type: string - format: date-time - published: - type: string - format: date-time - edited: - type: string - format: date-time - file: - type: object - properties: - name: - type: string - path: - type: string - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - example: - - id: '1836570' - user: '6570768' - service: fanbox - title: 今日はFANBOXを始まりました! - content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    - embed: { } - shared_file: false - added: '2021-03-30T18:00:05.973913' - published: '2021-01-24T17:54:38' - edited: '2021-01-24T18:46:15' - file: - name: a99d9674-5490-400e-acca-4bed99590699.jpg - path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg - attachments: [ ] - - id: '1836649' - user: '6570768' - service: fanbox - title: 忍ちゃん 脇コキ差分 - content: '' - embed: { } - shared_file: false - added: '2021-03-30T17:59:57.815397' - published: '2021-01-24T18:23:12' - edited: '2023-01-04T14:45:19' - file: - name: 4c5615f9-be74-4fa7-b88d-168fd37a2824.jpg - path: /d0/3c/d03c893927521536646619f5fb33426aa4b82dc12869865d6d666932755d9acd.jpg - attachments: - - name: 9cc982e4-1d94-4a1a-ac62-3dddd29f881c.png - path: /d7/4d/d74d1727f2c3fcf7a7cc2d244d677d93b4cc562a56904765e4e708523b34fb4c.png - - name: ab0e17d7-52e5-42c2-925b-5cfdb451df0c.png - path: /1b/67/1b677a8c0525e386bf2b2f013e36e29e4033feb2308798e4e5e3780da6c0e815.png - '400': - description: Offset provided which is not a multiple of 50 - '404': - description: The creator could not be found - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: The error message - enum: ["Creator not found."] - /{service}/user/{creator_id}/announcements: - get: - summary: Get creator announcements - tags: - - Posts - parameters: - - name: service - in: path - required: true - description: The service name - schema: - type: string - - name: creator_id - in: path - required: true - description: The creator's ID - schema: - type: string - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: array - items: - type: object - properties: - service: - type: string - user_id: - type: string - hash: - type: string - description: sha256 - content: - type: string - added: - type: string - format: date-time - description: isoformat UTC - example: - - service: patreon - user_id: '8693043' - hash: 820b7397c7f75efb13c4a8aa5d4aacfbb200749f3e1cec16e9f2951d158be8c2 - content: Hey guys, thank you so much for your support, that means a lot to me! - added: '2023-01-31T05:16:15.462035' - '404': - description: Artist not found - /{service}/user/{creator_id}/fancards: - get: - summary: Get fancards by creator, fanbox only - tags: - - Posts - parameters: - - name: service - in: path - required: true - description: The service name, has to be "fanbox" - schema: - type: string - - name: creator_id - in: path - required: true - description: The creator's ID - schema: - type: string - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: integer - user_id: - type: string - file_id: - type: integer - hash: - type: string - mtime: - type: string - format: date-time - ctime: - type: string - format: date-time - mime: - type: string - ext: - type: string - added: - type: string - format: date-time - size: - type: integer - ihash: - type: string - example: - - id: 108058645 - user_id: '3316400' - file_id: 108058645 - hash: 727bf3f0d774a98c80cf6c76c3fb0e049522b88eb7f02c8d3fc59bae20439fcf - mtime: '2023-05-23T15:09:43.941195' - ctime: '2023-05-23T15:09:43.941195' - mime: image/jpeg - ext: .jpg - added: '2023-05-23T15:09:43.960578' - size: 339710 - ihash: null - - id: 103286760 - user_id: '3316400' - file_id: 103286760 - hash: 8b0d0f1be38efab9306b32c7b14b74ddd92a2513026c859a280fe737980a467d - mtime: '2023-04-26T14:16:53.205183' - ctime: '2023-04-26T14:16:53.205183' - mime: image/jpeg - ext: .jpg - added: '2023-04-26T14:16:53.289143' - size: 339764 - ihash: null - '404': - description: Artist not found - /{service}/user/{creator_id}/links: - get: - summary: Get a creator's linked accounts - tags: - - Creators - parameters: - - name: service - in: path - description: The service where the creator is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - responses: - '200': - description: Linked accounts retrieved successfully - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: string - description: The ID of the creator - public_id: - type: string - nullable: true - description: The public ID of the creator - service: - type: string - description: The service where the creator is located - name: - type: string - description: The creator's display name - indexed: - type: string - format: date-time - description: The time the creator was last indexed - updated: - type: string - format: date-time - description: The time the creator was last updated - '404': - description: The creator could not be found - content: - application/json: - schema: - type: object - properties: - error: - type: string - description: The error message - enum: ["Creator not found."] - /{service}/user/{creator_id}/post/{post_id}: - get: - summary: Get a specific post - tags: - - Posts - parameters: - - name: service - in: path - required: true - description: The service name - schema: - type: string - - name: creator_id - in: path - required: true - description: The creator's ID - schema: - type: string - - name: post_id - in: path - required: true - description: The post ID - schema: - type: string - responses: - '200': - description: Successful response - content: - application/json: - schema: - type: object - properties: - id: - type: string - user: - type: string - service: - type: string - title: - type: string - content: - type: string - embed: - type: object - shared_file: - type: boolean - added: - type: string - format: date-time - published: - type: string - format: date-time - edited: - type: string - format: date-time - file: - type: object - properties: - name: - type: string - path: - type: string - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - next: - type: string - prev: - type: string - example: - id: '1836570' - user: '6570768' - service: fanbox - title: 今日はFANBOXを始まりました! - content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    - embed: { } - shared_file: false - added: '2021-03-30T18:00:05.973913' - published: '2021-01-24T17:54:38' - edited: '2021-01-24T18:46:15' - file: - name: a99d9674-5490-400e-acca-4bed99590699.jpg - path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg - attachments: [ ] - next: null - prev: '1836649' - '404': - description: Post not found - /discord/channel/{channel_id}: - get: - tags: - - Discord - summary: Get Discord channel posts by offset - parameters: - - name: channel_id - in: path - description: ID of the Discord channel - required: true - schema: - type: string - - name: o - in: query - description: Result offset, stepping of 150 is enforced - schema: - type: integer - responses: - '200': - description: Discord channel found - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: string - author: - type: object - properties: - id: - type: string - avatar: - type: string - username: - type: string - public_flags: - type: integer - discriminator: - type: string - server: - type: string - channel: - type: string - content: - type: string - added: - type: string - format: date-time - published: - type: string - format: date-time - edited: - type: string - format: date-time - embeds: - type: array - items: { } - mentions: - type: array - items: { } - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - example: - - id: '942909658610413578' - author: - id: '421590382300889088' - avatar: 0956f3dc18eba7da9daedc4e50fb96d0 - username: Merry - public_flags: 0 - discriminator: '7849' - server: '455285536341491714' - channel: '455287420959850496' - content: '@everyone Happy Valentine’s Day! 💜✨' - added: '2022-02-15T01:26:12.708959' - published: '2022-02-14T22:26:21.027000' - edited: null - embeds: [ ] - mentions: [ ] - attachments: [ ] - - id: '942909571947712594' - author: - id: '421590382300889088' - avatar: 0956f3dc18eba7da9daedc4e50fb96d0 - username: Merry - public_flags: 0 - discriminator: '7849' - server: '455285536341491714' - channel: '455287420959850496' - content: '' - added: '2022-02-15T01:26:13.006228' - published: '2022-02-14T22:26:00.365000' - edited: null - embeds: [ ] - mentions: [ ] - attachments: - - name: sofa_03.png - path: /3b/4e/3b4ed5aabdd85b26fbbc3ee9b0e5649df69167efe26b5abc24cc2a1159f446d4.png - '404': - description: Discord channel not found - /discord/channel/lookup/{discord_server}: - get: - tags: - - Discord - summary: Lookup Discord channels - parameters: - - name: discord_server - in: path - description: Discord Server ID - required: true - schema: - type: string - responses: - '200': - description: Discord channels found - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: string - name: - type: string - example: - - id: '455285536341491716' - name: news - - id: '455287420959850496' - name: nyarla-lewds - '404': - description: Discord server not found - /account/favorites: - get: - tags: - - Favorites - security: - - cookieAuth: [ ] - summary: List Account Favorites - description: List account favorites (posts or creators) for the authenticated user (cookie session) - parameters: - - name: type - in: query - description: Type of favorites to list (post or creator (artist) ) - schema: - type: string - enum: - - post - - artist - responses: - '200': - description: List of account favorites - content: - application/json: - schema: - type: array - items: - type: object - properties: - faved_seq: - type: integer - description: The sequence number of the favorite - id: - type: string - description: The ID of the favorite (post or creator) - indexed: - type: string - description: Timestamp when the creator was indexed isoformat - last_imported: - type: string - description: Timestamp when the creator was last imported - name: - type: string - description: The name of the creator - service: - type: string - description: The service where the creator is located - updated: - type: string - description: Timestamp when the creator was last updated - '401': - $ref: '#/components/schemas/401' - /favorites/post/{service}/{creator_id}/{post_id}: - post: - tags: - - Favorites - security: - - cookieAuth: [ ] - summary: Add Favorite Post - description: Add a post to the user's favorite posts - parameters: - - name: service - in: path - description: Service of the post - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - - name: post_id - in: path - description: The ID of the post - required: true - schema: - type: string - responses: - '200': - description: Favorite post added successfully - content: { } - '302': - description: Redirect to login if not authenticated - content: { } - '401': - $ref: '#/components/schemas/401' - delete: - tags: - - Favorites - security: - - cookieAuth: [ ] - summary: Remove Favorite Post - description: Remove a post from the user's favorite posts - parameters: - - name: service - in: path - description: The service where the post is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - - name: post_id - in: path - description: The ID of the post - required: true - schema: - type: string - responses: - '200': - description: Unfavorite post removed successfully - content: { } - '302': - description: Redirect to login if not authenticated - content: { } - '401': - $ref: '#/components/schemas/401' - /favorites/creator/{service}/{creator_id}: - post: - tags: - - Favorites - security: - - cookieAuth: [ ] - summary: Add Favorite creator - description: Add an creator to the user's favorite creators - parameters: - - name: service - in: path - description: The service where the creator is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - responses: - '200': - description: Favorite creator added successfully - content: { } - '302': - description: Redirect to login if not authenticated - content: { } - '401': - $ref: '#/components/schemas/401' - delete: - tags: - - Favorites - security: - - cookieAuth: [ ] - summary: Remove Favorite Creator - description: Remove an creator from the user's favorite creators - parameters: - - name: service - in: path - description: The service where the creator is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - responses: - '200': - description: Favorite creator removed successfully - content: { } - '302': - description: Redirect to login if not authenticated - content: { } - '401': - $ref: '#/components/schemas/401' - /search_hash/{file_hash}: - get: - tags: - - File Search - summary: Lookup file by hash - parameters: - - name: file_hash - in: path - required: true - description: SHA-2 / SHA-256 - schema: - type: string - format: hex - minLength: 64 - maxLength: 64 - responses: - '200': - description: File found - content: - application/json: - schema: - type: object - properties: - id: - type: integer - hash: - type: string - mtime: - type: string - format: date-time - ctime: - type: string - format: date-time - mime: - type: string - ext: - type: string - added: - type: string - format: date-time - size: - type: integer - ihash: - type: string - posts: - type: array - items: - type: object - properties: - file_id: - type: integer - id: - type: string - user: - type: string - service: - type: string - title: - type: string - substring: - type: string - published: - type: string - format: date-time - file: - type: object - properties: - name: - type: string - path: - type: string - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - discord_posts: - type: array - items: - type: object - properties: - file_id: - type: integer - id: - type: string - server: - type: string - channel: - type: string - substring: - type: string - published: - type: string - format: date-time - embeds: - type: array - items: { } - mentions: - type: array - items: { } - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - example: - id: 40694581 - hash: b926020cf035af45a1351e0a7e2c983ebcc93b4c751998321a6593a98277cdeb - mtime: '2021-12-04T07:16:09.385539' - ctime: '2021-12-04T07:16:09.385539' - mime: image/png - ext: .png - added: '2021-12-04T07:16:09.443016' - size: 10869921 - ihash: null - posts: - - file_id: 108400151 - id: '5956097' - user: '21101760' - service: fanbox - title: Loli Bae - substring: |- - Thank you for your continued support! - いつも支援ありがとうご - published: '2023-05-14T00:00:00' - file: - name: 8f183dac-470d-4587-9657-23efe8890a7b.jpg - path: /e5/1f/e51fc831dfdac7a21cc650ad46af59340e35e2a051aed8c1e65633592f4dc11c.jpg - attachments: - - name: b644eb9c-cffa-400e-9bd6-40cccb2331ba.png - path: /5e/b3/5eb3197668ac23bd7c473d3c750334eb206b060c610e4ac5fa1a9370fd1314d9.png - - name: 17f295ba-a9f2-4034-aafc-bf74904ec144.png - path: /88/ad/88ad2ba77c89e4d7a9dbe1f9531ba3e3077a82aee2b61efa29fda122ebe1b516.png - discord_posts: - - file_id: 40694581 - id: '769704201495904286' - server: '455285536341491714' - channel: '769703874356445216' - substring: '' - published: '2020-10-24T23:29:42.049' - embeds: [ ] - mentions: [ ] - attachments: - - name: 3.png - path: /b9/26/b926020cf035af45a1351e0a7e2c983ebcc93b4c751998321a6593a98277cdeb.png - '404': - description: File not found - /{service}/user/{creator_id}/post/{post}/flag: - post: - tags: - - Post Flagging - summary: Flag a post - parameters: - - name: service - in: path - required: true - schema: - type: string - - name: creator_id - in: path - required: true - schema: - type: string - - name: post - in: path - required: true - schema: - type: string - responses: - '201': - description: Flagged successfully - content: { } - '409': - description: Already flagged - content: { } - get: - tags: - - Post Flagging - summary: Check if a Post is flagged - description: Check if a Post is flagged - parameters: - - name: service - in: path - description: The service where the post is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The creator of the post - required: true - schema: - type: string - - name: post - in: path - description: The ID of the post to flag - required: true - schema: - type: string - responses: - '200': - description: The post is flagged - content: { } - '404': - description: The post has no flag - content: { } - /{service}/user/{creator_id}/post/{post_id}/revisions: - get: - tags: - - Posts - summary: List a Post's Revisions - description: List revisions of a specific post by service, creator_id, and post_id - parameters: - - name: service - in: path - description: The service where the post is located - required: true - schema: - type: string - - name: creator_id - in: path - description: The ID of the creator - required: true - schema: - type: string - - name: post_id - in: path - description: The ID of the post - required: true - schema: - type: string - responses: - '200': - description: List of post revisions - content: - application/json: - schema: - type: array - items: - type: object - properties: - revision_id: - type: integer - id: - type: string - user: - type: string - service: - type: string - title: - type: string - content: - type: string - embed: - type: object - shared_file: - type: boolean - added: - type: string - format: date-time - published: - type: string - format: date-time - edited: - type: string - format: date-time - file: - type: object - properties: - name: - type: string - path: - type: string - attachments: - type: array - items: - type: object - properties: - name: - type: string - path: - type: string - example: - - revision_id: 8059287 - id: '1836570' - user: '6570768' - service: fanbox - title: 今日はFANBOXを始まりました! - content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    - embed: { } - shared_file: false - added: '2023-09-19T13:19:57.416086' - published: '2021-01-24T17:54:38' - edited: '2021-01-24T18:46:15' - file: - name: 8c2be0fd-a130-4afb-9314-80f2501d94f7.jpg - path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg - attachments: - - name: attachment1.jpg - path: /attachments/attachment1.jpg - - name: attachment2.jpg - path: /attachments/attachment2.jpg - - revision_id: 6770513 - id: '1836570' - user: '6570768' - service: fanbox - title: 今日はFANBOXを始まりました! - content:

    みなさんこんにちは、影おじです。

    先週のように、FANBOXを始まりに決定しました!

    そしてFANBOXの更新内容について、アンケートのみなさん

    ありがとうございました!


    では更新内容の詳しいことはこちらです↓

    毎回の絵、元も差分がありませんの場合、ボナスとして差分イラストを支援者の皆様にプレゼント。

    もとも差分があれば、ボナスとしてヌード差分イラストを支援者の皆様にプレゼント。


    これから、仕事以外の時間、できる限り勤勉な更新したいと思います!

    どうぞよろしくお願いいたします!

    - embed: { } - shared_file: false - added: '2023-07-28T23:51:25.477291' - published: '2021-01-24T17:54:38' - edited: '2021-01-24T18:46:15' - file: - name: 0d133e49-a2d4-4733-9044-dd57e25b1fce.jpg - path: /5c/98/5c984d1f62f0990a0891d8fa359aecdff6ac1e26ac165ba7bb7f31cc99e7a674.jpg - attachments: - - name: attachment3.jpg - path: /attachments/attachment3.jpg - - name: attachment4.jpg - path: /attachments/attachment4.jpg - '404': - description: Post not found - /{service}/user/{creator_id}/post/{post_id}/comments: - get: - tags: - - Comments - summary: List a post's comments - description: List comments for a specific post by service, creator_id, and post_id. - parameters: - - name: service - in: path - description: The post's service. - required: true - schema: - type: string - - name: creator_id - in: path - description: The service ID of the post's creator. - required: true - schema: - type: string - - name: post_id - in: path - description: The service ID of the post. - required: true - schema: - type: string - responses: - '200': - description: List of post comments. - content: - application/json: - schema: - type: array - items: - type: object - properties: - id: - type: string - parent_id: - type: string - nullable: true - commenter: - type: string - content: - type: string - published: - type: string - format: date-time - revisions: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - added: - type: string - format: date-time - example: - - id: "121508687" - parent_id: null - commenter: "84534108" - content: "YOU DREW MORE YAYYYY" - published: "2023-11-05T20:17:47.635000" - revisions: - - id: 1 - content: "YOU DREW MORE YAYYYY2222222" - added: "2023-11-14T03:09:12.275975" - '404': - description: No comments found. - - /app_version: - get: - tags: - - Misc - summary: Git Commit Hash - description: Show current App commit hash - responses: - '200': - description: Commit Hash - content: - text/plain: - schema: - type: string - format: hex - minLength: 40 - maxLength: 40 - example: 3b9cd5fab1d35316436968fe85c90ff2de0cdca0 -components: - securitySchemes: - cookieAuth: - description: Session key that can be found in cookies after a successful login - type: apiKey - in: cookie - name: session - schemas: - '401': - title: Unauthorized - description: Unauthorized Access diff --git a/src/pages/api/v1/account.py b/src/pages/api/v1/account.py new file mode 100644 index 0000000..d1500dd --- /dev/null +++ b/src/pages/api/v1/account.py @@ -0,0 +1,222 @@ +from typing import TypedDict, List, Literal, Optional + +from flask import Blueprint, g, make_response, jsonify, abort, request, current_app + +from src.config import Configuration +from src.lib.account import ( + is_logged_in, + load_account, + get_saved_key_import_ids, + get_saved_keys, + revoke_saved_keys, + change_password as db_change_password, +) +from src.lib.notification import count_account_notifications, get_account_notifications, set_notifications_as_seen +from src.lib.security import is_password_compromised +from src.lib.api import create_client_error_response +from src.lib.dms import approve_dms, cleanup_unapproved_dms, get_unapproved_dms, clean_dms_already_approved +from src.pages.account.types import AccountPageProps, NotificationsProps, ServiceKeysProps +from src.types.props import SuccessProps +from src.types.account.account import Account +from src.types.kemono import Unapproved_DM + + +from . import v1api_bp +from .moderator import moderator_bp + +account_bp = Blueprint("account", __name__) + + +# check credentials for all requests for this blueprint +# so the subsequent handlers wouldn't need to check it again +@account_bp.before_request +def check_auth(): + if not is_logged_in(): + abort(401) + + account = load_account() + + if not account: + abort(401) + + +@account_bp.get("/account") +def get_account_info(): + account: Account = g.get("account") + + if Configuration().enable_notifications: + notifications_count = count_account_notifications(account.id) + else: + notifications_count = 0 + props = AccountPageProps(account=account, notifications_count=notifications_count) + + return make_response(jsonify(props=props), 200) + + +TDChangePasswordBody = TypedDict( + "TDChangePasswordBody", {"current-password": str, "new-password": str, "new-password-confirmation": str} +) + + +@account_bp.post("/account/change_password") +def post_change_password(): + body: TDChangePasswordBody = request.get_json() + current_password = body.get("current-password", "") + new_password = body.get("new-password", "").strip() + new_password_conf = body.get("new-password-confirmation", "").strip() + account: Account = load_account() + + if not new_password: + return create_client_error_response("New password cannot be empty.") + + if len(new_password) < 5: + return create_client_error_response("New password must have at least 5 characters.") + + if new_password != new_password_conf: + return create_client_error_response("New password and confirmation do not match.") + + if current_app.config.get("ENABLE_PASSWORD_VALIDATOR") and is_password_compromised(new_password): + response = create_client_error_response( + "We've detected that password was compromised in a data breach on another site. Please choose a different password." + ) + + return response + + if not db_change_password(account.id, current_password, new_password): + return create_client_error_response("Current password is invalid.") + + response = make_response(jsonify(True), 200) + + return response + + +@account_bp.get("/account/notifications") +def get_notifications(): + account: Account = g.get("account") + + if Configuration().enable_notifications: + notifications = get_account_notifications(account.id) + else: + notifications = [] + props = NotificationsProps(notifications=notifications) + + seen_notif_ids = [notification.id for notification in notifications if not notification.is_seen] + set_notifications_as_seen(seen_notif_ids) + + return make_response(jsonify(props=props), 200) + + +@account_bp.get("/account/keys") +def get_account_keys(): + account: Account = g.get("account") + + saved_keys = get_saved_keys(account.id) + props = ServiceKeysProps(service_keys=saved_keys) + + saved_session_key_import_ids = [] + for key in saved_keys: + saved_session_key_import_ids.append(get_saved_key_import_ids(key.id)) + + response = make_response( + jsonify(props=props, import_ids=saved_session_key_import_ids), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + +class TDKeyRevokeBody(TypedDict): + revoke: list[int] + + +@account_bp.post("/account/keys") +def revoke_service_keys(): + account: Account = g.get("account") + body: TDKeyRevokeBody = request.get_json() + keys_for_revocation = body["revoke"] + + revoke_saved_keys(keys_for_revocation, account.id) + + props = SuccessProps(currentPage="account", redirect="/account/keys") + response = make_response(jsonify(props=props), 200) + + return response + + +@account_bp.get("/account/posts/upload") +def upload_post(): + account: Account = g.get("account") + + required_roles = Configuration().filehaus["required_roles"] + if len(required_roles) and account.role not in required_roles: + return create_client_error_response( + "Filehaus uploading requires elevated permissions. Please contact the administrator to change your role." + ) + + props = {"currentPage": "posts"} + response = make_response(jsonify(props), 200) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + +class TDDMPageProps(TypedDict): + account_id: int + dms: List[Unapproved_DM] + status: str + currentPage: Literal["import"] + + +@account_bp.get("/account/review_dms") +def importer_dms(): + account: Account = g.get("account") + + status = "ignored" if request.args.get("status") == "ignored" else "pending" + dms = get_unapproved_dms(account.id, request.args.get("status") == "ignored") + + props = TDDMPageProps(currentPage="import", account_id=account.id, dms=dms, status=status) + + response = make_response( + jsonify( + props, + ), + 200, + ) + response.headers["Cache-Control"] = "max-age=0, private, must-revalidate" + + return response + + +class TDApproveDMsBody(TypedDict): + approved_hashes: list[str] + delete_ignored: Optional[bool] + + +@account_bp.post("/account/review_dms") +def approve_importer_dms(): + account: Account = g.get("account") + body: TDApproveDMsBody = request.get_json() + approved_hashes = body.get("approved_hashes", []) + delete_ignored = bool(body.get("delete_ignored", False)) + approve_dms(int(account.id), approved_hashes) + clean_dms_already_approved(int(account.id)) + cleanup_unapproved_dms(int(account.id)) + + if delete_ignored: + cleanup_unapproved_dms(int(account.id), delete=True) + + response = make_response(jsonify(True), 200) + response.headers["Cache-Control"] = "max-age=0, private, must-revalidate" + + return response + + +@account_bp.errorhandler(401) +def not_authorized_error(error): + return (jsonify(error="Not Authorized"), 401) + +account_bp.register_blueprint(moderator_bp, url_prefix="/account") +# not sure about resolution order +# so load it in the end +v1api_bp.register_blueprint(account_bp) diff --git a/src/pages/api/v1/authentication.py b/src/pages/api/v1/authentication.py new file mode 100644 index 0000000..e0c375b --- /dev/null +++ b/src/pages/api/v1/authentication.py @@ -0,0 +1,122 @@ +import re +from json import JSONDecodeError +from typing import TypedDict + +import orjson +from flask import Blueprint, request, current_app, make_response, jsonify, g, session + +from src.lib.account import attempt_login, create_account +from src.lib.api import create_client_error_response +from src.lib.security import is_password_compromised +from src.types.account import Account + +from . import v1api_bp + +# it is in a separate file because +# all account routes require auth + +authentication_bp = Blueprint("authentication", __name__) + + +class TDRegistrationBody(TypedDict): + location: str + username: str + password: str + confirm_password: str + favorites_json: str + + +USERNAME_REGEX = re.compile(r"^[a-z0-9_@+.\-]{3,15}$") + + +@authentication_bp.post("/authentication/register") +def post_register(): + body: TDRegistrationBody = request.get_json() + username = body.get("username", "").replace("\x00", "").strip() + password = body.get("password", "").strip() + confirm_password = body.get("confirm_password", "").strip() + favorites_json = body.get("favorites", "[]") + + favorites = [] + if favorites_json != "": + try: + favorites = orjson.loads(favorites_json) + except JSONDecodeError: + pass + + if username == "": + return create_client_error_response("Username cannot be empty") + + if not USERNAME_REGEX.match(username): + return create_client_error_response("Invalid username") + + if password == "": + return create_client_error_response("Password cannot be empty") + + if len(password) < 5: + return create_client_error_response("Password must have at least 5 characters.") + + if password != confirm_password: + return create_client_error_response("Passwords do not match") + + if current_app.config.get("ENABLE_PASSWORD_VALIDATOR") and is_password_compromised(password): + return create_client_error_response( + "We've detected that password was compromised in a data breach on another site. Please choose a different password." + ) + + success = create_account(username, password, favorites) + + if not success: + return create_client_error_response("Username already taken") + + response = make_response(jsonify(True), 200) + + return response + + +class TDLoginBody(TypedDict): + username: str + password: str + + +@authentication_bp.post("/authentication/login") +def post_login(): + body: TDLoginBody = request.get_json() + + account: Account | None = g.get("account") + + if account: + return create_client_error_response("Already logged in", 409) + + username = body.get("username", "").replace("\x00", "") + password = body.get("password", "") + + if not username: + return create_client_error_response("Username is required.") + + if not password: + return create_client_error_response("Password is required.") + + (account, error_message) = attempt_login(username, password) + + if error_message: + return create_client_error_response(error_message) + + if not account: + return create_client_error_response("Account doesn't exist") + + response = make_response(jsonify(account), 200) + + return response + + +@authentication_bp.post("/authentication/logout") +def logout(): + if "account_id" in session: + session.pop("account_id") + response = make_response(jsonify(True), 200) + + return response + + +v1api_bp.register_blueprint(authentication_bp) diff --git a/src/pages/api/v1/comments.py b/src/pages/api/v1/comments.py index 3577308..7d19cf8 100644 --- a/src/pages/api/v1/comments.py +++ b/src/pages/api/v1/comments.py @@ -1,18 +1,70 @@ +import re + from flask import jsonify, make_response from src.lib.post import get_post_comments +from src.lib.api import create_not_found_error_response from src.pages.api.v1 import v1api_bp +boosty_emote_uuid_to_file = { + "05df7389-a9e9-4a51-aefc-96c9c374175c": "Heart.webp", + "7b3fda0d-5ea9-4e66-a8bd-a19c590c8cef": "ClappingHands.webp", + "bb6e8aaf-4ac6-4f71-b8a5-b9307f86071b": "HighVoltage.webp", + "3f26b442-06b1-4b94-b9bd-3a1af887057e": "BeamingFace.webp", + "2f73c11d-ed75-4638-bb2c-4d6911d95e63": "PartyPopper.webp", + "67198e42-128a-4a41-bf7a-94d7c98bb44f": "Star.webp", + "5781617f-106b-4c05-bec0-f55d3904307a": "Gemstone.webp", + "1ad0dde5-846e-4c54-a20f-2d54a8ab1b85": "Gaspar.webp", + "84149636-8701-4d5b-a92d-445ebc49d39c": "Hurt.webp", + "3a7d0922-8dc1-4175-90bc-561d3d2bda7d": "MoneyFace.webp", + "da0661bb-aae6-4e54-87fa-0c4065ec435b": "Rocket.webp", + "9db7bb0d-1148-4686-9d0c-643d2c94837b": "ExplodingHead.webp", + "37a6e1ec-f63a-416f-88d4-63e48a68c71b": "ThinkingFace.webp", + "bc1334a6-af7e-4618-b37e-2be63bf8a112": "CheckMark.webp", + "663cbbdf-639e-4dac-8912-6a580d3ef3e6": "CallMe.webp", + "517b1805-13dd-43ed-ac65-bfa0fd0d16b8": "Burn.webp", + "4cd3b821-ec9c-4d35-827e-30be025c3ca0": "FaceScreaming.webp", + "6d674dd1-789c-408f-9ab4-912e9a8d2539": "LoudlyFace.webp", + "f3a31f3c-24b4-4760-a577-10fb0cce8605": "NauseatedFace.webp", + "eb55272b-0724-4854-ad44-899ad286a992": "Eggplant.webp", + "c00141f1-7f23-4841-b8c1-e3cd85f8f5bb": "Apple.webp", + "0b02f581-876f-4b38-823e-00d8c026dc39": "Peach.webp", + "deb46686-294c-49ae-b987-6df4d41e2b9d": "Hamburger.webp", + "5969bcfa-3dc5-4e1b-95dd-b7a1567220fb": "Pizza.webp", + "04541c27-5491-49f6-b70d-aefbdff0884c": "Banana.webp", + "90ccef34-14cf-4528-8763-0d993d892dfe": "Moon.webp", + "76119773-3a29-4548-b4c8-811f7fdc2936": "Sun.webp", + "58b34b35-4d64-45d1-852e-274206dd90b7": "ColdFace.webp", + "fd3780a9-05f7-46fe-92ec-c5f6f2ef5aa3": "Devil.webp", + "97cb65d2-15b7-42d2-9d44-6165f16f3e6e": "Shield.webp", + "fedfd339-daaf-4bff-857f-4d68ae9e5727": "SweatDroplets.webp", + "58852139-f95e-4238-9c1c-871fa6d0889a": "Beach.webp", + "10f3bc42-fc33-437b-a452-de97c748ca22": "Ball.webp", + "5388e3ce-e4d5-4b0c-ba4c-5b58d8d35db9": "Gift.webp", + "9c3d8ff6-bf13-4255-b8ff-30b9c9c98162": "MyPressF.webp", + "97101bae-9beb-47b0-bfe2-70ac24bce094": "MyIlluminati.webp" +} + +boosty_emote_regex = re.compile(r"https:\/\/images\.boosty\.to\/smile\/([a-f0-9\-]+)\/size\/large.*?") @v1api_bp.get("//user//post//comments") def get_comments(service: str, creator_id: str, post_id: str): comments = get_post_comments(post_id, service) + if service == "boosty": + for comment in comments: + comment["content"] = boosty_emote_regex.sub( + lambda match: f"/thumbnail/boosty_smile/{boosty_emote_uuid_to_file.get(match.group(1), match.group(1))}", + comment["content"], + ) + if not comments: - response = make_response(jsonify({"error": "Not found"}), 404) + response = create_not_found_error_response("No comments found.") response.headers["Cache-Control"] = "s-maxage=600" + return response response = make_response(jsonify(comments), 200) response.headers["Cache-Control"] = "s-maxage=600" + return response diff --git a/src/pages/api/v1/creators.py b/src/pages/api/v1/creators.py index e86f19a..8ddc752 100644 --- a/src/pages/api/v1/creators.py +++ b/src/pages/api/v1/creators.py @@ -1,11 +1,74 @@ -from flask import jsonify, make_response +import random +from typing import TypedDict, Optional, Literal +from flask import jsonify, make_response, redirect, url_for, request, session, abort from src.internals.database.database import query_db from src.lib.announcements import get_artist_announcements -from src.lib.artist import get_artist, get_fancards_by_artist, get_linked_creators +from src.lib.artist import ( + get_artist, + get_fancards_by_artist, + get_linked_creators, + get_artists_by_update_time, + delete_creator_link, + create_unapproved_link_request, + get_random_artist_keys, + TDArtist +) +from src.lib.filehaus import get_artist_share_count, get_artist_shares +from src.lib.post import get_artist_post_count, get_artist_posts_summary, get_all_posts_by_artist, get_render_data_for_posts, get_fileserver_for_value +from src.lib.posts import get_all_tags, count_all_posts_for_tag, get_tagged_posts +from src.lib.dms import count_user_dms, get_artist_dms +from src.lib.api import create_client_error_response, create_not_found_error_response +from src.utils.utils import parse_int, positive_or_none, step_int, take, sort_dict_list_by, offset_list +from src.utils.decorators import require_login +from src.pages.artists_types import ArtistShareProps, ArtistDisplayData, ArtistDMsProps, LinkedAccountsProps +from src.types.account.account import Account +from src.types.paysites import Paysite, Paysites + from src.pages.api.v1 import v1api_bp +@v1api_bp.get("/artists/updated") +def updated(): + base = dict(commit=True, sort_by="updated") + limit = 50 + + results = get_artists_by_update_time(offset=0, limit=limit) + props = dict( + currentPage="artists", + display="cached updated artists", + count=len(results), + limit=limit, + ) + + response = make_response(jsonify(props=props, results=results, base=base), 200) + response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" + + return response + + +@v1api_bp.get("/artists/random") +def random_artist(): + """todo decide after random posts with redis list if its worth""" + artist = get_random_artist() + + if artist is None: + response = make_response(jsonify(error="No artist found"), 404) + + return response + + response = make_response(jsonify(service=artist["service"], artist_id=artist["id"]), 200) + + return response + + +def get_random_artist(): + artists = get_random_artist_keys(1000) + if len(artists) == 0: + return None + return random.choice(artists) + + @v1api_bp.get("/creators") def all_creators(): """this view must be cached at nginx/cdn level""" @@ -16,15 +79,11 @@ def all_creators(): l.service, EXTRACT(epoch from l.indexed)::int AS indexed, EXTRACT(epoch from l.updated)::int AS updated, - COALESCE(aaf.favorited, 0) AS favorited + COALESCE(aaf.favorite_count, 0) AS favorited FROM lookup l LEFT JOIN ( - SELECT - artist_id, - service, - COUNT(*) AS favorited - FROM account_artist_favorite - GROUP BY artist_id, service + SELECT * + FROM favorite_counts ) aaf ON l.id = aaf.artist_id AND l.service = aaf.service @@ -49,6 +108,107 @@ def get_creator(service, creator_id): return response +class TDProfileDisplayData(TypedDict): + service: str + href: str + + +class TDProfilePostsProps(TypedDict): + currentPage: Literal["posts"] + id: str + service: str + name: str + count: int + limit: int + artist: TDArtist + display_data: TDProfileDisplayData + dm_count: int + share_count: int + has_links: str + + +@v1api_bp.get("//user//posts-legacy") +def get_profile_posts_legacy(service: str, creator_id: str): + if not service: + return create_client_error_response("Service name is required.") + + if not creator_id: + return create_client_error_response("Profile ID is required.") + + if service == "discord": + return create_client_error_response("Discord servers not allowed.") + + artist = get_artist(service, creator_id) + + if not artist: + return create_not_found_error_response() + + if artist["public_id"] == creator_id and artist["id"] != creator_id: + return create_client_error_response("Something something profile ID mismatch.") + + query = request.args.get("q", default="").strip() + tags = sorted(request.args.getlist("tag")) + limit = 50 + offset = positive_or_none(step_int(parse_int(request.args.get("o"), 0), limit)) + + if offset is None: + return create_client_error_response(f"Offset is not a multiple of {limit}.") + + if tags: + posts = get_tagged_posts(tags, offset, limit, service, creator_id) + total_count = count_all_posts_for_tag(tags, service, creator_id) + elif not query or len(query) < 2: + total_count = get_artist_post_count(service, creator_id) + + if offset > total_count: + return create_client_error_response(f"Offset {offset} is bigger than total count {total_count}.") + else: + posts = get_artist_posts_summary(creator_id, service, offset, limit, "published DESC NULLS LAST") + else: + (posts, total_count) = do_artist_post_search(creator_id, service, query, offset, limit) + + ( + result_previews, + result_attachments, + result_is_image, + ) = get_render_data_for_posts(posts) + + base = request.args.to_dict() + base.pop("o", None) + base["service"] = service + base["artist_id"] = creator_id + + props = TDProfilePostsProps( + currentPage="posts", + id=creator_id, + service=service, + name=artist["name"], + count=total_count, + limit=limit, + artist=artist, + display_data=make_artist_display_data(artist), + dm_count=count_user_dms(service, creator_id), + share_count=get_artist_share_count(service, creator_id), + has_links="✔️" if artist["relation_id"] else "0", + ) + + response = make_response( + jsonify( + props=props, + base=base, + results=posts, + result_previews=result_previews, + result_attachments=result_attachments, + result_is_image=result_is_image, + disable_service_icons=True, + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + @v1api_bp.get("//user//announcements") def get_announcements(service, creator_id): artist = get_artist(service, creator_id) @@ -65,13 +225,24 @@ def get_announcements(service, creator_id): @v1api_bp.get("//user//fancards") def get_fancards(service, creator_id): artist = get_artist(service, creator_id) + if not artist: response = make_response(jsonify({"error": "Artist not found."}), 404) response.headers["Cache-Control"] = "s-maxage=600" + return response + fancards = get_fancards_by_artist(creator_id, reload=True) + + for fancard in fancards: + fhash = fancard["hash"] + ext = fancard["ext"] + fancard["path"] = f"/data/{fhash[0:2]}/{fhash[2:4]}/{fhash}{ext}" + fancard["server"] = get_fileserver_for_value(fancard["path"]) + response = make_response(jsonify(fancards), 200) response.headers["Cache-Control"] = "s-maxage=600" + return response @@ -82,3 +253,261 @@ def get_linked_accounts(service, creator_id): response = make_response(jsonify(links), 200) response.headers["Cache-Control"] = "s-maxage=600" return response + + +@v1api_bp.delete("//user//links") +@require_login +def delete_linked_account(service: str, creator_id: str, user: Account): + if user.role != "administrator": + abort(404) + else: + delete_creator_link(service, creator_id) + return "", 204 + + +@v1api_bp.get("//user//links/new") +@require_login +def get_new_link_page(service: str, artist_id: str, user: Account): + artist = get_artist(service, artist_id) + if not artist: + response = make_response(jsonify({"error": "Artist not found."}), 404) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + elif artist["public_id"] == artist_id and artist["id"] != artist_id: + return redirect(url_for(".get_new_link_page", service=service, artist_id=artist["id"]), code=301) + + base = dict(service=service, artist_id=artist_id) + props = LinkedAccountsProps( + id=artist_id, + service=service, + artist=artist, + share_count=get_artist_share_count(service, artist_id), + dm_count=count_user_dms(service, artist_id), + has_links="✔️" if artist["relation_id"] else "0", + display_data=make_artist_display_data(artist), + ) + + response = make_response( + jsonify( + props=props, + base=base, + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + + +class TDArtistLinkRequest(TypedDict): + service: str + artist_id: str + reason: Optional[str] + + +@v1api_bp.post("//user//links/new") +@require_login +def post_new_link_page(service: str, artist_id: str, user: Account): + body: TDArtistLinkRequest = request.get_json() + + dest_service = body["service"] + dest_artist_id = body["artist_id"] + reason = body["reason"] or "" + + from_artist = get_artist(service, artist_id) + to_artist = get_artist(dest_service, dest_artist_id) + + if not from_artist: + response = make_response(jsonify({"error": "Artist not found."}), 404) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + elif from_artist["public_id"] == artist_id and from_artist["id"] != artist_id: + return redirect(url_for(".post_new_link_page", service=service, artist_id=from_artist["id"]), code=301) + + base = dict(service=service, artist_id=artist_id) + props = LinkedAccountsProps( + id=artist_id, + service=service, + artist=from_artist, + share_count=get_artist_share_count(service, artist_id), + dm_count=count_user_dms(service, artist_id), + has_links="✔️" if from_artist["relation_id"] else "0", + display_data=make_artist_display_data(from_artist), + ) + + if not to_artist: + message = f"Invalid creator (svc: {dest_service}, id: {dest_artist_id})" + response = make_response(jsonify(error=message), 400) + + return response + + if len(reason) > 140: + message = "Reason is too long" + response = make_response(jsonify(error=message), 400) + + return response + + if dest_service == service and dest_artist_id == artist_id: + message = "Can't link an artist to themself" + response = make_response(jsonify(error=message), 400) + + return response + + if from_artist["relation_id"] == to_artist["relation_id"] and from_artist["relation_id"] is not None: + message = "Already linked" + response = make_response(jsonify(error=message), 400) + + return response + + create_unapproved_link_request(from_artist, to_artist, user.id, reason) + + response = make_response( + jsonify( + message="Request created. It will be shown here when approved.", + props=props, + base=base, + ), + 200, + ) + + return response + + +@v1api_bp.get("//user//tags") +def get_tags(service: str, artist_id: str): + artist = get_artist(service, artist_id) + if not artist: + response = make_response(jsonify({"error": "Artist not found."}), 404) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + elif artist["public_id"] == artist_id and artist["id"] != artist_id: + return redirect(url_for(".get_tags", service=service, artist_id=artist["id"]), code=301) + + tags = get_all_tags(service, artist_id) + props = dict( + display_data=make_artist_display_data(artist), + artist=artist, + service=service, + id=artist["id"], + share_count=get_artist_share_count(service, artist_id), + dm_count=count_user_dms(service, artist_id), + has_links="✔️" if artist["relation_id"] else "0", + ) + response = make_response( + jsonify( + props=props, + tags=tags, + service=service, + artist=artist, + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + + +@v1api_bp.route("//user//shares") +def get_shares(service: str, artist_id: str): + base = request.args.to_dict() + base.pop("o", None) + base["service"] = service + base["artist_id"] = artist_id + + dm_count = count_user_dms(service, artist_id) + shares = get_artist_shares(artist_id, service) + + artist = get_artist(service, artist_id) + if artist is None: + response = make_response(jsonify({"error": "Artist not found."}), 404) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + elif artist["public_id"] == artist_id and artist["id"] != artist_id: + return redirect(url_for(".get_shares", service=service, artist_id=artist["id"]), code=301) + + props = ArtistShareProps( + display_data=make_artist_display_data(artist), + service=service, + artist=artist, + id=artist_id, + dm_count=dm_count, + share_count=len(shares), + has_links="✔️" if artist["relation_id"] else "0", + ) + + response = make_response( + jsonify(results=shares, props=props, base=base), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + +@v1api_bp.route("//user//dms") +def get_dms(service: str, artist_id: str): + + artist = get_artist(service, artist_id) + + if artist is None: + response = make_response(jsonify({"error": "Artist not found."}), 404) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + elif artist["public_id"] == artist_id and artist["id"] != artist_id: + return redirect(url_for(".get_dms", service=service, artist_id=artist["id"]), code=301) + + dms = get_artist_dms(service, artist_id) + + props = ArtistDMsProps( + id=artist_id, + service=service, + artist=artist, + display_data=make_artist_display_data(artist), + share_count=get_artist_share_count(service, artist_id), + dm_count=len(dms), + dms=dms, + has_links="✔️" if artist["relation_id"] else "0", + ) + + response = make_response( + jsonify( + props=props, + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + +def make_artist_display_data(artist: dict) -> ArtistDisplayData: + service_name: str = artist["service"] + pay_site: Paysite | None = getattr(Paysites, service_name, None) + + if pay_site: + return ArtistDisplayData(service=pay_site.title, href=pay_site.user.profile(artist)) + + raise Exception("Service not found in Paysites") + + +def do_artist_post_search(artist_id: str, service: str, search: str, o: int, limit: int): + posts = get_all_posts_by_artist(artist_id, service) + search = search.lower() + + matches = [] + for post in posts: + if ( + search in post["content"].lower() + or search in post["title"].lower() + or search in " ".join(post["tags"] or []).lower() + ): + matches.append(post) + + matches = sort_dict_list_by(matches, "published", True) + + return take(limit, offset_list(o, matches)), len(matches) diff --git a/src/pages/api/v1/dms.py b/src/pages/api/v1/dms.py index cf3c876..c7ff59d 100644 --- a/src/pages/api/v1/dms.py +++ b/src/pages/api/v1/dms.py @@ -1,9 +1,72 @@ -from flask import jsonify, make_response, session +from typing import TypedDict, Literal, List + +from flask import request, jsonify, make_response, session + +from src.config import Configuration +from src.lib.dms import ( + has_unapproved_dms, + get_all_dms, + get_all_dms_by_query, + get_all_dms_by_query_count, + get_all_dms_count, +) +from src.lib.api import create_client_error_response +from src.utils.utils import get_query_parameters_dict, parse_int, positive_or_none, step_int +from src.types.kemono import Approved_DM -from src.lib.dms import has_unapproved_dms from src.pages.api.v1 import v1api_bp +class TDDMsProps(TypedDict): + currentPage: Literal["artists"] + count: int + limit: int + dms: List[Approved_DM] + + +@v1api_bp.get("/dms") +def get_api_dms(): + base = get_query_parameters_dict(request, on_errors="ignore", clean_query_string=True) + + limit = 50 + max_offset = limit * 1000 # only load 1000 pages of any result + offset = positive_or_none(step_int(parse_int(base.pop("o", 0), 0), limit)) + + if offset is None or offset > max_offset: + return create_client_error_response("Offset is bigger than maximum offset.") + + query = base.get("q", "").strip()[: Configuration().webserver["max_full_text_search_input_len"]] + + if not query or len(query) < 3: + total_count = get_all_dms_count() + + if offset > total_count: + return create_client_error_response("Offset is bigger than the total count.") + + dms = get_all_dms(offset, limit) + + else: + total_count = get_all_dms_by_query_count(query) + + if offset > total_count: + return create_client_error_response("Offset is bigger than the total count.") + + dms = get_all_dms_by_query(query, offset, limit) + + props = TDDMsProps(currentPage="artists", count=total_count, limit=limit, dms=dms) + + response = make_response( + jsonify( + props=props, + base=base, + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + @v1api_bp.get("/has_pending_dms") def get_has_pending_dms(): has_pending_dms = False diff --git a/src/pages/api/v1/favorites.py b/src/pages/api/v1/favorites.py index 9631860..4dec1c2 100644 --- a/src/pages/api/v1/favorites.py +++ b/src/pages/api/v1/favorites.py @@ -31,25 +31,33 @@ def list_account_favorites(user: Account): @require_login def post_favorite_post(service, creator_id, post_id, user: Account): add_favorite_post(user.id, service, creator_id, post_id) - return "", 204 + response = make_response(jsonify(True), 200) + + return response @v1api_bp.route("/favorites/creator//", methods=["POST"]) @require_login def post_favorite_artist(service, creator_id, user: Account): add_favorite_artist(user.id, service, creator_id) - return "", 204 + response = make_response(jsonify(True), 200) + + return response @v1api_bp.route("/favorites/post///", methods=["DELETE"]) @require_login def delete_favorite_post(service, creator_id, post_id, user: Account): remove_favorite_post(user.id, service, creator_id, post_id) - return "", 204 + response = make_response(jsonify(True), 200) + + return response @v1api_bp.route("/favorites/creator//", methods=["DELETE"]) @require_login def delete_favorite_artist(service, creator_id, user: Account): remove_favorite_artist(user.id, service, creator_id) - return "", 204 + response = make_response(jsonify(True), 200) + + return response diff --git a/src/pages/api/v1/files.py b/src/pages/api/v1/files.py index 620d6aa..d78f1b5 100644 --- a/src/pages/api/v1/files.py +++ b/src/pages/api/v1/files.py @@ -1,33 +1,118 @@ +import re from flask import jsonify, make_response, request from src.config import Configuration -from src.lib.files import get_file_relationships, try_set_password +from src.lib.files import get_archive_files, get_file_relationships, try_set_password +from src.utils.utils import get_query_parameters_dict, parse_int, positive_or_none, step_int +from src.lib.filehaus import get_all_shares_count, get_files_for_share, get_share, get_shares +from src.lib.api import create_not_found_error_response, create_client_error_response from src.pages.api.v1 import v1api_bp -from src.utils.utils import get_query_parameters_dict + + +HASH_REGEX = re.compile(r"[a-f0-9]{64}") @v1api_bp.get("/search_hash/") -def lookup_file(file_hash): - if not (len(file_hash) == 64 and all(c in "0123456789abcdefABCDEF" for c in file_hash)): - response = make_response(jsonify({"error": "Invalid SHA256 hash"}), 400) - return response - if not (file := get_file_relationships(file_hash)): - response = make_response("{}", 404) - response.headers["Cache-Control"] = "s-maxage=600" - return response +def lookup_file(file_hash: str): + file_hash = file_hash.lower() + if not HASH_REGEX.match(file_hash): + return create_client_error_response("Invalid SHA256 hash") + + file = get_file_relationships(file_hash) response = make_response(jsonify(file), 200) response.headers["Cache-Control"] = "s-maxage=600" + return response -@v1api_bp.get("/set_password") -def set_password(): +@v1api_bp.get("/file/") +def get_archive_data(file_hash: str): + print("get_archive_data for", file_hash) + file_hash = file_hash.lower() + if not HASH_REGEX.match(file_hash): + return create_client_error_response("Invalid SHA256 hash") + + archive = get_archive_files(file_hash) + + if not archive: + return create_client_error_response("File not found", 404) + + response = make_response(jsonify(archive), 200) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + + +@v1api_bp.patch("/file/") +def set_archive_password(file_hash: str): if not Configuration().archive_server["enabled"]: - return "false" - q = get_query_parameters_dict(request) - file_hash = q.get("file_hash") - passwords = [password for password in request.args.getlist("password") if password] - if not file_hash or not passwords or not try_set_password(file_hash, passwords): - return "false" - return "true" + return create_not_found_error_response() + + file_hash = file_hash.lower() + if not HASH_REGEX.match(file_hash): + return create_client_error_response("Invalid SHA256 hash") + + passwords: list[str] = request.get_json() + + if try_set_password(file_hash, passwords): + return make_response(jsonify("ok"), 200) + return create_client_error_response("Invalid password") + + +@v1api_bp.route("/shares") +def get_shares_data(): + base = request.args.to_dict() + base.pop("o", None) + + limit = 50 + offset = positive_or_none(step_int(parse_int(request.args.get("o"), 0), limit)) + # query = request.args.get('q') + + shares = None + total_count = None + (shares, total_count) = get_share_page(offset, limit) + + props = dict(currentPage="shares", count=total_count, shares=shares, limit=limit) + + response = make_response( + jsonify( + props=props, + base=base, + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response + + +def get_share_page(offset: int, limit: int): + posts = get_shares(offset, limit) + total_count = get_all_shares_count() + + return posts, total_count + + +@v1api_bp.get("/share/") +def get_share_handler(share_id: str): + base = request.args.to_dict() + base.pop("o", None) + + if (not share_id.isdigit()): + return create_client_error_response("Invalid share ID.") + + share = get_share(int(share_id)) + + if share is None: + return create_not_found_error_response("Share not found.") + + share_files = get_files_for_share(share["id"]) + + response = make_response( + jsonify(share_files=share_files, share=share, base=base), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=60" + + return response diff --git a/src/pages/api/v1/flags.py b/src/pages/api/v1/flags.py deleted file mode 100644 index df045f2..0000000 --- a/src/pages/api/v1/flags.py +++ /dev/null @@ -1,30 +0,0 @@ -from src.config import Configuration -from src.internals.cache.redis import get_conn -from src.internals.database.database import query_db -from src.lib.post import is_post_flagged -from src.pages.api.v1 import v1api_bp - - -@v1api_bp.route("//user//post//flag", methods=["POST"]) -def flag_post_api(service, creator_id, post): - query = """ - INSERT INTO booru_flags (id, "user", service) - SELECT %s, %s, %s - WHERE NOT EXISTS ( - SELECT 1 - FROM booru_flags - WHERE id = %s AND "user" = %s AND service = %s - ) - RETURNING id, service - """ - rows_returned = query_db(query, (post, creator_id, service, post, creator_id, service)) - get_conn().set( - f"is_post_flagged:{service}:{creator_id}:{post}", len(rows_returned), ex=Configuration().redis["default_ttl"] - ) - - return "", (201 if len(rows_returned) else 409) - - -@v1api_bp.route("/user//post//flag", methods=["GET"]) -def flag_api(service, creator_id, post): - return "", 200 if is_post_flagged(service, creator_id, post) else 404 diff --git a/src/pages/api/v1/importer.py b/src/pages/api/v1/importer.py index 8cce42a..f875f52 100644 --- a/src/pages/api/v1/importer.py +++ b/src/pages/api/v1/importer.py @@ -1,63 +1,128 @@ import base64 import logging import re +from typing import TypedDict, Union, Literal, NotRequired import orjson -from flask import current_app, make_response, render_template, request, session +from flask import make_response, request, session, jsonify from src.config import Configuration from src.internals.cache.redis import get_conn -from src.internals.database.database import query_db, query_one_db +from src.internals.database.database import query_db, query_one_db, query_rowcount_db from src.lib.imports import validate_import_key +from src.lib.api import create_client_error_response from src.pages.api.v1 import v1api_bp -from src.types.props import SuccessProps + +TDOnlyFansImportCreateBody = TypedDict( + "TDOnlyFansImportCreateBody", + { + "service": Literal["onlyfans"], + "session_key": str, + "auto_import": str | int | None, + "save_session_key": str | int | None, + "x-bc": str, + "auth_id": str, + "user_agent": str, + }, +) + + +class TDPatreonImportCreateBody(TypedDict): + service: Literal["patreon"] + session_key: str + auto_import: str | int | None + save_session_key: str | int | None + save_dms: NotRequired[bool] + + +class TDDiscordImportCreateBody(TypedDict): + service: Literal["discord"] + session_key: str + auto_import: str | int | None + save_session_key: str | int | None + channel_ids: str @v1api_bp.post("/importer/submit") def importer_submit(): - if not session.get("account_id") and request.form.get("save_dms") and request.form.get("service") == "patreon": - return "You must be logged in to import direct messages.", 401 - - if not request.form.get("session_key"): - return "Session key missing.", 401 - - key = request.form.get("session_key").strip().strip("\" \t'") - if request.form.get("service") == "onlyfans": - key = base64.b64encode( - orjson.dumps( - { - "sess": key, - "x-bc": request.form.get("x-bc").strip().strip("\" \t'"), - "auth_id": request.form.get("auth_id").strip().strip("\" \t'"), - "auth_uid_": "None", - "user_agent": request.form.get("user_agent").strip().strip("\" \t'"), - } - ) - ).decode() - result = validate_import_key(key, request.form.get("service")) + """ + TODO: split into per-service endpoints + """ + body: Union[TDOnlyFansImportCreateBody, TDPatreonImportCreateBody, TDDiscordImportCreateBody] = request.get_json() + account_id = session.get("account_id") + session_key = body.get("session_key") + auto_import = body.get("auto_import") + save_session_key = body.get("save_session_key") + country = request.headers.get(Configuration().webserver["country_header_key"]) + user_agent = request.headers.get("User-Agent") + save_dms = None + key = session_key.strip().strip("\" \t'") discord_channels = None - if (input_channels := request.form.get("channel_ids")) and request.form.get("service") == "discord": + result = None + + if not session_key: + return create_client_error_response("Session key missing.", 401) + + if not body.get("service"): + return create_client_error_response("Service is required.", 400) + + # per service validation + if body["service"] == "patreon": + save_dms = body.get("save_dms") + + if not account_id and save_dms: + return create_client_error_response("You must be logged in to import direct messages.", 401) + + elif body["service"] == "onlyfans": + xBC = body["x-bc"].strip().strip("\" \t'") + auth_id = body["auth_id"].strip().strip("\" \t'") + of_user_agent = body["user_agent"].strip().strip("\" \t'") + key_dict = { + "sess": key, + "x-bc": xBC, + "auth_id": auth_id, + "auth_uid_": "None", + "user_agent": of_user_agent, + } + key = base64.b64encode(orjson.dumps(key_dict)).decode() + + elif body["service"] == "discord": + channel_ids = body["channel_ids"] regex = r"https://discord\.com/channels/\d+/(?P\d+)" - input_channels = [ - re.match(regex, item).group("ch") if re.match(regex, item) else item for item in input_channels.split(",") + + if not channel_ids: + return create_client_error_response("Channel IDs is required.") + + temp_input_channels = [ + re.match(regex, item).group("ch") if re.match(regex, item) else item for item in channel_ids.split(",") ] - discord_channels = list(s.strip() for s in re.split(r"[\s,.、。/']", ",".join(input_channels)) if s.strip()) + + discord_channels = list( + s.strip() for s in re.split(r"[\s,.、。/']", ",".join(temp_input_channels)) if s.strip() + ) + if any(not s.isdigit() for s in discord_channels): msg = "Discord channel ids are numbers, the last number of the url (notice the / between the 2 numbers)" - logging.exception(msg, extra=dict(input_channels=input_channels, discord_channels=discord_channels)) - return msg, 422 + logging.exception(msg, extra=dict(input_channels=channel_ids, discord_channels=discord_channels)) + + return create_client_error_response(msg, 422) + if not discord_channels: msg = "Discord submit requires channels" - logging.exception(msg, extra=dict(input_channels=input_channels, discord_channels=discord_channels)) - return msg, 422 + logging.exception(msg, extra=dict(input_channels=channel_ids, discord_channels=discord_channels)) + + return create_client_error_response(msg, 422) + discord_channels = ",".join(discord_channels) + result = validate_import_key(key, body["service"]) + if not result.is_valid: return "\n".join(result.errors), 422 formatted_key = result.modified_result if result.modified_result else key - service = request.form.get("service") + service = body["service"] queue_name = f"import:{service}" existing_imports = query_db( @@ -68,44 +133,50 @@ def importer_submit(): AND queue_name = %s AND job_input ->> 'key' = %s """, - (queue_name, formatted_key) + (queue_name, formatted_key), ) if existing_imports: existing_import = existing_imports[0]["job_id"] - props = SuccessProps( - message="This key is already being used for an import. Redirecting to logs...", - currentPage="import", - redirect=f"/importer/status/{existing_import}{"?dms=1" if request.form.get("save_dms") else ""}", + + _update_count = query_rowcount_db( + f""" + UPDATE public.jobs + SET priority = LEAST(priority, 1) - 1 + WHERE job_id = %s; + """, + (str(existing_import),), ) - return make_response(render_template("success.html", props=props), 200) + response = make_response(jsonify(import_id=existing_import), 200) + + return response data = dict( key=formatted_key, service=service, channel_ids=discord_channels, - auto_import=request.form.get("auto_import"), - save_session_key=request.form.get("save_session_key"), - save_dms=request.form.get("save_dms"), - contributor_id=session.get("account_id"), + auto_import=auto_import, + save_session_key=save_session_key, + save_dms=save_dms, + contributor_id=account_id, priority=1, - country=request.headers.get(Configuration().webserver["country_header_key"]), - user_agent=request.headers.get("User-Agent"), + country=country, + user_agent=user_agent, ) query = b""" - INSERT INTO jobs (queue_name, priority, job_input) - VALUES (%s, %s, %s) - RETURNING job_id; + INSERT INTO jobs + (queue_name, priority, job_input) + VALUES + (%s, %s, %s) + RETURNING + job_id; """ import_id = query_one_db(query, (queue_name, 1, orjson.dumps(data).decode()))["job_id"] - props = SuccessProps( - currentPage="import", - redirect=f"/importer/status/{import_id}{"?dms=1" if request.form.get("save_dms") else ""}", - ) + response = make_response(jsonify(import_id=import_id), 200) - return make_response(render_template("success.html", props=props), 200) + return response @v1api_bp.route("/importer/logs/") diff --git a/src/pages/api/v1/moderator.py b/src/pages/api/v1/moderator.py new file mode 100644 index 0000000..7f67e3e --- /dev/null +++ b/src/pages/api/v1/moderator.py @@ -0,0 +1,40 @@ +from flask import Blueprint, g, make_response, jsonify, abort, request + +from src.lib.artist import get_unapproved_links_with_artists +from src.lib.artist import approve_unapproved_link_request, reject_unapproved_link_request +from src.types.account import Account + +moderator_bp = Blueprint("administrator", __name__) + + +@moderator_bp.before_request +def check_credentials(): + account: Account = g.get("account") + + if account.role != "moderator" and account.role != "administrator": + return abort(code=404) + + +@moderator_bp.get("/moderator/tasks/creator_links") +def get_creator_links(): + links = get_unapproved_links_with_artists() + + response = make_response(jsonify(links), 200) + + return response + + +@moderator_bp.post("/moderator/creator_link_requests//approve") +def approve_request(request_id: int): + approve_unapproved_link_request(request_id) + response = make_response(jsonify({"response": "approved"}), 200) + + return response + + +@moderator_bp.post("/moderator/creator_link_requests//reject") +def reject_request(request_id: int): + reject_unapproved_link_request(request_id) + response = make_response(jsonify({"response": "rejected"}), 200) + + return response diff --git a/src/pages/api/v1/posts.py b/src/pages/api/v1/posts.py index 7af06cc..2cdab7e 100644 --- a/src/pages/api/v1/posts.py +++ b/src/pages/api/v1/posts.py @@ -1,11 +1,64 @@ -from flask import jsonify, make_response, request +import datetime +import json +import re +import logging +from typing import cast, get_args, TypedDict +from pathlib import PurePath + +import dateutil.parser +from datetime import date +from flask import jsonify, make_response, request, url_for, redirect, session from src.config import Configuration -from src.lib.post import get_artist_posts_full, get_post, get_post_revisions -from src.lib.posts import get_all_posts_for_query, get_all_posts_summary -from src.pages.api.v1 import v1api_bp +from src.lib.artist import get_artist +from src.lib.post import ( + flag_post, + get_artist_posts_full, + get_post, + get_post_revisions, + get_random_post_key, + get_post_by_id, + get_render_data_for_posts, + get_fileserver_for_value, + get_posts_incomplete_rewards, + is_post_flagged, + patch_inline_img, + TDPostRevision +) +from src.lib.posts import get_all_posts_for_query, get_all_posts_summary, get_popular_posts_for_date_range, \ + get_all_tags, count_all_posts, Post, count_all_posts_for_query, get_tagged_posts, count_all_posts_for_tag, \ + POST_FLAG_REASON_SLUG_TO_NUMBER +from src.lib.files import get_archive_files +from src.lib.api import create_not_found_error_response, create_client_error_response from src.pages.artists import do_artist_post_search -from src.utils.utils import get_query_parameters_dict, parse_int, positive_or_none, step_int +from src.pages.post import ready_post_props +from src.utils.datetime_ import PeriodScale, parse_scale_string +from src.utils.utils import get_query_parameters_dict, parse_int, positive_or_none, step_int, set_query_parameter, images_pattern, sanitize_html, limit_int + +from src.pages.api.v1 import v1api_bp + + +@v1api_bp.get("//post/") +def get_by_id(service, post_id): + post = get_post_by_id(post_id, service) + + if not post: + message = "No post found" + response = make_response(jsonify(error=message), 404) + + return response + + response = make_response( + jsonify( + service=post["service"], + artist_id=post["user"], + post_id=post["id"], + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=86400" + + return response @v1api_bp.get("//user//posts") @@ -18,21 +71,39 @@ def list_posts_api(service, creator_id): return response query = request.args.get("q", default="").strip() + if not query or len(query) < 2: posts = get_artist_posts_full(creator_id, service, offset, limit, "published DESC NULLS LAST") else: (posts, total_count) = do_artist_post_search(creator_id, service, query, offset, limit) response = make_response(jsonify(posts), 200) + return response +video_extensions = Configuration().webserver["ui"]["video_extensions"] + + @v1api_bp.get("//user//post/") def get_post_api(service, creator_id, post_id): post = get_post(service, creator_id, post_id) + if not post or post["user"] != creator_id: - response = make_response(jsonify({"error": "Not Found"}), 404) - return response - response = make_response(jsonify(post), 200) + return create_not_found_error_response() + + attachments, previews, videos, props = ready_post_props_light(post) + response = make_response( + jsonify( + post=post, + attachments=attachments, + previews=previews, + videos=videos, + props=props + ), + 200 + ) + response.headers["Cache-Control"] = "s-maxage=60" + return response @@ -41,10 +112,63 @@ def list_post_revision_api(service, creator_id, post_id): revisions = get_post_revisions(service, creator_id, post_id) response = make_response(jsonify(revisions), 200 if revisions else 404) response.headers["Cache-Control"] = "max-age=600" + return response -@v1api_bp.route("/posts") +@v1api_bp.route("//user//post//revision/") +def get_post_revision(service: str, artist_id: str, post_id: str, revision_id: str): + revisions = get_post_revisions(service, artist_id, post_id) if revision_id.isdigit() else [] + revision = next((rev for rev in revisions if rev["revision_id"] == int(revision_id)), None) + + if not revision or not ( + service == revision["service"] and artist_id == revision["user"] and post_id == revision["id"] + ): + message = "No post revision found" + response = make_response(jsonify(error=message), 404) + + return response + + attachments, comments, previews, videos, props = ready_post_props(revision) + props["currentPage"] = "revisions" + + response = make_response( + jsonify( + props=props, + post=revision, + comments=comments, + result_previews=previews, + result_attachments=attachments, + videos=videos, + archives_enabled=Configuration().archive_server["enabled"], + ), + 200, + ) + response.headers["Cache-Control"] = "s-maxage=600" + + return response + + +@v1api_bp.post("//user//post//flag") +def flag_post_api(service, user_id, post_id): + flagger_id = session.get("account_id") + if not flagger_id: + return make_response(jsonify(error="Not logged in"), 401) + + body = request.get_json() + reason = body.get("reason") + + if not reason: + return make_response(jsonify(error="No reason provided"), 400) + + if reason not in POST_FLAG_REASON_SLUG_TO_NUMBER: + return make_response(jsonify(error="Invalid reason provided"), 400) + + result = flag_post(service, user_id, post_id, POST_FLAG_REASON_SLUG_TO_NUMBER[reason], flagger_id, request.remote_addr) + return make_response(jsonify(result), 200) + + +@v1api_bp.get("/posts") def recent(): limit = 50 query_params = get_query_parameters_dict(request, on_errors="ignore", clean_query_string=True) @@ -52,17 +176,31 @@ def recent(): extra_pages = Configuration().webserver["extra_pages_to_load_on_posts"] max_offset = limit * 1000 # only load 1000 pages of any result query = query_params.get("q", "").strip()[: Configuration().webserver["max_full_text_search_input_len"]] + tags = request.args.getlist("tag") o = query_params.pop("o", 0) offset = positive_or_none(step_int(parse_int(o, 0), limit)) + if offset is None or offset > max_offset: - response = make_response(jsonify({"error": "offset not multiple of 150 or too large"}), 400) - return response + return create_client_error_response("offset not multiple of 150 or too large") + extra_offset = positive_or_none(step_int(parse_int(o, 0), limit * extra_pages)) slice_offset = offset - extra_offset - if not query or len(query) < 2: - extra_results = get_all_posts_summary(extra_offset, limit * extra_pages, cache_ttl=Configuration().cache_ttl_for_recent_posts)[slice_offset : limit + slice_offset] - # true_count = count_all_posts() - # count = limit_int(count_all_posts(), max_offset) + count = 0 + true_count = 0 + + if tags: + extra_results = get_tagged_posts(tags, extra_offset, limit * extra_pages) + total_count = count_all_posts_for_tag(tags) + true_count = total_count + count = limit_int(total_count, max_offset) + + elif not query or len(query) < 2: + extra_results = get_all_posts_summary( + extra_offset, limit * extra_pages, cache_ttl=Configuration().cache_ttl_for_recent_posts + )[slice_offset : limit + slice_offset] + true_count = count_all_posts() + count = limit_int(true_count, max_offset) + else: try: extra_results = get_all_posts_for_query(query, extra_offset, limit * extra_pages) @@ -73,23 +211,362 @@ def recent(): query_params["q"] = query extra_results = get_all_posts_for_query(query, extra_offset, limit * extra_pages) - # count not used - # if not offset and len(extra_results) < limit: - # true_count = 0 - # count = len(extra_results) - # else: - # try: - # true_count = count_all_posts_for_query(query) - # count = limit_int(props["true_count"], max_offset) - # except Exception as count_error: # catch timeouts, set count as max offset - # logging.exception( - # "Caught error in count_all_posts_for_query", - # extra={"e": count_error}, - # ) - # true_count = 0 - # count = max_offset + if not offset and len(extra_results) < limit: + true_count = 0 + count = len(extra_results) + else: + try: + true_count = count_all_posts_for_query(query) + count = limit_int(true_count, max_offset) + except Exception as count_error: # catch timeouts, set count as max offset + logging.exception( + "Caught error in count_all_posts_for_query", + extra={"e": count_error}, + ) + true_count = 0 + count = max_offset results = extra_results[slice_offset : limit + slice_offset] - response = make_response(jsonify(results), 200) + response = make_response( + jsonify( + count=count, + true_count=true_count, + posts=results + ), + 200 + ) response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" + return response + + +@v1api_bp.get("/posts/random") +def random_post(): + post = get_random_post_key(Configuration().webserver.get("table_sample_bernoulli_sample_size")) + + if post is None: + message = "No post found" + response = make_response(jsonify(error=message), 404) + + return response + + response = make_response( + jsonify( + service=post["service"], + artist_id=post["user"], + post_id=post["id"], + ), + 200, + ) + + return response + + +@v1api_bp.get("/posts/popular") +def popular_posts(): + query_params = get_query_parameters_dict(request) + earliest_date_for_popular: date = Configuration().webserver.get("earliest_date_for_popular") + # checked below but doesn't typecheck without a cast + scale = cast(PeriodScale, query_params.get("period", "recent")) + + if scale not in get_args(PeriodScale): + scale = "recent" + + info, valid_date = parse_scale_string(query_params.get("date"), scale) + does_not_match_step_date = scale != "recent" and info.date.date() != info.navigation_dates[scale][2] + + if ( + not valid_date + or does_not_match_step_date + or info.date.date() > datetime.date.today() + or info.date.date() < earliest_date_for_popular + ): + correct_date = info.navigation_dates[scale][2].isoformat() + + if info.date.date() > datetime.date.today(): + correct_date = datetime.date.today() + scale = "day" + + elif info.date.date() < earliest_date_for_popular: + correct_date = earliest_date_for_popular + scale = "day" + + new_url = set_query_parameter(url_for("api.v1.popular_posts"), {"date": correct_date, "period": scale}) + response = redirect(new_url) + cache_seconds = int(datetime.timedelta(days=7).total_seconds()) + + if info.date.date() > datetime.date.today(): + cache_seconds = int(datetime.timedelta(hours=3).total_seconds()) + + response.headers["Cache-Control"] = f"max-age={cache_seconds}" + + return response + + expiry = int(datetime.timedelta(days=30).total_seconds()) + + if scale == "recent": + expiry = int(datetime.timedelta(minutes=30 + 1).total_seconds()) + elif info.max_date > datetime.datetime.utcnow(): + if scale == "day": + expiry = int(datetime.timedelta(hours=3).total_seconds()) + + elif scale == "week": + expiry = int(datetime.timedelta(days=1).total_seconds()) + + elif scale == "month": + if datetime.date.today().day < 7: + expiry = int(datetime.timedelta(days=1).total_seconds()) + else: + expiry = int(datetime.timedelta(days=5).total_seconds()) + + pages = Configuration().webserver.get("pages_in_popular") + per_page = 50 + offset = positive_or_none(step_int(parse_int(query_params.pop("o", 0), 0), per_page)) + + if offset is None: + response = redirect(url_for("posts.popular_posts")) + response.headers["Cache-Control"] = f"max-age={int(datetime.timedelta(days=7).total_seconds())}" + + return response + + posts = get_popular_posts_for_date_range( + info.min_date, info.max_date, scale, offset // per_page, per_page, pages, expiry + ) + (previews, attachments, is_image) = get_render_data_for_posts(posts) + props = dict( + currentPage="popular_posts", + today=datetime.date.today(), + earliest_date_for_popular=Configuration().webserver.get("earliest_date_for_popular"), + limit=per_page, + count=pages * per_page, + ) + + response = make_response( + jsonify( + info=info, + props=props, + results=posts, + base=query_params, + result_previews=previews, + result_attachments=attachments, + result_is_image=is_image, + ), + 200, + ) + response.headers["Cache-Control"] = f"max-age={int(expiry)}" + + return response + + +@v1api_bp.get("/posts/tags") +def list_tags(): + props = dict(currentPage="tags") + response = make_response( + jsonify( + props=props, + tags=get_all_tags(), + ), + ) + response.headers["Cache-Control"] = "s-maxage=3600" + + return response + + +DOWNLOAD_URL_FANBOX_REGEX = re.compile(r"") + + +class TDPostProps(TypedDict): + flagged: str + revisions: list[TDPostRevision] + + +def ready_post_props_light(post: Post): + service = post["service"] + artist_id = post["user"] + post_id = post["id"] + + if service in ("patreon",): + + if post["file"] and post["attachments"] and post["file"] == post["attachments"][0]: + post["attachments"] = post["attachments"][1:] + + if service in ("fansly", "onlyfans"): + posts_incomplete_rewards = get_posts_incomplete_rewards(post_id, artist_id, service) + + if posts_incomplete_rewards: + post["incomplete_rewards"] = "This post is missing paid rewards from a higher tier or payment." + + if post["service"] == "onlyfans": + try: + rewards_info_text = ( + f"{posts_incomplete_rewards["incomplete_attachments_info"]["media_count"]} media, " + f"{posts_incomplete_rewards["incomplete_attachments_info"]["photo_count"]} photos, " + f"{posts_incomplete_rewards["incomplete_attachments_info"]["video_count"]} videos, " + f"for {posts_incomplete_rewards["incomplete_attachments_info"]["price"]}$." + ) + post["incomplete_rewards"] += "\n" + rewards_info_text + except Exception: + pass + + elif post["service"] == "fansly": + try: + rewards_info_text = ( + f"Downloaded:{posts_incomplete_rewards["incomplete_attachments_info"]["complete"]} " + f"Missing:{posts_incomplete_rewards["incomplete_attachments_info"]["incomplete"]}" + ) + post["incomplete_rewards"] += "\n" + rewards_info_text + except Exception: + pass + previews = [] + attachments = [] + videos = [] + + if "path" in post["file"]: + + if images_pattern.search(post["file"]["path"]): + previews.append( + { + "type": "thumbnail", + "server": get_fileserver_for_value(f"/data{post["file"]["path"]}"), + "name": post["file"].get("name"), + "path": post["file"]["path"], + } + ) + else: + file_extension = PurePath(post["file"]["path"]).suffix + name_extension = PurePath(post["file"].get("name") or "").suffix + # filename without extension + stem = PurePath(post["file"]["path"]).stem + attachments.append( + { + "server": get_fileserver_for_value(f"/data{post["file"]["path"]}"), + "name": post["file"].get("name"), + "extension": file_extension, + "name_extension": name_extension, + "stem": stem, + "path": post["file"]["path"], + } + ) + + if len(post.get("embed") or []): + previews.append( + { + "type": "embed", + "url": post["embed"]["url"], + "subject": post["embed"]["subject"], + "description": post["embed"]["description"], + } + ) + + for attachment in post["attachments"]: + + if images_pattern.search(attachment["path"]): + previews.append( + { + "type": "thumbnail", + "server": get_fileserver_for_value(f"/data{attachment["path"]}"), + "name": attachment["name"], + "path": attachment["path"], + } + ) + else: + file_extension = PurePath(attachment["path"]).suffix + name_extension = PurePath(attachment.get("name") or "").suffix + # filename without extension + stem = PurePath(attachment["path"]).stem + attachments.append( + { + "server": get_fileserver_for_value(f"/data{attachment["path"]}"), + "name": attachment.get("name"), + "extension": file_extension, + "name_extension": name_extension, + "stem": stem, + "path": attachment["path"], + } + ) + + for i, attachment in enumerate(attachments): + if attachment["extension"] in video_extensions: + videos.append( + { + "index": i, + "path": attachment["path"], + "name": attachment.get("name"), + "extension": attachment["extension"], + "name_extension": attachment["name_extension"], + "server": get_fileserver_for_value(f"/data{attachment["path"]}"), + } + ) + + if post.get("poll") is not None: + post["poll"]["total_votes"] = sum(choice["votes"] for choice in post["poll"]["choices"]) + post["poll"]["created_at"] = datetime.datetime.fromisoformat(post["poll"]["created_at"]) + if post["poll"]["closes_at"]: + post["poll"]["closes_at"] = datetime.datetime.fromisoformat(post["poll"]["closes_at"]) + + if (captions := post.get("captions")) is not None: + for file_hash, caption_data in captions.items(): + for preview_data in [preview for preview in previews if preview.get("path") == file_hash]: + if isinstance(caption_data, dict): + preview_data["caption"] = caption_data.get("text") or "" + elif isinstance(caption_data, list): + preview_data["caption"] = " ".join(each.get("text") or "" for each in caption_data) + for video in [video for video in videos if video["path"] == file_hash]: + if isinstance(caption_data, dict): + video["caption"] = caption_data.get("text") or "" + elif isinstance(caption_data, list): + video["caption"] = " ".join(each.get("text") or "" for each in caption_data) + + props = TDPostProps( + flagged=is_post_flagged(service, artist_id, post_id), + revisions=get_post_revisions(service, artist_id, post_id), + ) + real_post = post if not post.get("revision_id") else get_post(service, artist_id, post_id) + all_revisions = [real_post] + props["revisions"] + for set_prev_next_revision in (post, *props["revisions"]): + set_prev_next_revision["prev"] = real_post["prev"] + set_prev_next_revision["next"] = real_post["next"] + + if props["revisions"]: + last_date = real_post["added"] + for i, rev in enumerate(all_revisions[:-1]): + rev["added"] = all_revisions[i + 1]["added"] + props["revisions"][-1]["added"] = last_date + + if real_post["service"] == "fanbox": + top_rev_stripped = all_revisions[0].copy() + top_rev_stripped.pop("file") + top_rev_stripped.pop("added") + top_rev_stripped.pop("revision_id", None) + for fanbox_attachment in top_rev_stripped["attachments"]: + if 41 >= len(fanbox_attachment["name"]) >= 39: + fanbox_attachment.pop("name", None) + for duplicated_check_rev in all_revisions[1:]: + duplicated_check_rev_file_stripped = duplicated_check_rev.copy() + duplicated_check_rev_file_stripped.pop("file") + duplicated_check_rev_file_stripped.pop("added") + duplicated_check_rev_file_stripped.pop("revision_id", None) + for fanbox_attachment in duplicated_check_rev_file_stripped["attachments"]: + if 41 >= len(fanbox_attachment["name"]) >= 39: + fanbox_attachment.pop("name", None) + if duplicated_check_rev_file_stripped == top_rev_stripped: + all_revisions.remove(duplicated_check_rev) + else: + top_rev_stripped = duplicated_check_rev_file_stripped + + if isinstance(post["tags"], str): + post["tags"] = [tag.strip('"') for tag in post["tags"][1:-1].split(",")] + + transformed_revisions = list(reversed([ + (i, rev) + for i, rev + in enumerate(reversed(all_revisions)) + ])) + props["revisions"] = transformed_revisions + if post["service"] == "fanbox": + post["content"] = DOWNLOAD_URL_FANBOX_REGEX.sub("", post["content"]) + post["content"] = sanitize_html(post["content"], allow_iframe=post["service"] == "fanbox") + if post["service"] == "boosty": + post["content"] = patch_inline_img(post["content"]) + + return attachments, previews, videos, props diff --git a/src/pages/api/v2/__init__.py b/src/pages/api/v2/__init__.py new file mode 100644 index 0000000..a1f0e18 --- /dev/null +++ b/src/pages/api/v2/__init__.py @@ -0,0 +1,47 @@ +from flask import Blueprint, abort, request + +from src.lib.api import ( + create_api_v2_invalid_body_error_response, + create_api_v2_error_response, + TDAPIRequestBody, + APIV2_REQUEST_BODY_TYPE, + TDAPIError, +) + +from .account import account_bp + +v2api_bp = Blueprint("v2", __name__, url_prefix="/v2") + +methods_with_body = ("POST", "PUT", "PATH", "DELETE") + + +# validate body of request for methods which support bodies +@v2api_bp.before_request +def check_api_request(): + method = request.method + + if method not in methods_with_body: + return + + body: TDAPIRequestBody = request.get_json() + + if not (isinstance(body, dict)): + abort(create_api_v2_invalid_body_error_response()) + + type = body.get("type") + + if not type or type != APIV2_REQUEST_BODY_TYPE: + abort(create_api_v2_invalid_body_error_response()) + + +@v2api_bp.errorhandler(500) +def handle_server_error(error: Exception): + """ + TODO: proper logging + """ + responseError = TDAPIError(type="server_error", message="Unknown error.") + + return create_api_v2_error_response(responseError, 500) + + +v2api_bp.register_blueprint(account_bp) diff --git a/src/pages/api/v2/account/__init__.py b/src/pages/api/v2/account/__init__.py new file mode 100644 index 0000000..834ed81 --- /dev/null +++ b/src/pages/api/v2/account/__init__.py @@ -0,0 +1,33 @@ +from flask import Blueprint, abort + +from src.lib.account import ( + is_logged_in, + load_account, +) +from src.lib.api import create_api_v2_client_error_response, TDAPIError + +from .administrator import administrator_bp + +account_bp = Blueprint("account", __name__, url_prefix="/account") + + +# check credentials for all requests for this blueprint +# so the subsequent handlers wouldn't need to check it again +@account_bp.before_request +def check_auth(): + if not is_logged_in(): + response = create_api_v2_client_error_response( + TDAPIError(type="api_account_not_authenticated", message="Account not authenticated."), 401 + ) + abort(response) + + account = load_account() + + if not account: + response = create_api_v2_client_error_response( + TDAPIError(type="api_account_not_authenticated", message="Account not authenticated."), 401 + ) + abort(response) + + +account_bp.register_blueprint(administrator_bp) diff --git a/src/pages/api/v2/account/administrator/__init__.py b/src/pages/api/v2/account/administrator/__init__.py new file mode 100644 index 0000000..d4d727c --- /dev/null +++ b/src/pages/api/v2/account/administrator/__init__.py @@ -0,0 +1,23 @@ +from flask import Blueprint, g, abort + +from src.lib.api import create_api_v2_not_found_error_response +from src.types.account import Account + +from .accounts import get_accounts_count, get_account_list +from .account import get_target_account_overview, change_target_account_details + +administrator_bp = Blueprint("administrator", __name__, url_prefix="/administrator") + + +@administrator_bp.before_request +def check_credentials(): + account: Account = g.get("account") + + if account.role != "administrator": + return abort(create_api_v2_not_found_error_response()) + + +administrator_bp.get("/accounts")(get_accounts_count) +administrator_bp.get("/accounts/")(get_account_list) +administrator_bp.get("/account/")(get_target_account_overview) +administrator_bp.patch("/account/")(change_target_account_details) diff --git a/src/pages/api/v2/account/administrator/account.py b/src/pages/api/v2/account/administrator/account.py new file mode 100644 index 0000000..6a47a24 --- /dev/null +++ b/src/pages/api/v2/account/administrator/account.py @@ -0,0 +1,81 @@ +from flask import g, request +from typing import TypedDict + +from src.lib.api import ( + create_api_v2_response, + create_api_v2_client_error_response, + TDAPIError, + create_api_v2_not_found_error_response, + create_api_v2_invalid_body_error_response, + get_api_v2_request_data, +) +from src.lib.account import load_account +from src.lib.administrator import change_account_role +from src.types.account import Account, visible_roles, AccountRoleChange + + +def get_target_account_overview(account_id: str): + parsed_account_id = int(account_id) + + if parsed_account_id == 0: + return create_api_v2_client_error_response( + TDAPIError(type="invalid_id", message="Account ID must be positive.") + ) + + target_account = load_account(account_id) + + if not target_account: + return create_api_v2_not_found_error_response() + + response = create_api_v2_response(target_account) + + return response + + +class TDAccountUpdate(TypedDict): + role: str + + +def change_target_account_details(account_id: str): + parsed_account_id = int(account_id) + body: TDAccountUpdate = get_api_v2_request_data(request) + + if parsed_account_id == 0: + return create_api_v2_client_error_response( + TDAPIError(type="invalid_id", message="Account ID must be positive.") + ) + + is_valid_body = body is not None and isinstance(body, dict) and (body.get("role") in visible_roles) + + if not is_valid_body: + return create_api_v2_invalid_body_error_response() + + role = body["role"] + target_account = load_account(account_id) + + if not target_account: + return create_api_v2_not_found_error_response() + + if target_account.role == "administrator": + return create_api_v2_client_error_response( + TDAPIError(type="http_conflict", message="Cannot change the role of another admin."), 409 + ) + + if target_account.role == role: + return create_api_v2_client_error_response( + TDAPIError(type="http_conflict", message="Target role is the same as old one."), 409 + ) + + admin_account: Account = g.get("account") + + if admin_account.id == target_account.id: + return create_api_v2_client_error_response( + TDAPIError(type="http_conflict", message="Cannot change the role of yourself."), 409 + ) + + change_data = AccountRoleChange(old_role=target_account.role, new_role=role) + change_account_role([str(target_account.id)], change_data) + + response = create_api_v2_response(account_id) + + return response diff --git a/src/pages/api/v2/account/administrator/accounts.py b/src/pages/api/v2/account/administrator/accounts.py new file mode 100644 index 0000000..af3eef9 --- /dev/null +++ b/src/pages/api/v2/account/administrator/accounts.py @@ -0,0 +1,45 @@ +from flask import request + +from src.lib.pagination import create_pagination +from src.lib.api import create_api_v2_response, create_api_v2_client_error_response, TDAPIError +from src.lib.administrator import count_accounts, get_accounts + + +def get_accounts_count(): + name = request.args.get("name") + role = request.args.get("role") + + if name: + name = name.strip() + + count = count_accounts(role, name) + response = create_api_v2_response(count) + + return response + + +def get_account_list(page: str): + current_page = int(page) + name = request.args.get("name") + role = request.args.get("role") + + if name: + name = name.strip() + + if current_page < 1: + return create_api_v2_client_error_response( + TDAPIError(type="invalid_page", message="Page number must be positive.") + ) + + count = count_accounts(role, name) + pagination = create_pagination(count, current_page) + + if current_page > pagination["total_pages"]: + return create_api_v2_client_error_response( + TDAPIError(type="invalid_page", message="Page number must not be higher than total pages.") + ) + + accounts = get_accounts(pagination, role, name) + response = create_api_v2_response(accounts) + + return response diff --git a/src/pages/artists.py b/src/pages/artists.py index 8d65f4b..c696107 100644 --- a/src/pages/artists.py +++ b/src/pages/artists.py @@ -1,466 +1,11 @@ -from flask import Blueprint, Response, abort, flash, g, make_response, redirect, render_template, request, session, url_for - -from src.lib.announcements import get_artist_announcements -from src.lib.artist import ( - delete_creator_link, - get_artist, - get_artists_by_update_time, - get_fancards_by_artist, - get_top_artists_by_faves, - create_unapproved_link_request, - get_linked_creators, -) -from src.lib.dms import count_user_dms, get_artist_dms -from src.lib.filehaus import get_artist_share_count, get_artist_shares from src.lib.post import ( get_all_posts_by_artist, - get_artist_post_count, - get_artist_posts_summary, - get_fileserver_for_value, - get_render_data_for_posts, ) -from src.lib.posts import count_all_posts_for_tag, get_all_tags, get_tagged_posts from src.pages.artists_types import ( - ArtistAnnouncementsProps, ArtistDisplayData, - ArtistDMsProps, - ArtistFancardsProps, - ArtistPageProps, - ArtistShareProps, - LinkedAccountsProps, ) from src.types.paysites import Paysite, Paysites -from src.utils.utils import offset_list, parse_int, positive_or_none, sort_dict_list_by, step_int, take -from src.types.account.account import Account -from src.utils.decorators import require_login - -artists_bp = Blueprint("artists", __name__) - - -@artists_bp.route("/artists") -def list(): - base = dict() - limit = 50 - - results = get_top_artists_by_faves(0, limit) - props = dict( - currentPage="artists", - display="cached popular artists", - count=len(results), - limit=limit, - ) - - response = make_response(render_template("artists.html", props=props, results=results, base=base), 200) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.route("/artists/updated") -def updated(): - base = dict(commit=True, sort_by="updated") - limit = 50 - - results = get_artists_by_update_time(offset=0, limit=limit) - props = dict( - currentPage="artists", - display="cached updated artists", - count=len(results), - limit=limit, - ) - - response = make_response(render_template("artists.html", props=props, results=results, base=base), 200) - response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" - return response - - -@artists_bp.route("//user/") -def get(service: str, artist_id: str): - if service == "discord": - response = redirect(f"/discord/server/{artist_id}", 308) - response.headers["Cache-Control"] = "s-maxage=86400" - return response - - base = request.args.to_dict() - base.pop("o", None) - base["service"] = service - base["artist_id"] = artist_id - - artist = get_artist(service, artist_id) - if artist is None: - return redirect(url_for("artists.list")) - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get", service=service, artist_id=artist["id"])) - - query = request.args.get("q", default="").strip() - tags = sorted(request.args.getlist("tag")) - limit = 50 - offset = positive_or_none(step_int(parse_int(request.args.get("o"), 0), limit)) - if offset is None: - return redirect(url_for("artists.list")) - - if tags: - posts = get_tagged_posts(tags, offset, limit, service, artist_id) - total_count = count_all_posts_for_tag(tags, service, artist_id) - elif not query or len(query) < 2: - total_count = get_artist_post_count(service, artist_id) - if offset > total_count: - return redirect(url_for("artists.get", service=service, artist_id=artist_id)) - else: - posts = get_artist_posts_summary(artist_id, service, offset, limit, "published DESC NULLS LAST") - else: - (posts, total_count) = do_artist_post_search(artist_id, service, query, offset, limit) - - ( - result_previews, - result_attachments, - result_is_image, - ) = get_render_data_for_posts(posts) - - props = ArtistPageProps( - id=artist_id, - service=service, - session=session, - name=artist["name"], - count=total_count, - limit=limit, - artist=artist, - display_data=make_artist_display_data(artist), - dm_count=count_user_dms(service, artist_id), - share_count=get_artist_share_count(service, artist_id), - has_links="✔️" if artist["relation_id"] else "0", - ) - - response = make_response( - render_template( - "user.html", - props=props, - base=base, - results=posts, - result_previews=result_previews, - result_attachments=result_attachments, - result_is_image=result_is_image, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.get("//user//tags") -def get_tags(service: str, artist_id: str): - artist = get_artist(service, artist_id) - if not artist: - response = redirect(url_for("artists.list"), code=301) - response.headers["Cache-Control"] = "s-maxage=60" - return response - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get_tags", service=service, artist_id=artist["id"]), code=301) - - tags = get_all_tags(service, artist_id) - - response = make_response( - render_template( - "artist/tags.html", - props={ - "display_data": make_artist_display_data(artist), - "artist": artist, - "service": service, - "id": artist["id"], - "share_count": get_artist_share_count(service, artist_id), - "dm_count": count_user_dms(service, artist_id), - "has_links": "✔️" if artist["relation_id"] else "0", - }, - tags=tags, - service=service, - artist=artist, - ) - ) - response.headers["Cache-Control"] = "s-maxage=600" - return response - - -@artists_bp.route("/fanbox/user//fancards") -def get_fancards(artist_id: str): - service = "fanbox" - artist = get_artist(service, artist_id) - if not artist: - response = redirect(url_for("artists.list"), code=301) - response.headers["Cache-Control"] = "s-maxage=60" - return response - elif artist["public_id"] == artist_id: - return redirect(url_for("artists.get_fancards", artist_id=artist["id"]), code=301) - - fancards = get_fancards_by_artist(artist_id) - for fancard in fancards: - fhash = fancard["hash"] - ext = fancard["ext"] - fancard["path"] = f"/data/{fhash[0:2]}/{fhash[2:4]}/{fhash}{ext}" - fancard["server"] = get_fileserver_for_value(fancard["path"]) - - props = ArtistFancardsProps( - id=artist_id, - session=session, - artist=artist, - display_data=make_artist_display_data(artist), - fancards=fancards, - share_count=get_artist_share_count(artist_id=artist_id, service=service), - dm_count=count_user_dms(service, artist_id), - has_links="✔️" if artist["relation_id"] else "0", - ) - - response = make_response( - render_template( - "artist/fancards.html", - artist=artist, - fancards=fancards, - props=props, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.route("//user//shares") -def get_shares(service: str, artist_id: str): - base = request.args.to_dict() - base.pop("o", None) - base["service"] = service - base["artist_id"] = artist_id - - dm_count = count_user_dms(service, artist_id) - shares = get_artist_shares(artist_id, service) - - artist = get_artist(service, artist_id) - if artist is None: - return redirect(url_for("artists.list")) - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get_shares", service=service, artist_id=artist["id"]), code=301) - - props = ArtistShareProps( - display_data=make_artist_display_data(artist), - service=service, - session=session, - artist=artist, - id=artist_id, - dm_count=dm_count, - share_count=len(shares), - has_links="✔️" if artist["relation_id"] else "0", - ) - - response = make_response( - render_template("artist/shares.html", results=shares, props=props, base=base), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.route("//user//dms") -def get_dms(service: str, artist_id: str): - # pagination might be added at some point if we need it, but considering how few dms most artists end up having, we probably won't - # base = request.args.to_dict() - # base.pop('o', None) - # base["service"] = service - # base["artist_id"] = artist_id - - # offset = int(request.args.get('o') or 0) - # query = request.args.get('q') - # limit = limit_int(int(request.args.get('limit') or 25), 50) - - artist = get_artist(service, artist_id) - if artist is None: - return redirect(url_for("artists.list")) - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get_dms", service=service, artist_id=artist["id"]), code=301) - - dms = get_artist_dms(service, artist_id) - - props = ArtistDMsProps( - id=artist_id, - service=service, - session=session, - artist=artist, - display_data=make_artist_display_data(artist), - share_count=get_artist_share_count(service, artist_id), - dm_count=len(dms), - dms=dms, - has_links="✔️" if artist["relation_id"] else "0", - ) - - response = make_response( - render_template( - "artist/dms.html", - props=props, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.route("//user//announcements") -def get_announcements(service: str, artist_id: str) -> Response: - # offset = int(request.args.get("o") or 0) - query = request.args.get("q", "") - - artist = get_artist(service, artist_id) - if artist is None: - return redirect(url_for("artists.list")) - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get_announcements", service=service, artist_id=artist["id"]), code=301) - - announcements = get_artist_announcements(service, artist_id, query=query, reload=True) - # total_announcement_count = get_announcement_count(service=service, artist_id=artist_id, query=query, reload=True) - - props = ArtistAnnouncementsProps( - id=artist_id, - service=service, - artist=artist, - announcements=announcements, - # count=total_announcement_count, - share_count=get_artist_share_count(service, artist_id), - dm_count=count_user_dms(service, artist_id), - has_links="✔️" if artist["relation_id"] else "0", - session=session, - display_data=make_artist_display_data(artist), - ) - response: Response = make_response( - render_template( - "artist/announcements.html", - props=props, - base={"service": service, "artist_id": artist_id}, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.get("//user//links") -def get_linked_accounts(service: str, artist_id: str): - artist = get_artist(service, artist_id) - if not artist: - return redirect(url_for("artists.list")) - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get_linked_accounts", service=service, artist_id=artist["id"]), code=301) - links = get_linked_creators(service, artist_id) - - props = LinkedAccountsProps( - id=artist_id, - service=service, - artist=artist, - share_count=get_artist_share_count(service, artist_id), - dm_count=count_user_dms(service, artist_id), - has_links="✔️" if artist["relation_id"] else "0", - display_data=make_artist_display_data(artist), - ) - - response = make_response( - render_template( - "artist/linked_accounts.html", - props=props, - links=links, - base={"service": service, "artist_id": artist_id}, - ), - 200 - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@artists_bp.delete("//user//links") -@require_login -def delete_linked_account(service: str, creator_id: str, user: Account): - if user.role != "administrator": - abort(404) - else: - delete_creator_link(service, creator_id) - return "", 204 - - -@artists_bp.get("//user//links/new") -@require_login -def get_new_link_page(service: str, artist_id: str, user: Account): - artist = get_artist(service, artist_id) - if not artist: - return redirect(url_for("artists.list")) - elif artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("artists.get_new_link_page", service=service, artist_id=artist["id"]), code=301) - - props = LinkedAccountsProps( - id=artist_id, - service=service, - artist=artist, - share_count=get_artist_share_count(service, artist_id), - dm_count=count_user_dms(service, artist_id), - has_links="✔️" if artist["relation_id"] else "0", - display_data=make_artist_display_data(artist), - ) - - response = make_response( - render_template( - "artist/new_linked_account.html", - props=props, - base={"service": service, "artist_id": artist_id}, - ), - 200 - ) - response.headers["Cache-Control"] = "s-maxage=600" - return response - - -@artists_bp.post("//user//links/new") -@require_login -def post_new_link_page(service: str, artist_id: str, user: Account): - dest_service, dest_artist_id = request.form.get("creator", "/").split("/") - reason = request.form.get("reason", "") - - from_artist = get_artist(service, artist_id) - to_artist = get_artist(dest_service, dest_artist_id) - - if not from_artist: - return redirect(url_for("artists.list")) - elif from_artist["public_id"] == artist_id and from_artist["id"] != artist_id: - return redirect(url_for("artists.post_new_link_page", service=service, artist_id=from_artist["id"]), code=301) - - props = LinkedAccountsProps( - id=artist_id, - service=service, - artist=from_artist, - share_count=get_artist_share_count(service, artist_id), - dm_count=count_user_dms(service, artist_id), - has_links="✔️" if from_artist["relation_id"] else "0", - display_data=make_artist_display_data(from_artist), - ) - - tmpl = render_template( - "artist/new_linked_account.html", - props=props, - base={"service": service, "artist_id": artist_id}, - ) - if not to_artist: - flash(f"Invalid creator (svc: {dest_service}, id: {dest_artist_id})") - response = make_response(tmpl, 404) - return response - - if len(reason) > 140: - flash("Reason is too long") - return tmpl, 422 - - if dest_service == service and dest_artist_id == artist_id: - flash("Can't link an artist to themself") - response = make_response(tmpl, 422) - return response - - if from_artist["relation_id"] == to_artist["relation_id"] and from_artist["relation_id"] is not None: - flash("Already linked") - response = make_response(tmpl, 422) - return response - - create_unapproved_link_request(from_artist, to_artist, user.id, reason) - flash("Request created. It will be shown here when approved.") - return redirect(url_for("artists.get_linked_accounts", service=service, artist_id=artist_id)) +from src.utils.utils import offset_list, sort_dict_list_by, take def do_artist_post_search(artist_id, service, search, o, limit): diff --git a/src/pages/artists_types.py b/src/pages/artists_types.py index 9cea111..709becf 100644 --- a/src/pages/artists_types.py +++ b/src/pages/artists_types.py @@ -1,8 +1,6 @@ from dataclasses import dataclass from typing import Any, Dict, List -from flask.sessions import SessionMixin - from src.internals.internal_types import PageProps from src.types.kemono import Approved_DM @@ -18,7 +16,6 @@ class ArtistPageProps(PageProps): currentPage = "posts" id: str service: str - session: SessionMixin name: str count: int limit: int @@ -34,7 +31,6 @@ class ArtistShareProps(PageProps): currentPage = "shares" id: str service: str - session: SessionMixin artist: Dict display_data: ArtistDisplayData dm_count: int @@ -47,7 +43,6 @@ class ArtistDMsProps(PageProps): currentPage = "dms" id: str service: str - session: SessionMixin artist: Dict display_data: ArtistDisplayData dm_count: int @@ -59,7 +54,6 @@ class ArtistDMsProps(PageProps): @dataclass class ArtistFancardsProps(PageProps): id: str - session: SessionMixin artist: Dict display_data: ArtistDisplayData fancards: List[Any] # todo remove any @@ -80,7 +74,6 @@ class ArtistAnnouncementsProps(PageProps): share_count: int dm_count: int has_links: str - session: SessionMixin display_data: ArtistDisplayData currentPage: str = "announcements" limit: int = 50 diff --git a/src/pages/creator_link_requests.py b/src/pages/creator_link_requests.py deleted file mode 100644 index 3ed6533..0000000 --- a/src/pages/creator_link_requests.py +++ /dev/null @@ -1,26 +0,0 @@ -from flask import Blueprint, abort, jsonify - -from src.lib.artist import approve_unapproved_link_request, reject_unapproved_link_request -from src.utils.decorators import require_login -from src.types.account.account import Account - - -bp = Blueprint("creator_link_requests", __name__) - - -@bp.post("/creator_link_requests//approve") -@require_login -def approve_request(request_id: int, user: Account): - if user.role not in ["moderator", "administrator"]: - return abort(404) - approve_unapproved_link_request(request_id) - return jsonify({"response": "approved"}) - - -@bp.post("/creator_link_requests//reject") -@require_login -def reject_request(request_id: int, user: Account): - if user.role not in ["moderator", "administrator"]: - return abort(404) - reject_unapproved_link_request(request_id) - return jsonify({"response": "rejected"}) diff --git a/src/pages/dms.py b/src/pages/dms.py deleted file mode 100644 index 13fcb04..0000000 --- a/src/pages/dms.py +++ /dev/null @@ -1,58 +0,0 @@ -from dataclasses import dataclass -from typing import Dict, List - -from flask import Blueprint, make_response, redirect, render_template, request, url_for - -from src.config import Configuration -from src.internals.internal_types import PageProps -from src.lib.artist import get_artist -from src.lib.dms import get_all_dms, get_all_dms_by_query, get_all_dms_by_query_count, get_all_dms_count -from src.types.kemono import Approved_DM -from src.utils.utils import get_query_parameters_dict, parse_int, positive_or_none, step_int - - -@dataclass -class DMsProps(PageProps): - currentPage = "artists" - count: int - limit: int - dms: List[Approved_DM] - - -dms_bp = Blueprint("dms", __name__) - - -@dms_bp.route("/dms") -def get_dms(): - base = get_query_parameters_dict(request, on_errors="ignore", clean_query_string=True) - - limit = 50 - max_offset = limit * 1000 # only load 1000 pages of any result - offset = positive_or_none(step_int(parse_int(base.pop("o", 0), 0), limit)) - if offset is None or offset > max_offset: - return redirect(url_for("dms.get_dms")) - query = base.get("q", "").strip()[: Configuration().webserver["max_full_text_search_input_len"]] - - if not query or len(query) < 3: - total_count = get_all_dms_count() - if offset > total_count: - return redirect(url_for("dms.get_dms")) - dms = get_all_dms(offset, limit) - else: - total_count = get_all_dms_by_query_count(query) - if offset > total_count: - return redirect(url_for("dms.get_dms")) - dms = get_all_dms_by_query(query, offset, limit) - - props = DMsProps(count=total_count, limit=limit, dms=dms) - - response = make_response( - render_template( - "all_dms.html", - props=props, - base=base, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response diff --git a/src/pages/favorites.py b/src/pages/favorites.py deleted file mode 100644 index ab41025..0000000 --- a/src/pages/favorites.py +++ /dev/null @@ -1,60 +0,0 @@ -from flask import Blueprint, make_response, redirect, render_template, request, url_for - -from src.lib.favorites import get_favorite_artists, get_favorite_posts -from src.types.account.account import Account -from src.utils.decorators import require_login -from src.utils.utils import offset_list, parse_int, positive_or_none, restrict_value, sort_dict_list_by, step_int, take - -favorites_bp = Blueprint("favorites", __name__) - - -@favorites_bp.route("/favorites", methods=["GET"]) -@require_login -def list_favorites(user: Account): - props = {"currentPage": "favorites"} - base = request.args.to_dict() - base.pop("o", None) - - fave_type = request.args.get("type", "artist") - if fave_type == "post": - favorites = get_favorite_posts(user.id) - sort_field = restrict_value(request.args.get("sort"), ["faved_seq", "published"], "faved_seq") - else: - fave_type = "artist" - favorites = get_favorite_artists(user.id) - sort_field = restrict_value( - request.args.get("sort"), - ["faved_seq", "updated", "last_imported"], - "updated", - ) - - limit = 50 - offset = positive_or_none(step_int(parse_int(request.args.get("o"), 0), limit)) - if offset is None: - return redirect(url_for("favorites.list_favorites")) - sort_asc = request.args.get("order") == "asc" - results = sort_and_filter_favorites(favorites, offset, sort_field, sort_asc) - - props["fave_type"] = fave_type - props["sort_field"] = sort_field - props["sort_asc"] = sort_asc - props["count"] = len(favorites) - props["limit"] = limit - - response = make_response( - render_template( - "favorites.html", - props=props, - base=base, - source="account", - results=results, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -def sort_and_filter_favorites(favorites, o, field, asc): - favorites = sort_dict_list_by(favorites, field, not asc) - return take(50, offset_list(o, favorites)) diff --git a/src/pages/filehaus.py b/src/pages/filehaus.py deleted file mode 100644 index cf715b3..0000000 --- a/src/pages/filehaus.py +++ /dev/null @@ -1,79 +0,0 @@ -from flask import Blueprint, flash, make_response, redirect, render_template, request, url_for, g - -from src.config import Configuration -from src.lib.filehaus import get_all_shares_count, get_files_for_share, get_share, get_shares -from src.utils.utils import parse_int, positive_or_none, step_int - -filehaus_bp = Blueprint("filehaus", __name__) - - -@filehaus_bp.route("/share/") -def get_share_handler(share_id: str): - base = request.args.to_dict() - base.pop("o", None) - - props = dict(currentPage="shares") - share = get_share(int(share_id)) if share_id.isdigit() else None - if share is None: - response = redirect(url_for("filehaus.get_shares_page")) - return response - - share_files = get_files_for_share(share["id"]) - - response = make_response( - render_template("share.html", share_files=share_files, share=share, props=props, base=base), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@filehaus_bp.route("/shares") -def get_shares_page(): - base = request.args.to_dict() - base.pop("o", None) - - limit = 50 - offset = positive_or_none(step_int(parse_int(request.args.get("o"), 0), limit)) - # query = request.args.get('q') - - shares = None - total_count = None - (shares, total_count) = get_share_page(offset, limit) - - props = dict(currentPage="shares", count=total_count, shares=shares, limit=limit) - - response = make_response( - render_template( - "shares.html", - props=props, - base=base, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -def get_share_page(offset: int, limit: int): - posts = get_shares(offset, limit) - total_count = get_all_shares_count() - return posts, total_count - - -@filehaus_bp.route("/posts/upload") -def upload_post(): - account = g.get("account") - if Configuration().filehaus["requires_account"] and account is None: - flash("Filehaus uploading requires an account.") - return redirect(url_for("account.get_login")) - required_roles = Configuration().filehaus["required_roles"] - if len(required_roles) and account.role not in required_roles: - flash( - "Filehaus uploading requires elevated permissions. " "Please contact the administrator to change your role." - ) - return redirect(url_for("account.get_account")) - props = {"currentPage": "posts"} - response = make_response(render_template("upload.html", props=props), 200) - response.headers["Cache-Control"] = "s-maxage=60" - return response diff --git a/src/pages/files.py b/src/pages/files.py deleted file mode 100644 index af3f793..0000000 --- a/src/pages/files.py +++ /dev/null @@ -1,43 +0,0 @@ -from flask import Blueprint, make_response, redirect, render_template, request, url_for - -from src.internals.database.database import get_cursor -from src.lib.files import get_file_relationships - -files_bp = Blueprint("files", __name__) - - -@files_bp.route("/search_hash", methods=["GET", "POST"]) -def search_hash(): - file_hash = request.args.get("hash") - if file_hash: - if not (len(file_hash) == 64 and all(c in "0123456789abcdefABCDEF" for c in file_hash)): - return redirect(url_for("files.search_hash")) - file_data: dict | None = get_file_relationships(file_hash) - for discord_post in (file_data["discord_posts"] or []) if file_data else []: - cursor = get_cursor() - cursor.execute( - "SELECT * FROM discord_channels WHERE channel_id = %s", - (discord_post["channel"],), - ) - lookup_result = cursor.fetchall() - discord_post["channel_name"] = lookup_result[0]["name"] if len(lookup_result) else "" - response = make_response( - render_template( - "search_results.html", - hash=file_hash, - file_data=file_data, - props={"currentPage": "search_hash"}, - ) - ) - response.headers["Cache-Control"] = "s-maxage=60" - else: - response = make_response( - render_template( - "search_hash.html", - props={"currentPage": "search_hash"}, - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - - return response diff --git a/src/pages/help.py b/src/pages/help.py deleted file mode 100644 index 9191e2f..0000000 --- a/src/pages/help.py +++ /dev/null @@ -1,16 +0,0 @@ -from flask import Blueprint, make_response, redirect, render_template, url_for - -help_app_bp = Blueprint("help_app", __name__) - - -@help_app_bp.route("/") -def help(): - return redirect(url_for("help_app.faq"), 302) - - -@help_app_bp.get("/faq") -def faq(): - props = dict(currentPage="help") - response = make_response(render_template("help/faq.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" - return response diff --git a/src/pages/home.py b/src/pages/home.py deleted file mode 100644 index 3140220..0000000 --- a/src/pages/home.py +++ /dev/null @@ -1,13 +0,0 @@ -from flask import Blueprint, make_response, render_template, request - -home_bp = Blueprint("pages", __name__) - - -@home_bp.get("/") -def get_home(): - props = {} - base = request.args.to_dict() - base.pop("o", None) - response = make_response(render_template("home.html", props=props, base=base), 200) - response.headers["Cache-Control"] = "s-maxage=60" - return response diff --git a/src/pages/imports/__init__.py b/src/pages/imports/__init__.py deleted file mode 100644 index c02a770..0000000 --- a/src/pages/imports/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .blueprint import importer_page_bp diff --git a/src/pages/imports/blueprint.py b/src/pages/imports/blueprint.py deleted file mode 100644 index b5df5d8..0000000 --- a/src/pages/imports/blueprint.py +++ /dev/null @@ -1,55 +0,0 @@ -from flask import Blueprint, make_response, redirect, render_template, request, session, url_for - -from src.lib.dms import approve_dms, cleanup_unapproved_dms, get_unapproved_dms -from src.types.props import SuccessProps - -from .types import DMPageProps, ImportProps, StatusPageProps - -importer_page_bp = Blueprint("importer_page", __name__) - - -@importer_page_bp.get("/importer") -def importer(): - props = ImportProps() - - response = make_response(render_template("importer_list.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" - return response - - -@importer_page_bp.get("/importer/tutorial") -def importer_tutorial(): - props = ImportProps() - - response = make_response(render_template("importer_tutorial.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" - return response - - -@importer_page_bp.get("/importer/tutorial_fanbox") -def importer_tutorial_fanbox(): - props = ImportProps() - - response = make_response(render_template("importer_tutorial_fanbox.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" - return response - - -@importer_page_bp.get("/importer/ok") -def importer_ok(): - props = ImportProps() - - response = make_response(render_template("importer_ok.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=60, public, stale-while-revalidate=2592000" - return response - - -@importer_page_bp.get("/importer/status/") -def importer_status(import_id): - is_dms = bool(request.args.get("dms")) - - props = StatusPageProps(import_id=import_id, is_dms=is_dms) - response = make_response(render_template("importer_status.html", props=props), 200) - - response.headers["Cache-Control"] = "max-age=0, private, must-revalidate" - return response diff --git a/src/pages/imports/types.py b/src/pages/imports/types.py deleted file mode 100644 index d9bbdd3..0000000 --- a/src/pages/imports/types.py +++ /dev/null @@ -1,23 +0,0 @@ -from dataclasses import dataclass -from typing import List - -from src.internals.internal_types import PageProps -from src.types.kemono import Unapproved_DM - - -@dataclass -class ImportProps(PageProps): - currentPage = "import" - - -@dataclass -class StatusPageProps(ImportProps): - import_id: str - is_dms: bool - - -@dataclass -class DMPageProps(ImportProps): - account_id: int - dms: List[Unapproved_DM] - status: str diff --git a/src/pages/post.py b/src/pages/post.py index 2258cba..da957a4 100644 --- a/src/pages/post.py +++ b/src/pages/post.py @@ -1,91 +1,42 @@ import datetime -import json import re from pathlib import PurePath -import dateutil.parser -from flask import Blueprint, make_response, redirect, render_template, url_for - from src.config import Configuration from src.lib.artist import get_artist from src.lib.post import ( get_fileserver_for_value, get_post, - get_post_by_id, get_post_comments, get_post_revisions, get_posts_incomplete_rewards, is_post_flagged, + patch_inline_img, ) +from src.lib.posts import Post from src.utils.utils import images_pattern, sanitize_html -post_bp = Blueprint("post", __name__) +from flask import session + video_extensions = Configuration().webserver["ui"]["video_extensions"] -@post_bp.route("//post/") -def get_by_id(service, post_id): - post = get_post_by_id(post_id, service) - - if post: - response = redirect( - url_for( - "post.get", - service=post["service"], - artist_id=post["user"], - post_id=post["id"], - ) - ) - response.headers["Cache-Control"] = "s-maxage=86400" - else: - response = redirect(url_for("artists.list"), code=301) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -@post_bp.route("//user//post/") -def get(service, artist_id, post_id): - artist = get_artist(service, artist_id) - if artist and artist["public_id"] == artist_id and artist["id"] != artist_id: - return redirect(url_for("post.get", service=service, artist_id=artist["id"], post_id=post_id), code=301) - - post: dict = get_post(service, artist_id, post_id) - if not post: - response = redirect(url_for("artists.get", service=service, artist_id=artist_id)) - return response - - attachments, comments, previews, videos, props = ready_post_props(post) - props["currentPage"] = "posts" - - response = make_response( - render_template( - "post.html", - props=props, - post=post, - comments=comments, - result_previews=previews, - result_attachments=attachments, - videos=videos, - archives_enabled=Configuration().archive_server["enabled"], - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=60" - return response - - -def ready_post_props(post): +def ready_post_props(post: Post): service = post["service"] artist_id = post["user"] post_id = post["id"] if service in ("patreon",): + if post["file"] and post["attachments"] and post["file"] == post["attachments"][0]: post["attachments"] = post["attachments"][1:] + if service in ("fansly", "onlyfans"): posts_incomplete_rewards = get_posts_incomplete_rewards(post_id, artist_id, service) + if posts_incomplete_rewards: post["incomplete_rewards"] = "This post is missing paid rewards from a higher tier or payment." + if post["service"] == "onlyfans": try: rewards_info_text = ( @@ -97,6 +48,7 @@ def ready_post_props(post): post["incomplete_rewards"] += "\n" + rewards_info_text except Exception: pass + elif post["service"] == "fansly": try: rewards_info_text = ( @@ -109,7 +61,9 @@ def ready_post_props(post): previews = [] attachments = [] videos = [] + if "path" in post["file"]: + if images_pattern.search(post["file"]["path"]): previews.append( { @@ -134,6 +88,7 @@ def ready_post_props(post): "path": post["file"]["path"], } ) + if len(post.get("embed") or []): previews.append( { @@ -143,7 +98,9 @@ def ready_post_props(post): "description": post["embed"]["description"], } ) + for attachment in post["attachments"]: + if images_pattern.search(attachment["path"]): previews.append( { @@ -168,6 +125,7 @@ def ready_post_props(post): "path": attachment["path"], } ) + for i, attachment in enumerate(attachments): if attachment["extension"] in video_extensions: videos.append( @@ -247,6 +205,8 @@ def ready_post_props(post): if post["service"] == "fanbox": post["content"] = DOWNLOAD_URL_FANBOX_REGEX.sub("", post["content"]) post["content"] = sanitize_html(post["content"], allow_iframe=post["service"] == "fanbox") + if post["service"] == "boosty": + post["content"] = patch_inline_img(post["content"]) return attachments, comments, previews, videos, props diff --git a/src/pages/posts.py b/src/pages/posts.py deleted file mode 100644 index c98dc1c..0000000 --- a/src/pages/posts.py +++ /dev/null @@ -1,228 +0,0 @@ -import datetime -import logging -from typing import cast, get_args - -from flask import Blueprint, Response, make_response, redirect, render_template, request, url_for -from psycopg.errors import QueryCanceled -from src.lib.files import get_archive_files - -from src.config import Configuration -from src.lib.post import get_render_data_for_posts -from src.lib.posts import ( - count_all_posts, - count_all_posts_for_query, - count_all_posts_for_tag, - get_all_posts_for_query, - get_all_posts_summary, - get_all_tags, - get_popular_posts_for_date_range, - get_tagged_posts, -) -from src.utils.datetime_ import PeriodScale, parse_scale_string -from src.utils.utils import ( - get_query_parameters_dict, - limit_int, - parse_int, - parse_offset, - positive_or_none, - set_query_parameter, - step_int, -) - -posts_bp = Blueprint("posts", __name__) - - -@posts_bp.route("/posts") -def get_posts(): - props = { - "currentPage": "posts", - "limit": 50, - } - - query_params = get_query_parameters_dict(request, on_errors="ignore", clean_query_string=True) - - extra_pages = Configuration().webserver["extra_pages_to_load_on_posts"] - max_offset = props["limit"] * 1000 # only load 1000 pages of any result - query = query_params.get("q", "").strip()[: Configuration().webserver["max_full_text_search_input_len"]] - tags = request.args.getlist("tag") - o = query_params.pop("o", 0) - offset = positive_or_none(step_int(parse_int(o, 0), props["limit"])) - if offset is None or offset > max_offset: - return redirect(url_for("posts.get_posts")) - extra_offset = positive_or_none(step_int(parse_int(o, 0), props["limit"] * extra_pages)) - slice_offset = offset - extra_offset - # todo this true_count and count have no real meaning seeing it is displayed as {{props.true_count or props.count }} - if tags: - extra_results = get_tagged_posts(tags, extra_offset, props["limit"] * extra_pages) - total_count = count_all_posts_for_tag(tags) - props["true_count"] = total_count - props["count"] = limit_int(total_count, max_offset) - elif not query or len(query) < 2: - extra_results = get_all_posts_summary(extra_offset, props["limit"] * extra_pages, cache_ttl=Configuration().cache_ttl_for_recent_posts)[ - slice_offset : props["limit"] + slice_offset - ] - props["true_count"] = count_all_posts() - props["count"] = limit_int(count_all_posts(), max_offset) - else: - try: - extra_results = get_all_posts_for_query(query, extra_offset, props["limit"] * extra_pages) - except QueryCanceled: - return make_response("Query Timeout. Please fix your query text or try again later.", 408) - except Exception as error: - if "failed to parse expression" not in str(error): - raise - query = "Failed to parse query." - query_params["q"] = query - extra_results = get_all_posts_for_query(query, extra_offset, props["limit"] * extra_pages) - - if not offset and len(extra_results) < props["limit"]: - props["true_count"] = 0 - props["count"] = len(extra_results) - else: - try: - props["true_count"] = count_all_posts_for_query(query) - props["count"] = limit_int(props["true_count"], max_offset) - except Exception as count_error: # catch timeouts, set count as max offset - logging.exception( - "Caught error in count_all_posts_for_query", - extra={"e": count_error}, - ) - props["true_count"] = 0 - props["count"] = max_offset - - results = extra_results[slice_offset : props["limit"] + slice_offset] - - ( - result_previews, - result_attachments, - result_is_image, - ) = get_render_data_for_posts(results) - - response = make_response( - render_template( - "posts.html", - props=props, - results=results, - base=query_params, - result_previews=result_previews, - result_attachments=result_attachments, - result_is_image=result_is_image, - ), - 200, - ) - # response.headers["Cache-Control"] = "no-store, max-age=0" - return response - - -@posts_bp.route("/posts/popular") -def popular_posts() -> Response: - query_params = get_query_parameters_dict(request) - earliest_date_for_popular = Configuration().webserver.get("earliest_date_for_popular") - # checked below but doesn't typecheck without a cast - scale: PeriodScale = cast(PeriodScale, query_params.get("period", "recent")) - - if scale not in get_args(PeriodScale): - scale = "recent" - - info, valid_date = parse_scale_string(query_params.get("date"), scale) - does_not_match_step_date = scale != "recent" and info.date.date() != info.navigation_dates[scale][2] - if ( - not valid_date - or does_not_match_step_date - or info.date.date() > datetime.date.today() - or info.date.date() < earliest_date_for_popular - ): - correct_date = info.navigation_dates[scale][2].isoformat() - if info.date.date() > datetime.date.today(): - correct_date = datetime.date.today() - scale = "day" - elif info.date.date() < earliest_date_for_popular: - correct_date = earliest_date_for_popular - scale = "day" - new_url = set_query_parameter(url_for("posts.popular_posts"), {"date": correct_date, "period": scale}) - response = redirect(new_url) - cache_seconds = int(datetime.timedelta(days=7).total_seconds()) - if info.date.date() > datetime.date.today(): - cache_seconds = int(datetime.timedelta(hours=3).total_seconds()) - response.headers["Cache-Control"] = f"max-age={cache_seconds}" - return response - expiry = int(datetime.timedelta(days=30).total_seconds()) - if scale == "recent": - expiry = int(datetime.timedelta(minutes=30 + 1).total_seconds()) - elif info.max_date > datetime.datetime.utcnow(): - if scale == "day": - expiry = int(datetime.timedelta(hours=3).total_seconds()) - elif scale == "week": - expiry = int(datetime.timedelta(days=1).total_seconds()) - elif scale == "month": - if datetime.date.today().day < 7: - expiry = int(datetime.timedelta(days=1).total_seconds()) - else: - expiry = int(datetime.timedelta(days=5).total_seconds()) - - pages = Configuration().webserver.get("pages_in_popular") - per_page = 50 - offset = positive_or_none(step_int(parse_int(query_params.pop("o", 0), 0), per_page)) - if offset is None: - response = redirect(url_for("posts.popular_posts")) - response.headers["Cache-Control"] = f"max-age={int(datetime.timedelta(days=7).total_seconds())}" - return response - posts = get_popular_posts_for_date_range( - info.min_date, info.max_date, scale, offset // per_page, per_page, pages, expiry - ) - (previews, attachments, is_image) = get_render_data_for_posts(posts) - - response = make_response( - render_template( - "posts/popular.html", - info=info, - props={ - "currentPage": "popular_posts", - "today": datetime.date.today(), - "earliest_date_for_popular": Configuration().webserver.get("earliest_date_for_popular"), - "limit": per_page, - "count": pages * per_page, - }, - results=posts, - base=query_params, - result_previews=previews, - result_attachments=attachments, - result_is_image=is_image, - ), - 200, - ) - response.headers["Cache-Control"] = f"max-age={int(expiry)}" - return response - - -@posts_bp.get("/posts/tags") -def list_tags(): - response = make_response( - render_template( - "tags.html", - props={"currentPage": "tags"}, - tags=get_all_tags(), - ), - ) - response.headers["Cache-Control"] = "s-maxage=3600" - return response - - -@posts_bp.route("/discord/server/") -def discord_server(server_id): - response = make_response(render_template("discord.html"), 200) - response.headers["Cache-Control"] = "s-maxage=600" - return response - - -@posts_bp.route("/posts/archives/") -def list_archive(file_hash: str): - archive = get_archive_files(file_hash) - response = make_response(render_template( - "posts/archive.html", - props={}, - archive=archive, - file_serving_enabled=Configuration().archive_server["file_serving_enabled"], - ), 200) - response.headers["Cache-Control"] = "s-maxage=600" - return response diff --git a/src/pages/random_.py b/src/pages/random_.py deleted file mode 100644 index 3c74a69..0000000 --- a/src/pages/random_.py +++ /dev/null @@ -1,45 +0,0 @@ -import random - -from flask import Blueprint, redirect, url_for - -from src.config import Configuration -from src.lib.artist import get_random_artist_keys -from src.lib.post import get_random_post_key - -random_bp = Blueprint("random", __name__) - - -@random_bp.route("/posts/random") -def random_post(): - post = get_random_post_key(Configuration().webserver.get("table_sample_bernoulli_sample_size")) - if post is None: - return redirect(url_for("posts.get_posts")) - - return redirect( - url_for( - "post.get", - service=post["service"], - artist_id=post["user"], - post_id=post["id"], - ) - ) - - -@random_bp.route("/artists/random") -def random_artist(): - """todo decide after random posts with redis list if its worth""" - artist = get_random_artist() - if artist is None: - return redirect(url_for("artists.list")) - - # currently we don't get random discord artists but anyway... - if artist["service"] == "discord": - return redirect(url_for("posts.discord_server", server_id=artist["id"])) - return redirect(url_for("artists.get", service=artist["service"], artist_id=artist["id"])) - - -def get_random_artist(): - artists = get_random_artist_keys(1000) - if len(artists) == 0: - return None - return random.choice(artists) diff --git a/src/pages/review_dms.py b/src/pages/review_dms.py deleted file mode 100644 index 98571a6..0000000 --- a/src/pages/review_dms.py +++ /dev/null @@ -1,47 +0,0 @@ -from flask import Blueprint, make_response, redirect, render_template, request, url_for - -from src.lib.dms import approve_dms, cleanup_unapproved_dms, get_unapproved_dms, clean_dms_already_approved -from src.pages.imports.types import DMPageProps -from src.types.account.account import Account -from src.types.props import SuccessProps -from src.utils.decorators import require_login - -review_dms_bp = Blueprint("review_dms", __name__) - - -@review_dms_bp.get("/account/review_dms") -@require_login -def importer_dms(user: Account): - account_id_int = int(user.id) - status = "ignored" if request.args.get("status") == "ignored" else "pending" - dms = get_unapproved_dms(account_id_int, request.args.get("status") == "ignored") - - props = DMPageProps(account_id=account_id_int, dms=dms, status=status) - - response = make_response( - render_template( - "review_dms/review_dms.html", - props=props, - ), - 200, - ) - - response.headers["Cache-Control"] = "max-age=0, private, must-revalidate" - return response - - -@review_dms_bp.post("/account/review_dms") -@require_login -def approve_importer_dms(user: Account): - props = SuccessProps(currentPage="import", redirect="/account/review_dms") - approved_hashes = request.form.getlist("approved_hashes") - delete_ignored = bool(request.form.get("delete_ignored", default=False)) - approve_dms(int(user.id), approved_hashes) - clean_dms_already_approved(int(user.id)) - cleanup_unapproved_dms(int(user.id)) - if delete_ignored: - cleanup_unapproved_dms(int(user.id), delete=True) - - response = make_response(render_template("success.html", props=props), 200) - response.headers["Cache-Control"] = "max-age=0, private, must-revalidate" - return response diff --git a/src/pages/revisions.py b/src/pages/revisions.py deleted file mode 100644 index 37c3c31..0000000 --- a/src/pages/revisions.py +++ /dev/null @@ -1,37 +0,0 @@ -from flask import Blueprint, make_response, redirect, render_template, url_for - -from src.config import Configuration -from src.lib.post import get_post_revisions -from src.pages.post import ready_post_props - -revisions_bp = Blueprint("revisions", __name__) - - -@revisions_bp.route("//user//post//revision/") -def get(service: str, artist_id: str, post_id: str, revision_id: str): - revisions = get_post_revisions(service, artist_id, post_id) if revision_id.isdigit() else [] - revision = next((rev for rev in revisions if rev["revision_id"] == int(revision_id)), None) - if not revision or not ( - service == revision["service"] and artist_id == revision["user"] and post_id == revision["id"] - ): - response = redirect(url_for("post.get", service=service, artist_id=artist_id, post_id=get)) - return response - - attachments, comments, previews, videos, props = ready_post_props(revision) - props["currentPage"] = "revisions" - - response = make_response( - render_template( - "post.html", - props=props, - post=revision, - comments=comments, - result_previews=previews, - result_attachments=attachments, - videos=videos, - archives_enabled=Configuration().archive_server["enabled"], - ), - 200, - ) - response.headers["Cache-Control"] = "s-maxage=600" - return response diff --git a/src/server.py b/src/server.py index 5e43bf1..27a07bf 100644 --- a/src/server.py +++ b/src/server.py @@ -3,10 +3,11 @@ import os import pathlib import jinja2 -from flask import Flask, g, make_response, render_template, request, send_from_directory, session +from flask import Flask, g, make_response, render_template, request, send_from_directory, session, jsonify from flask.json.provider import JSONProvider from src.config import Configuration +from src.lib.api import create_api_v2_client_error_response, TDAPIError app = Flask( __name__, @@ -39,22 +40,7 @@ import orjson from src.lib.account import is_logged_in, load_account from src.lib.notification import count_new_notifications from src.lib.post import get_fileserver_for_value -from src.pages.account import account_bp from src.pages.api import api_bp -from src.pages.artists import artists_bp -from src.pages.dms import dms_bp -from src.pages.favorites import favorites_bp -from src.pages.filehaus import filehaus_bp -from src.pages.files import files_bp -from src.pages.help import help_app_bp -from src.pages.home import home_bp -from src.pages.imports import importer_page_bp -from src.pages.post import post_bp -from src.pages.posts import posts_bp -from src.pages.random_ import random_bp -from src.pages.revisions import revisions_bp -from src.pages.review_dms import review_dms_bp -from src.pages.creator_link_requests import bp as link_request_bp from src.types.account import Account from src.utils.utils import ( freesites, @@ -68,21 +54,6 @@ from src.utils.utils import ( app.url_map.strict_slashes = False app.register_blueprint(api_bp) -app.register_blueprint(home_bp) -app.register_blueprint(artists_bp) -app.register_blueprint(random_bp) -app.register_blueprint(post_bp) -app.register_blueprint(posts_bp) -app.register_blueprint(revisions_bp) -app.register_blueprint(account_bp) -app.register_blueprint(favorites_bp) -app.register_blueprint(filehaus_bp) -app.register_blueprint(files_bp) -app.register_blueprint(importer_page_bp) -app.register_blueprint(dms_bp) -app.register_blueprint(review_dms_bp) -app.register_blueprint(help_app_bp, url_prefix="/help") -app.register_blueprint(link_request_bp) app.config.update( @@ -279,6 +250,31 @@ def do_finish_stuff(response): return response +# adding the handlers there because +# flask doesn't allow handling 404/405 in blueprints +# https://flask.palletsprojects.com/en/2.3.x/errorhandling/#blueprint-error-handlers +@app.errorhandler(404) +def route_not_found(error): + if request.path.startswith('/api/v1'): + return jsonify(error="Not Found"), 404 + + if request.path.startswith('/api/v2'): + error = TDAPIError(type="http_error", message="Not Found") + return create_api_v2_client_error_response(error, 404) + + return error, 404 + +@app.errorhandler(405) +def method_not_allowed(error): + if request.path.startswith('/api/v1'): + return jsonify(error="Method Not Allowed"), 405 + + if request.path.startswith('/api/v2'): + error = TDAPIError(type="http_error", message="Method Not Allowed") + return create_api_v2_client_error_response(error, 405) + + return error, 405 + @app.errorhandler(413) def upload_exceeded(error): props = {"redirect": request.headers.get("Referer") if request.headers.get("Referer") else "/"} diff --git a/src/types/account/account.py b/src/types/account/account.py index cdf8c2f..aede2b8 100644 --- a/src/types/account/account.py +++ b/src/types/account/account.py @@ -13,38 +13,3 @@ class Account(DatabaseEntry): username: str created_at: datetime role: str - - -# @dataclass -# class Consumer(Account): -# pass - -# @dataclass -# class Moderator(Account): -# pass - -# @dataclass -# class Administrator(Account): -# pass - -# class Agreement: -# """ -# The user's agreement. -# """ -# name: str -# agreed_at: datetime -# version: str -# def is_outdated(self, version: str) -> bool: -# current_version = parse_version(self.version) -# new_version = parse_version(version) -# return current_version < new_version - -# class __Import: -# """ -# The user's import. -# """ -# id: str -# service: str -# approved: list[str] -# rejected: list[str] -# pending: list[str] diff --git a/src/types/account/notification.py b/src/types/account/notification.py index ba03bb2..e3d6be0 100644 --- a/src/types/account/notification.py +++ b/src/types/account/notification.py @@ -12,7 +12,7 @@ class Notification(DatabaseEntry): account_id: int type: str created_at: datetime - extra_info: Optional[TypedDict] + extra_info: Optional[dict] is_seen: bool = False diff --git a/src/types/paysites/__init__.py b/src/types/paysites/__init__.py index e650c9f..d4f6936 100644 --- a/src/types/paysites/__init__.py +++ b/src/types/paysites/__init__.py @@ -16,6 +16,12 @@ from .subscribestar import Subscribestar # duplicated in /client/src/utils/_index.js +# nothing can be done about that (for now) +# short of refactoring into JSON file and URL templates +# but those have their own issues and sometimes can be way less readable +# than string interpolations for respective languages +# not to mention different libs are going to parse templates +# with all the subtle interop issues class Paysites: afdian = Afdian() diff --git a/src/types/paysites/boosty.py b/src/types/paysites/boosty.py index 062d4d4..5b1a2b2 100644 --- a/src/types/paysites/boosty.py +++ b/src/types/paysites/boosty.py @@ -6,13 +6,13 @@ from .base import Paysite, Service_Post, Service_User @dataclass class User(Service_User): def profile(self, artist: dict) -> str: - return "" + return f"https://boosty.to/{(artist or {}).get('id')}" @dataclass class Post(Service_Post): def link(self, post_id: str, user_id: str) -> str: - return "" + return f"https://boosty.to/{user_id}/posts/{post_id}" @dataclass diff --git a/src/utils/datetime_.py b/src/utils/datetime_.py index 10af38a..232f0ce 100644 --- a/src/utils/datetime_.py +++ b/src/utils/datetime_.py @@ -16,7 +16,7 @@ class TimeRangeInfo: date: datetime min_date: datetime max_date: datetime - navigation_dates: dict[PeriodScale, tuple[date, date, date]] + navigation_dates: dict[PeriodScale, tuple['date', 'date', 'date']] range_desc: str scale: PeriodScale @@ -32,25 +32,33 @@ def get_minmax_ts( round_factor = int(round_to.total_seconds()) rounded_seconds = round(now_as_timedelta.total_seconds() / round_factor) * round_factor rdt = today_as_datetime + timedelta(seconds=rounded_seconds) + return rdt - timedelta(days=1), rdt + case "day": + return ( datetime.combine(input_date, time.min), datetime.combine(input_date, time.max), ) + case "week": input_date = beginning_of_week(input_date) + return ( datetime.combine(beginning_of_week(input_date), time.min), datetime.combine(next_week(input_date), time.min), ) + case "month": input_date = input_date.replace(day=1) last_day = calendar.monthrange(input_date.year, input_date.month)[1] + return ( datetime.combine(input_date.replace(day=1), time.min), datetime.combine(input_date.replace(day=last_day), time.max), ) + case _: raise Exception("Invalid scale") @@ -59,16 +67,21 @@ def surrounding_dates_for_scale(input_date: date, scale: PeriodScale) -> tuple[d match scale: case "recent": # not meaningful delta = relativedelta(hours=24) + case "day": delta = relativedelta(days=1) + case "week": input_date = beginning_of_week(input_date) delta = relativedelta(days=7) + case "month": input_date = input_date.replace(day=1) delta = relativedelta(months=1) + case _: raise Exception("Invalid scale") + return input_date - delta, input_date + delta, input_date @@ -76,18 +89,23 @@ def date_range_description(input_date: datetime, scale: PeriodScale) -> str: match scale: case "recent": return "the past 24 hours" + case "day": return input_date.strftime("%B %d, %Y") + case "week": min_date = beginning_of_week(input_date) max_date = next_week(input_date) + return f"{min_date.strftime("%B %d, %Y")} - {max_date.strftime("%B %d, %Y")}" + case "month": return input_date.strftime("%B %Y") def beginning_of_week(input_date: date) -> date: days_to_monday = input_date.weekday() - 1 if input_date.weekday() != 0 else 6 + return input_date - timedelta(days=days_to_monday) @@ -97,6 +115,7 @@ def next_week(input_datetime: date) -> date: def parse_scale_string(date_string: str | None, scale: PeriodScale = "recent") -> tuple[TimeRangeInfo, bool]: valid_date = True + if date_string is None: parsed_date = datetime.now() else: @@ -107,8 +126,10 @@ def parse_scale_string(date_string: str | None, scale: PeriodScale = "recent") - valid_date = False nav: dict[PeriodScale, tuple[date, date, date]] = {} + for period in get_args(PeriodScale): nav[period] = surrounding_dates_for_scale(parsed_date.date(), period) + desc = date_range_description(parsed_date, scale) (min_date, max_date) = get_minmax_ts(parsed_date, scale)