diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ba349a9c..f6630c524 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,20 +6,35 @@ on: pull_request: jobs: - web: - name: Lint, test and build the React app + build: + name: Lint, test and build runs-on: ubuntu-latest - defaults: - run: - working-directory: web steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - cache-dependency-path: web/package-lock.json - run: npm ci - run: npm run lint - run: npm test - run: npm run build + + e2e: + name: End-to-end tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npx playwright install --with-deps chromium + - run: npm run test:e2e + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index f4f46a5fe..baad8e4d6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,17 +2,18 @@ # compiled output /dist +/dev-dist /tmp -/out-tsc -# Only exists if Bazel was run -/bazel-out # dependencies /node_modules -# profiling files -chrome-profiler-events.json -speed-measure-plugin.json +# tests +/coverage +/test-results +/playwright-report +/blob-report +/playwright/.cache # IDEs and editors /.idea @@ -32,14 +33,9 @@ speed-measure-plugin.json .history/* # misc -/.sass-cache -/connect.lock -/coverage -/libpeerconnection.log npm-debug.log yarn-error.log -testem.log -/typings +*.local # System Files .DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ff155b5fa..000000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: node_js -node_js: - - "6.9" - -branches: - only: - - master - -before_script: - - npm install -g firebase-tools - - npm install -g @angular/cli - -script: - - npm run build - -after_success: - - firebase use default - - firebase deploy --token $FIREBASE_TOKEN - -notifications: - email: - on_failure: change - on_success: change diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 840cf8244..7d862ed94 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,27 +1,27 @@ # Contributing Thank you for your interest in contributing! Please feel free to put up a PR for any issue or feature request. -Even if you have little to no experience with Angular, I'll be more than happy to help. :) +Even if you have little to no experience with React, I'll be more than happy to help. :) ## Setup 1. Fork the repo 2. Clone your fork 3. Make a branch for your feature or bug fix -4. If you don't have Angular CLI installed: `npm install -g angular-cli@latest` -5. `ng init` -6. Type `n` for each file to not overwrite any file changes -7. Run `npm start` and open `localhost:4200` in a browser -8. Work your magic -9. Run `npm run build` or `npm run static-serve` to kick off a production build and make sure nothing is broken -10. To test service worker changes: - * `npm run build` to kick off a fresh build and update the `dist/` directory - * `npm run precache` to generate the service worker file - * `npm run static-serve` to load the application along with the service worker asset using [live-server](https://github.com/tapio/live-server) -11. Add yourself to the [contributor's list](https://github.com/hdjirdeh/angular2-hn#contributors) in the README! -12. Commit your changes and reference the issue you're addressing (for example: `git commit -am 'Commit message. Closes #5'`) -13. Push your branch to your fork -14. Create a pull request from your branch on your fork to `master` on this repo -15. Have your branch get merged in! :star2: +4. `npm ci` +5. `npm run dev` and open `localhost:5173` in a browser +6. Work your magic +7. Before pushing, make sure nothing is broken: + * `npm run lint` + * `npm test` (add or update Vitest/React Testing Library tests for your change) + * `npm run build` + * `npm run test:e2e` for the Playwright suite (run `npx playwright install chromium` once beforehand) +8. To test service worker changes, run `npm run build` followed by `npm run preview` — the service worker is only + generated for production builds +9. Add yourself to the [contributor's list](https://github.com/hdjirdeh/angular2-hn#contributors) in the README! +10. Commit your changes and reference the issue you're addressing (for example: `git commit -am 'Commit message. Closes #5'`) +11. Push your branch to your fork +12. Create a pull request from your branch on your fork to `master` on this repo +13. Have your branch get merged in! :star2: If you experience a problem at any point, please don't hesitate to file an issue or send me a message! diff --git a/README.md b/README.md index 8fca3ce71..3fa3c0ea1 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- A progressive Hacker News client built with Angular + A progressive Hacker News client built with React

@@ -14,52 +14,80 @@

PRs Welcome - Build Status + Build Status

--- -## React migration (in progress) - -The app is being migrated from Angular to React 18 + TypeScript + Vite. The React app lives in [`web/`](/web) while the -Angular app in `src/` stays buildable until parity is reached. - -```bash -cd web -npm ci -npm run dev # dev server on http://localhost:5173 -npm run lint -npm test -npm run build -``` - ---- - :zap: **Fast:** Service Worker App Shell + Dynamic Content model to achieve faster load times with and without a network. :iphone: **Responsive:** Completely responsive UI that can be installed to your mobile home screen to provide a native feel. -:rocket: **Progressive:** [Lighthouse](https://github.com/GoogleChrome/lighthouse) score of 87/100. +:rocket: **Progressive:** installable, offline-capable PWA.

-## Mobile Preview +## Stack -

- -

+The app was originally written in Angular and has been rewritten in React with no change in behaviour: -## Laptop Preview +| | | +| --- | --- | +| UI | React 18 + TypeScript | +| Build / dev server | Vite 5 | +| Routing | React Router v6 | +| Styling | SCSS (three themes: Default, Night, Black (AMOLED)) | +| Unit / component tests | Vitest + React Testing Library | +| End-to-end tests | Playwright | +| PWA | `vite-plugin-pwa` (Workbox) | +| Data | [node-hnapi](https://github.com/cheeaun/node-hnapi) over native `fetch` | -

- -

+## Getting started -## Offline Support +```bash +npm ci +npm run dev # dev server on http://localhost:5173 +npm run build # type-check and build to dist/ +npm run preview # serve the production build (service worker included) +``` + +## Tests -This app uses [Workbox](https://workboxjs.org/) to generate a service worker as part of the build step to load quickly and work offline. +```bash +npm run lint # ESLint (with Prettier compatibility) +npm test # Vitest unit and component tests +npm run test:coverage # the same suite with a V8 coverage report +npm run test:e2e # Playwright end-to-end tests against the production build +``` + +The Playwright suite stubs the Hacker News API with fixtures (`e2e/fixtures.ts`) so it is deterministic and can run +offline. CI (`.github/workflows/ci.yml`) runs lint, unit tests, the build and the e2e suite on every pull request. + +## Project layout + +``` +src/ + api/ fetch-based Hacker News API client + components/ shared presentational components (Loader, ErrorMessage) + context/ SettingsContext (theme, font size, list spacing, link behaviour) + core/ app chrome: Header, Footer, Settings modal + feeds/ feed item row + item-details/ recursive comment tree + models/ TypeScript models (Story, Comment, User, PollResult, Settings) + pages/ routed pages: FeedPage, ItemDetailsPage, UserPage + styles/ SCSS themes and shared variables/mixins + utils/ pure helpers (formatCommentCount) + routes.tsx route table mirroring the original Angular routes +e2e/ Playwright specs and API fixtures +``` + +## Offline support + +`vite-plugin-pwa` generates a Workbox service worker at build time: the app shell is precached and every navigation +falls back to `index.html`, while Hacker News API responses are served with a `NetworkFirst` strategy (24h expiry) so +previously visited feeds and items keep working offline. ## Manifest @@ -78,27 +106,26 @@ Current themes: * Night * Black (AMOLED) -More to come! +The theme follows `prefers-color-scheme` on first load and is then persisted to `localStorage`. -## Areas of improvement +## Mobile Preview - - Realtime updating using the Firebase SDK (may need to add option to settings so service worker can still rely on REST endpoints) - - Server side rendering +

+ +

-Feel free to send me feedback on [twitter](https://twitter.com/hdjirdeh) or [file an issue](https://github.com/hdjirdeh/angular2-hn/issues/new)! Feature requests are always welcome. +## Laptop Preview -## Build process +

+ +

-Note: This project has been ejected (with AOT + production settings) in order to customize Webpack configurations. +## Areas of improvement - - Clone or download the repo - - `npm install` - - `npm start` to run the application with webpack-dev-server or `npm build` to kick off a fresh build and update the output directory (`dist/`) + - Realtime updating using the Firebase SDK (may need to add option to settings so the service worker can still rely on REST endpoints) + - Server side rendering -Note: Any Service Worker changes will not be reflected when you run the application locally in development. To test service worker changes: - - `npm build` - - `npm run precache` to generate the service worker file - - `npm run static-serve` to load the application along with the service worker asset using [live-server](https://github.com/tapio/live-server) +Feel free to send me feedback on [twitter](https://twitter.com/hdjirdeh) or [file an issue](https://github.com/hdjirdeh/angular2-hn/issues/new)! Feature requests are always welcome. ## Contributors diff --git a/angular.json b/angular.json deleted file mode 100644 index bae858c47..000000000 --- a/angular.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "$schema": "./node_modules/@angular/cli/lib/config/schema.json", - "version": 1, - "newProjectRoot": "projects", - "projects": { - "angular-hnpwa": { - "projectType": "application", - "schematics": { - "@schematics/angular:component": { - "style": "scss" - } - }, - "root": "", - "sourceRoot": "src", - "prefix": "app", - "architect": { - "build": { - "builder": "@angular-devkit/build-angular:browser", - "options": { - "aot": true, - "outputPath": "dist/angular-hnpwa", - "index": "src/index.html", - "main": "src/main.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.app.json", - "assets": [ - "src/favicon.ico", - "src/assets", - "src/manifest.json", - "src/manifest.webmanifest" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - }, - "configurations": { - "production": { - "fileReplacements": [ - { - "replace": "src/environments/environment.ts", - "with": "src/environments/environment.prod.ts" - } - ], - "optimization": true, - "outputHashing": "all", - "sourceMap": true, - "extractCss": true, - "namedChunks": true, - "aot": true, - "extractLicenses": true, - "vendorChunk": false, - "buildOptimizer": true, - "budgets": [ - { - "type": "initial", - "maximumWarning": "2mb", - "maximumError": "5mb" - }, - { - "type": "anyComponentStyle", - "maximumWarning": "6kb" - } - ], - "serviceWorker": true, - "ngswConfigPath": "ngsw-config.json" - } - } - }, - "serve": { - "builder": "@angular-devkit/build-angular:dev-server", - "options": { - "browserTarget": "angular-hnpwa:build" - }, - "configurations": { - "production": { - "browserTarget": "angular-hnpwa:build:production" - } - } - }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "browserTarget": "angular-hnpwa:build" - } - }, - "test": { - "builder": "@angular-devkit/build-angular:karma", - "options": { - "main": "src/test.ts", - "polyfills": "src/polyfills.ts", - "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", - "assets": [ - "src/favicon.ico", - "src/assets", - "src/manifest.webmanifest" - ], - "styles": [ - "src/styles.scss" - ], - "scripts": [] - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "tsconfig.app.json", - "tsconfig.spec.json", - "e2e/tsconfig.json" - ], - "exclude": [ - "**/node_modules/**" - ] - } - }, - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "e2e/protractor.conf.js", - "devServerTarget": "angular-hnpwa:serve" - }, - "configurations": { - "production": { - "devServerTarget": "angular-hnpwa:serve:production" - } - } - } - } - } - }, - "defaultProject": "angular-hnpwa" -} diff --git a/browserslist b/browserslist deleted file mode 100644 index 80848532e..000000000 --- a/browserslist +++ /dev/null @@ -1,12 +0,0 @@ -# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. -# For additional information regarding the format and rule options, please see: -# https://github.com/browserslist/browserslist#queries - -# You can see what browsers were selected by your queries by running: -# npx browserslist - -> 0.5% -last 2 versions -Firefox ESR -not dead -not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/e2e/app.spec.ts b/e2e/app.spec.ts new file mode 100644 index 000000000..1521a3b70 --- /dev/null +++ b/e2e/app.spec.ts @@ -0,0 +1,91 @@ +import { expect, test } from '@playwright/test'; + +import { mockHackerNewsApi } from './fixtures'; + +test.beforeEach(async ({ page }) => { + await mockHackerNewsApi(page); +}); + +test('redirects the root url to the news feed and lists stories', async ({ page }) => { + await page.goto('/'); + + await expect(page).toHaveURL('/news/1'); + await expect(page.locator('li.post')).toHaveCount(30); + await expect(page.locator('li.post').first().locator('a.title')).toHaveText('news story 1 (page 1)'); +}); + +test('navigates between the feeds from the header', async ({ page }) => { + await page.goto('/news/1'); + + for (const [link, feedType] of [ + ['new', 'newest'], + ['show', 'show'], + ['ask', 'ask'], + ['jobs', 'jobs'], + ]) { + await page.locator('.header-nav').getByRole('link', { name: link, exact: true }).click(); + await expect(page).toHaveURL(`/${feedType}/1`); + await expect(page.locator('li.post').first().locator('a.title')).toHaveText(`${feedType} story 1 (page 1)`); + } + + await expect(page.locator('p.job-header')).toBeVisible(); +}); + +test('paginates the feed', async ({ page }) => { + await page.goto('/news/1'); + + await expect(page.locator('a.prev')).toHaveCount(0); + await page.locator('a.more').click(); + + await expect(page).toHaveURL('/news/2'); + await expect(page.locator('ol')).toHaveAttribute('start', '31'); + await expect(page.locator('a.prev')).toBeVisible(); +}); + +test('opens an item and renders its nested comments', async ({ page }) => { + await page.goto('/news/1'); + + await page.locator('li.post').first().locator('.subtext-laptop a[href^="/item/"]').click(); + + await expect(page).toHaveURL(/\/item\/\d+$/); + await expect(page.locator('.laptop a.title')).toHaveText('An item with comments'); + await expect(page.getByText('A top level comment')).toBeVisible(); + await expect(page.getByText('A nested reply')).toBeVisible(); + + // Collapsing the top level comment hides its content and its replies. + await page.locator('.comment-list .collapse').first().click(); + await expect(page.getByText('A nested reply')).toBeHidden(); + await expect(page.locator('.comment-list .collapse').first()).toHaveText('[+]'); +}); + +test('opens a user profile from a comment', async ({ page }) => { + await page.goto('/item/42'); + + await page.locator('.comment-list').getByRole('link', { name: 'commenter' }).click(); + + await expect(page).toHaveURL('/user/commenter'); + await expect(page.locator('.profile .name')).toHaveText('commenter'); + await expect(page.locator('.profile')).toContainText('4321 ★'); + await expect(page.getByText('All about the author')).toBeVisible(); +}); + +test('toggles the settings modal and applies a theme', async ({ page }) => { + await page.goto('/news/1'); + + await expect(page.locator('#popup1')).toHaveCount(0); + await page.locator('img.settings').click(); + await expect(page.locator('#popup1')).toBeVisible(); + + await page.getByRole('radio', { name: 'Night' }).check(); + await expect(page.locator('div.night')).toBeVisible(); + + await page.getByRole('radio', { name: 'Black (AMOLED)' }).check(); + await expect(page.locator('div.amoledblack')).toBeVisible(); + + await page.locator('.popup .close').click(); + await expect(page.locator('#popup1')).toHaveCount(0); + + // The theme survives a reload because it is persisted in localStorage. + await page.reload(); + await expect(page.locator('div.amoledblack')).toBeVisible(); +}); diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts new file mode 100644 index 000000000..b60fb4fc0 --- /dev/null +++ b/e2e/fixtures.ts @@ -0,0 +1,84 @@ +import { Page } from '@playwright/test'; + +const API = 'https://node-hnapi.herokuapp.com'; + +export function feedStories(feedType: string, page: number, count = 30) { + return Array.from({ length: count }, (_, index) => ({ + id: page * 1000 + index, + title: `${feedType} story ${index + 1} (page ${page})`, + points: 10 + index, + user: `user${index}`, + time: 1600000000, + time_ago: `${index + 1} hours ago`, + type: 'story', + url: `https://example.com/${feedType}/${index}`, + domain: 'example.com', + comments_count: index, + })); +} + +export const itemWithComments = { + id: 42, + title: 'An item with comments', + points: 123, + user: 'author', + time: 1600000000, + time_ago: '3 hours ago', + type: 'story', + url: 'https://example.com/an-item', + domain: 'example.com', + content: '

Item body

', + comments_count: 2, + comments: [ + { + id: 1, + level: 0, + user: 'commenter', + time: 1600000001, + time_ago: '2 hours ago', + content: '

A top level comment

', + comments: [ + { + id: 2, + level: 1, + user: 'replier', + time: 1600000002, + time_ago: '1 hour ago', + content: '

A nested reply

', + comments: [], + }, + ], + }, + ], +}; + +export const user = { + id: 'author', + created: 'October 22, 2010', + karma: 4321, + about: '

All about the author

', +}; + +/** + * Serves deterministic fixtures for every Hacker News API call so the e2e run + * never depends on the live API. + */ +export async function mockHackerNewsApi(page: Page) { + await page.route(`${API}/**`, async (route) => { + const url = new URL(route.request().url()); + const [, resource, id] = url.pathname.split('/'); + const pageNumber = Number(url.searchParams.get('page') ?? '1'); + + if (resource === 'item') { + await route.fulfill({ json: { ...itemWithComments, id: Number(id) } }); + return; + } + + if (resource === 'user') { + await route.fulfill({ json: { ...user, id: String(id) } }); + return; + } + + await route.fulfill({ json: feedStories(resource, pageNumber) }); + }); +} diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js deleted file mode 100644 index 73e4e6806..000000000 --- a/e2e/protractor.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// @ts-check -// Protractor configuration file, see link for more information -// https://github.com/angular/protractor/blob/master/lib/config.ts - -const { SpecReporter } = require('jasmine-spec-reporter'); - -/** - * @type { import("protractor").Config } - */ -exports.config = { - allScriptsTimeout: 11000, - specs: [ - './src/**/*.e2e-spec.ts' - ], - capabilities: { - 'browserName': 'chrome' - }, - directConnect: true, - baseUrl: 'http://localhost:4200/', - framework: 'jasmine', - jasmineNodeOpts: { - showColors: true, - defaultTimeoutInterval: 30000, - print: function() {} - }, - onPrepare() { - require('ts-node').register({ - project: require('path').join(__dirname, './tsconfig.json') - }); - jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); - } -}; \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts deleted file mode 100644 index 0897abab0..000000000 --- a/e2e/src/app.e2e-spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppPage } from './app.po'; -import { browser, logging } from 'protractor'; - -describe('workspace-project App', () => { - let page: AppPage; - - beforeEach(() => { - page = new AppPage(); - }); - - it('should display welcome message', () => { - page.navigateTo(); - expect(page.getTitleText()).toEqual('Welcome to angular-hnpwa!'); - }); - - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - } as logging.Entry)); - }); -}); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts deleted file mode 100644 index 5776aa9eb..000000000 --- a/e2e/src/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, by, element } from 'protractor'; - -export class AppPage { - navigateTo() { - return browser.get(browser.baseUrl) as Promise; - } - - getTitleText() { - return element(by.css('app-root h1')).getText() as Promise; - } -} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json deleted file mode 100644 index 39b800f78..000000000 --- a/e2e/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "../out-tsc/e2e", - "module": "commonjs", - "target": "es5", - "types": [ - "jasmine", - "jasminewd2", - "node" - ] - } -} diff --git a/web/eslint.config.js b/eslint.config.js similarity index 93% rename from web/eslint.config.js rename to eslint.config.js index 2f61ede78..2cd1abada 100644 --- a/web/eslint.config.js +++ b/eslint.config.js @@ -7,7 +7,7 @@ import reactRefresh from 'eslint-plugin-react-refresh'; import prettier from 'eslint-config-prettier'; export default tseslint.config( - { ignores: ['dist', 'coverage', 'dev-dist'] }, + { ignores: ['dist', 'coverage', 'dev-dist', 'playwright-report', 'test-results'] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended, prettier], files: ['**/*.{ts,tsx}'], diff --git a/index.html b/index.html new file mode 100644 index 000000000..d0d63244b --- /dev/null +++ b/index.html @@ -0,0 +1,79 @@ + + + + + Angular 2 HN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + + + diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index f88489561..000000000 --- a/karma.conf.js +++ /dev/null @@ -1,32 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage-istanbul-reporter'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - coverageIstanbulReporter: { - dir: require('path').join(__dirname, './coverage/angular-hnpwa'), - reports: ['html', 'lcovonly', 'text-summary'], - fixWebpackSourcePaths: true - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/ngsw-config.json b/ngsw-config.json deleted file mode 100644 index 23720c348..000000000 --- a/ngsw-config.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "./node_modules/@angular/service-worker/config/schema.json", - "index": "/index.html", - "assetGroups": [ - { - "name": "app", - "installMode": "prefetch", - "resources": { - "files": [ - "/favicon.ico", - "/index.html", - "/*.css", - "/*.js", - "/manifest.webmanifest" - ] - } - }, { - "name": "assets", - "installMode": "lazy", - "updateMode": "prefetch", - "resources": { - "files": [ - "/assets/**", - "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" - ] - } - } - ] -} diff --git a/web/package-lock.json b/package-lock.json similarity index 71% rename from web/package-lock.json rename to package-lock.json index 9034567d9..fe858adc7 100644 --- a/web/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "react-hnpwa", + "name": "angular2-hn", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-hnpwa", + "name": "angular2-hn", "version": "0.0.0", "dependencies": { "react": "^18.3.1", @@ -14,10 +14,12 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@playwright/test": "^1.62.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.5.0", "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", + "@types/node": "^20.19.43", "@types/react": "^18.3.10", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", @@ -34,6 +36,7 @@ "typescript": "^5.5.4", "typescript-eslint": "^8.7.0", "vite": "^5.4.8", + "vite-plugin-pwa": "^0.20.5", "vitest": "^2.1.1" } }, @@ -152,57 +155,1198 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.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.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "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/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", + "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", + "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "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.29.7", + "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.29.7.tgz", + "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", + "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", + "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", + "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", + "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "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.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", + "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", + "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/template": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", + "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", + "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", + "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", + "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", + "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", + "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", + "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", + "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", + "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", + "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.8.tgz", + "integrity": "sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", + "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", + "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", + "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", + "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", + "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-globals": { + "node_modules/@babel/plugin-transform-react-jsx-source": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-module-transforms": { + "node_modules/@babel/plugin-transform-regexp-modifiers": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", + "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -211,83 +1355,144 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { + "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", + "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-string-parser": { + "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.8.tgz", + "integrity": "sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-identifier": { + "node_modules/@babel/plugin-transform-sticky-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", + "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helper-validator-option": { + "node_modules/@babel/plugin-transform-template-literals": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/helpers": { + "node_modules/@babel/plugin-transform-typeof-symbol": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", + "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", + "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.8" + "@babel/helper-plugin-utils": "^7.29.7" }, - "bin": { - "parser": "bin/babel-parser.js" + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", + "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { + "node_modules/@babel/plugin-transform-unicode-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", "dev": true, "license": "MIT", "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { @@ -297,22 +1502,124 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { + "node_modules/@babel/plugin-transform-unicode-sets-regex": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", + "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", "dev": true, "license": "MIT", "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", + "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.29.7", + "@babel/plugin-syntax-import-attributes": "^7.29.7", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.29.7", + "@babel/plugin-transform-async-generator-functions": "^7.29.7", + "@babel/plugin-transform-async-to-generator": "^7.29.7", + "@babel/plugin-transform-block-scoped-functions": "^7.29.7", + "@babel/plugin-transform-block-scoping": "^7.29.7", + "@babel/plugin-transform-class-properties": "^7.29.7", + "@babel/plugin-transform-class-static-block": "^7.29.7", + "@babel/plugin-transform-classes": "^7.29.7", + "@babel/plugin-transform-computed-properties": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-dotall-regex": "^7.29.7", + "@babel/plugin-transform-duplicate-keys": "^7.29.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-dynamic-import": "^7.29.7", + "@babel/plugin-transform-explicit-resource-management": "^7.29.7", + "@babel/plugin-transform-exponentiation-operator": "^7.29.7", + "@babel/plugin-transform-export-namespace-from": "^7.29.7", + "@babel/plugin-transform-for-of": "^7.29.7", + "@babel/plugin-transform-function-name": "^7.29.7", + "@babel/plugin-transform-json-strings": "^7.29.7", + "@babel/plugin-transform-literals": "^7.29.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", + "@babel/plugin-transform-member-expression-literals": "^7.29.7", + "@babel/plugin-transform-modules-amd": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-modules-systemjs": "^7.29.7", + "@babel/plugin-transform-modules-umd": "^7.29.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", + "@babel/plugin-transform-new-target": "^7.29.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", + "@babel/plugin-transform-numeric-separator": "^7.29.7", + "@babel/plugin-transform-object-rest-spread": "^7.29.7", + "@babel/plugin-transform-object-super": "^7.29.7", + "@babel/plugin-transform-optional-catch-binding": "^7.29.7", + "@babel/plugin-transform-optional-chaining": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/plugin-transform-private-methods": "^7.29.7", + "@babel/plugin-transform-private-property-in-object": "^7.29.7", + "@babel/plugin-transform-property-literals": "^7.29.7", + "@babel/plugin-transform-regenerator": "^7.29.7", + "@babel/plugin-transform-regexp-modifiers": "^7.29.7", + "@babel/plugin-transform-reserved-words": "^7.29.7", + "@babel/plugin-transform-shorthand-properties": "^7.29.7", + "@babel/plugin-transform-spread": "^7.29.7", + "@babel/plugin-transform-sticky-regex": "^7.29.7", + "@babel/plugin-transform-template-literals": "^7.29.7", + "@babel/plugin-transform-typeof-symbol": "^7.29.7", + "@babel/plugin-transform-unicode-escapes": "^7.29.7", + "@babel/plugin-transform-unicode-property-regex": "^7.29.7", + "@babel/plugin-transform-unicode-regex": "^7.29.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.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, + "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/runtime": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", @@ -1167,6 +2474,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "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.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1194,41 +2512,219 @@ ], "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.1.0.tgz", + "integrity": "sha512-dFZNuFD2YRcoomP4oYf+DvQNSUA9ih+A3vUqopQx5EdtPGo3WBnQcI/S8pwpz91UsGfL0HsMSOlaMld8HrbubA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz", + "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "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/@rollup/plugin-replace": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.3.tgz", + "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, "engines": { - "node": "^22.20 || ^24.12 || >=25" + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@rollup/plugin-terser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz", + "integrity": "sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==", "dev": true, "license": "MIT", - "optional": true, + "dependencies": { + "serialize-javascript": "^7.0.3", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, "engines": { - "node": ">=14" + "node": ">=20.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, - "node_modules/@remix-run/router": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", - "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "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/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "node_modules/@rollup/pluginutils/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/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.4", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", @@ -1668,6 +3164,22 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@trickfilm400/rollup-plugin-off-main-thread": { + "version": "3.0.0-pre1", + "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz", + "integrity": "sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.10", + "json5": "^2.2.3", + "magic-string": "^0.30.21", + "string.prototype.matchall": "^4.0.12" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -1734,6 +3246,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -1762,6 +3284,20 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.66.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", @@ -2479,6 +4015,13 @@ "node": ">=12" } }, + "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/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -2496,6 +4039,16 @@ "dev": true, "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2512,6 +4065,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "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.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "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", @@ -2603,6 +4198,13 @@ "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/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -2809,6 +4411,23 @@ "node": ">= 0.8" } }, + "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/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2823,6 +4442,23 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js-compat": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2838,6 +4474,16 @@ "node": ">= 8" } }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -2983,6 +4629,16 @@ "dev": true, "license": "MIT" }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -3081,6 +4737,22 @@ "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.401", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.401.tgz", @@ -3609,6 +5281,19 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz", + "integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -3640,6 +5325,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3653,6 +5355,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "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", @@ -3754,6 +5489,22 @@ "node": ">= 6" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3858,6 +5609,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -3994,6 +5752,13 @@ "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==", + "dev": true, + "license": "ISC" + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4149,6 +5914,13 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4464,6 +6236,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -4504,6 +6283,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4530,6 +6319,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -4559,6 +6358,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -4758,6 +6570,24 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4875,6 +6705,29 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4901,6 +6754,16 @@ "json-buffer": "3.0.1" } }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4931,6 +6794,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5471,6 +7341,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "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/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5536,6 +7453,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -5717,6 +7647,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5738,6 +7688,54 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "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.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "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/resolve": { "version": "2.0.0-next.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", @@ -5937,6 +7935,16 @@ "semver": "bin/semver.js" } }, + "node_modules/serialize-javascript": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -6105,6 +8113,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/smob": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.2.tgz", + "integrity": "sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz", + "integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6115,6 +8143,27 @@ "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/source-map-support/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/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6296,6 +8345,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-ansi": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", @@ -6339,6 +8403,16 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -6398,6 +8472,54 @@ "dev": true, "license": "MIT" }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.49.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.2.tgz", + "integrity": "sha512-rGbJiKeQ4WDe3EXlDAIaQcwftVfv2Q8o1awFNfvXolJYKkb1AuZY1RTOmqx4LJXZENbWZA7eIsYGHuEzHsi1nQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/test-exclude": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", @@ -6629,6 +8751,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -6721,47 +8856,132 @@ "node": ">=14.17" } }, - "node_modules/typescript-eslint": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", - "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", + "node_modules/typescript-eslint": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", + "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.66.0", + "@typescript-eslint/parser": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.66.0", - "@typescript-eslint/parser": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0" + "crypto-random-string": "^2.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">=8" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "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", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10.0.0" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" } }, "node_modules/update-browserslist-db": { @@ -6888,6 +9108,37 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-pwa": { + "version": "0.20.5", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.20.5.tgz", + "integrity": "sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.0", + "workbox-build": "^7.1.0", + "workbox-window": "^7.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^0.2.6", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0", + "workbox-build": "^7.1.0", + "workbox-window": "^7.1.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, "node_modules/vitest": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", @@ -7147,6 +9398,384 @@ "node": ">=0.10.0" } }, + "node_modules/workbox-background-sync": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz", + "integrity": "sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.1.tgz", + "integrity": "sha512-uAlgslKLvbQY+suirIdnBCSYrcgBhjp81Nj4l1lj/Jmj0MJO2CJERnCJjT0GFVwmReV0N+zs78K6gqd5gr9/+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-build": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.1.tgz", + "integrity": "sha512-SDhxIvEAde9Gy/5w4Yo1Jh/M49Z0qE3q0oteyE8zGq0DScxFqVBcCtIXFuLtmtxRQZCMbf0prco4VyEu3KBQuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.3", + "@rollup/plugin-replace": "^6.0.3", + "@rollup/plugin-terser": "^1.0.0", + "@trickfilm400/rollup-plugin-off-main-thread": "^3.0.0-pre1", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "eta": "^4.5.1", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "pretty-bytes": "^5.3.0", + "rollup": "^4.53.3", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.1", + "workbox-broadcast-update": "7.4.1", + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-google-analytics": "7.4.1", + "workbox-navigation-preload": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-range-requests": "7.4.1", + "workbox-recipes": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1", + "workbox-streams": "7.4.1", + "workbox-sw": "7.4.1", + "workbox-window": "7.4.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz", + "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jsonpointer": "^5.0.1", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "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/workbox-build/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/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/workbox-build/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.1.tgz", + "integrity": "sha512-8xaFoJdDc2OjrlbbL3gEeBO1WKcMwRqwLRupgqahYXu75yXajPLuwrbXMrIGZuWYXrQwk0xDjOxZ/ujCy/oJYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-core": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.1.tgz", + "integrity": "sha512-DT+vu46eh/2vRsSHTY4Xmc32Z1rr9PRlQUXr1Dx30ZuXRWwOsvZgGgcwxcasubQLQmbTNYZjv44LkBAQ4tT5tQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.1.tgz", + "integrity": "sha512-lRKUF7b+OGbeXkQk1s6MHXOa3d7Xxf7Of31W6c6hCfipfIyrtdWZ89stq21AHZMaoG7VNFoHply4Ox+rU31TWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.1.tgz", + "integrity": "sha512-Mks1JwLEt++ZAkF6sS1OpSh9RtAMIsiDgRpK+codiHGIPXeaUOgi4cPc3GFadUl8V5QPeypEk8Oxgl3HlwVzHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "7.4.1", + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.1.tgz", + "integrity": "sha512-C4KVsjPcYKJOhr631AxR9XoG2rLF3QiTk5aMv36MXOjtWvm8axwNFAtKUPGsWUwLXXAMgYM1En7fsvndaXeXRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.1.tgz", + "integrity": "sha512-cdr/9qByww7yzEp7zg/qI4ukUrrNjQLgN+ONQRpjy/VqGQXwkgHwr00KksGJK8v0VifwDXBb8a4cWNZH71jn3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.1.tgz", + "integrity": "sha512-7i2oxAUE82gHdAJBCAQ04JzNOdRPqzuOzGfoUyJpFSmeqBNYGPrAH8GPoPjUQTfp+NycwrD2H68VtuF8qxv0vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.1.tgz", + "integrity": "sha512-gnbVfmV4/TtmQaM4x9AtuXhcdstJsep3XMVeztOrQVPT+R6+6DeBjGTCQ7fFCXm+4GEHUA5VEBTyi5+4gWGeog==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "7.4.1", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.1.tgz", + "integrity": "sha512-yubJGErZOusuidAenaL5ypfhQOa7urxP/f8E0ws7FPb4039RiWXUWBAyUkmUoOL/BcQGen3h0J8872d51IYxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.1.tgz", + "integrity": "sha512-GZxpaw9NbmOelj7667uZ2kpk5BFpOGbO4X0qjwh5ls8XQ8C+Lha5LQchTiUzsTFSS+NlUpftYAyOVXvQUrcqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.1.tgz", + "integrity": "sha512-HWWtraKUbJknd9kgqGcpQ3G114HOPYvqs8HaJMDs2ebLNAimDkVDaWfAXE6Ybl+m8U6KsCE6pWyLYuigWmnAXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.1", + "workbox-routing": "7.4.1" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.1.tgz", + "integrity": "sha512-fez5f2DUlDJWTFYkCWQpY10N8gtztd849NswCbVFk0QlcSM4HT5A8x4g4ii650yem4I8tHY0R7JZahwp3ltIPw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-window": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.1.tgz", + "integrity": "sha512-notZDH2u8VXaqyuD7xaqIfEFi6SRM4SUSd7ewe9PDsVqADuepxX2ZMY3uvuZGxzY5ZOsGC/vD3A/3smFtJt4/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.1" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/package.json b/package.json index ae8aeb42d..24712dd10 100644 --- a/package.json +++ b/package.json @@ -1,57 +1,58 @@ { - "name": "angular-hnpwa", - "version": "0.0.0", - "scripts": { - "ng": "ng", - "start": "ng serve", - "build": "ng build", - "test": "ng test", - "lint": "ng lint", - "e2e": "ng e2e" - }, - "private": true, - "dependencies": { - "@angular/animations": "~9.0.1", - "@angular/common": "~9.0.1", - "@angular/compiler": "~9.0.1", - "@angular/core": "~9.0.1", - "@angular/forms": "~9.0.1", - "@angular/platform-browser": "~9.0.1", - "@angular/platform-browser-dynamic": "~9.0.1", - "@angular/router": "~9.0.1", - "@angular/service-worker": "~9.0.1", - "node-fetch": "^2.6.0", - "rxjs": "~6.5.4", - "rxjs-compat": "^6.5.2", - "tslib": "^1.10.0", - "unfetch": "^4.1.0", - "zone.js": "~0.10.2" - }, - "devDependencies": { - "@angular-devkit/build-angular": "~0.900.2", - "@angular/cli": "~9.0.2", - "@angular/compiler-cli": "~9.0.1", - "@angular/language-service": "~9.0.1", - "@types/jasmine": "~3.3.8", - "@types/jasminewd2": "~2.0.3", - "@types/node": "^12.11.1", - "codelyzer": "^5.1.2", - "jasmine-core": "~3.4.0", - "jasmine-spec-reporter": "~4.2.1", - "karma": "~4.1.0", - "karma-chrome-launcher": "~2.2.0", - "karma-coverage-istanbul-reporter": "~2.0.1", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.4.0", - "protractor": "~5.4.0", - "ts-node": "~7.0.0", - "tslint": "~5.15.0", - "typescript": "~3.7.5" - }, - "prettier": { - "trailingComma": "es5", - "tabWidth": 4, - "singleQuote": true, - "printWidth": 120 - } + "name": "angular2-hn", + "description": "A Hacker News progressive web app built with React, TypeScript and Vite", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "start": "vite", + "build": "tsc --noEmit && vite build", + "preview": "vite preview", + "lint": "eslint .", + "format": "prettier --write \"src/**/*.{ts,tsx,scss}\"", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@eslint/js": "^9.11.1", + "@playwright/test": "^1.62.1", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.1", + "@testing-library/user-event": "^14.5.2", + "@types/node": "^20.19.43", + "@types/react": "^18.3.10", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "@vitest/coverage-v8": "^2.1.1", + "eslint": "^9.11.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.12", + "globals": "^15.9.0", + "jsdom": "^25.0.1", + "prettier": "^3.3.3", + "sass": "~1.77.8", + "typescript": "^5.5.4", + "typescript-eslint": "^8.7.0", + "vite": "^5.4.8", + "vite-plugin-pwa": "^0.20.5", + "vitest": "^2.1.1" + }, + "prettier": { + "trailingComma": "es5", + "tabWidth": 4, + "singleQuote": true, + "printWidth": 120 + } } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..78579bc63 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,22 @@ +import { defineConfig, devices } from '@playwright/test'; + +const PORT = 4173; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'list', + use: { + baseURL: `http://localhost:${PORT}`, + trace: 'on-first-retry', + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], + webServer: { + command: `npm run build && npm run preview -- --port ${PORT} --strictPort`, + url: `http://localhost:${PORT}`, + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, +}); diff --git a/src/assets/icons/android-chrome-144x144.png b/public/assets/icons/android-chrome-144x144.png similarity index 100% rename from src/assets/icons/android-chrome-144x144.png rename to public/assets/icons/android-chrome-144x144.png diff --git a/src/assets/icons/android-chrome-192x192.png b/public/assets/icons/android-chrome-192x192.png similarity index 100% rename from src/assets/icons/android-chrome-192x192.png rename to public/assets/icons/android-chrome-192x192.png diff --git a/src/assets/icons/android-chrome-256x256.png b/public/assets/icons/android-chrome-256x256.png similarity index 100% rename from src/assets/icons/android-chrome-256x256.png rename to public/assets/icons/android-chrome-256x256.png diff --git a/src/assets/icons/android-chrome-512x512.png b/public/assets/icons/android-chrome-512x512.png similarity index 100% rename from src/assets/icons/android-chrome-512x512.png rename to public/assets/icons/android-chrome-512x512.png diff --git a/src/assets/icons/apple-touch-icon-120x120.png b/public/assets/icons/apple-touch-icon-120x120.png similarity index 100% rename from src/assets/icons/apple-touch-icon-120x120.png rename to public/assets/icons/apple-touch-icon-120x120.png diff --git a/src/assets/icons/apple-touch-icon-152x152.png b/public/assets/icons/apple-touch-icon-152x152.png similarity index 100% rename from src/assets/icons/apple-touch-icon-152x152.png rename to public/assets/icons/apple-touch-icon-152x152.png diff --git a/src/assets/icons/apple-touch-icon-180x180.png b/public/assets/icons/apple-touch-icon-180x180.png similarity index 100% rename from src/assets/icons/apple-touch-icon-180x180.png rename to public/assets/icons/apple-touch-icon-180x180.png diff --git a/src/assets/icons/apple-touch-icon-60x60.png b/public/assets/icons/apple-touch-icon-60x60.png similarity index 100% rename from src/assets/icons/apple-touch-icon-60x60.png rename to public/assets/icons/apple-touch-icon-60x60.png diff --git a/src/assets/icons/apple-touch-icon-76x76.png b/public/assets/icons/apple-touch-icon-76x76.png similarity index 100% rename from src/assets/icons/apple-touch-icon-76x76.png rename to public/assets/icons/apple-touch-icon-76x76.png diff --git a/src/assets/icons/apple-touch-icon.png b/public/assets/icons/apple-touch-icon.png similarity index 100% rename from src/assets/icons/apple-touch-icon.png rename to public/assets/icons/apple-touch-icon.png diff --git a/src/assets/icons/browserconfig.xml b/public/assets/icons/browserconfig.xml similarity index 100% rename from src/assets/icons/browserconfig.xml rename to public/assets/icons/browserconfig.xml diff --git a/src/assets/icons/favicon-16x16.png b/public/assets/icons/favicon-16x16.png similarity index 100% rename from src/assets/icons/favicon-16x16.png rename to public/assets/icons/favicon-16x16.png diff --git a/src/assets/icons/favicon-32x32.png b/public/assets/icons/favicon-32x32.png similarity index 100% rename from src/assets/icons/favicon-32x32.png rename to public/assets/icons/favicon-32x32.png diff --git a/src/assets/icons/mstile-150x150.png b/public/assets/icons/mstile-150x150.png similarity index 100% rename from src/assets/icons/mstile-150x150.png rename to public/assets/icons/mstile-150x150.png diff --git a/src/assets/icons/safari-pinned-tab.svg b/public/assets/icons/safari-pinned-tab.svg similarity index 100% rename from src/assets/icons/safari-pinned-tab.svg rename to public/assets/icons/safari-pinned-tab.svg diff --git a/src/assets/images/cog.svg b/public/assets/images/cog.svg similarity index 100% rename from src/assets/images/cog.svg rename to public/assets/images/cog.svg diff --git a/src/assets/images/logo-header.png b/public/assets/images/logo-header.png similarity index 100% rename from src/assets/images/logo-header.png rename to public/assets/images/logo-header.png diff --git a/src/assets/images/logo.svg b/public/assets/images/logo.svg similarity index 100% rename from src/assets/images/logo.svg rename to public/assets/images/logo.svg diff --git a/src/favicon.ico b/public/favicon.ico similarity index 100% rename from src/favicon.ico rename to public/favicon.ico diff --git a/web/src/App.scss b/src/App.scss similarity index 100% rename from web/src/App.scss rename to src/App.scss diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 000000000..9323ceac6 --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,81 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { App } from './App'; +import { SettingsProvider } from './context/SettingsContext'; +import { stubMatchMedia } from './testUtils/matchMedia'; + +function renderApp(initialEntry = '/news/1') { + vi.stubGlobal('scrollTo', vi.fn()); + + return render( + + + + }> + news feed

} /> + show feed

} /> +
+
+
+
+ ); +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('App', () => { + it('renders the themed shell with the header, the routed page and the footer', () => { + stubMatchMedia(false); + + const { container } = renderApp(); + + expect(container.querySelector('.default .body-cover')).not.toBeNull(); + expect(screen.getByText('news feed').parentElement).toHaveClass('wrapper'); + expect(container.querySelector('.wrapper #header')).not.toBeNull(); + expect(container.querySelector('.wrapper #footer')).not.toBeNull(); + }); + + it('applies the theme coming from the settings', () => { + localStorage.setItem('theme', 'amoledblack'); + stubMatchMedia(false); + + const { container } = renderApp(); + + expect(container.querySelector('.amoledblack')).not.toBeNull(); + }); + + it('sends a Google Analytics pageview on the initial render and on every navigation', async () => { + stubMatchMedia(false); + const ga = vi.fn(); + vi.stubGlobal('ga', ga); + + renderApp(); + + expect(ga.mock.calls).toEqual([ + ['set', 'page', '/news/1'], + ['send', 'pageview'], + ]); + + ga.mockClear(); + await userEvent.click(screen.getByRole('link', { name: 'show' })); + + expect(screen.getByText('show feed')).toBeInTheDocument(); + expect(ga.mock.calls).toEqual([ + ['set', 'page', '/show/1'], + ['send', 'pageview'], + ]); + }); + + it('does not throw when Google Analytics is not loaded', () => { + stubMatchMedia(false); + vi.stubGlobal('ga', undefined); + + expect(() => renderApp()).not.toThrow(); + expect(screen.getByText('news feed')).toBeInTheDocument(); + }); +}); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 000000000..c47d2f5cd --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; + +import { useSettings } from './context/SettingsContext'; +import { Footer } from './core/Footer'; +import { Header } from './core/Header'; +import './App.scss'; + +declare global { + interface Window { + ga?: (...args: unknown[]) => void; + } +} + +export function App() { + const { settings } = useSettings(); + const { pathname } = useLocation(); + + useEffect(() => { + if (typeof window.ga !== 'function') { + return; + } + + window.ga('set', 'page', pathname); + window.ga('send', 'pageview'); + }, [pathname]); + + return ( +
+
+
+
+ +
+
+
+ ); +} + +export default App; diff --git a/web/src/api/hackerNews.test.ts b/src/api/hackerNews.test.ts similarity index 100% rename from web/src/api/hackerNews.test.ts rename to src/api/hackerNews.test.ts diff --git a/web/src/api/hackerNews.ts b/src/api/hackerNews.ts similarity index 100% rename from web/src/api/hackerNews.ts rename to src/api/hackerNews.ts diff --git a/src/app/app.component.html b/src/app/app.component.html deleted file mode 100644 index e60bb4612..000000000 --- a/src/app/app.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
- - - -
-
diff --git a/src/app/app.component.scss b/src/app/app.component.scss deleted file mode 100644 index c0b93891e..000000000 --- a/src/app/app.component.scss +++ /dev/null @@ -1,24 +0,0 @@ -@import "./shared/scss/media"; -@import "./shared/scss/theme_variables"; - -.body-cover { - width: 100%; - z-index: 0; - position: fixed; - height: 100%; -} - -.wrapper { - position: relative; - width: 85%; - min-height: 80px; - margin: 0 auto; - font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; - font-size: 15px; - height: 100%; - line-height: 1.3; - - @media #{$mobile-only} { - width: 100%; - } -} diff --git a/src/app/app.component.ts b/src/app/app.component.ts deleted file mode 100644 index dba13510e..000000000 --- a/src/app/app.component.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Component } from '@angular/core'; -import { Router, NavigationEnd } from '@angular/router'; - -import { SettingsService } from './shared/services/settings.service'; -import { Settings } from './shared/models/settings'; - -declare let ga: Function; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.scss'] -}) - -export class AppComponent { - settings: Settings; - theme: string; - - constructor( - private _settingsService: SettingsService, - public router: Router - ) { - this.settings = this._settingsService.settings; - this.router.events.subscribe(event => { - if (event instanceof NavigationEnd) { - ga('set', 'page', event.urlAfterRedirects); - ga('send', 'pageview'); - } - }); - } -} diff --git a/src/app/app.module.ts b/src/app/app.module.ts deleted file mode 100644 index 5753df619..000000000 --- a/src/app/app.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BrowserModule } from '@angular/platform-browser'; -import { NgModule } from '@angular/core'; - -import { routing } from './app.routes'; - -import { AppComponent } from './app.component'; -import { CoreModule } from './core/core.module'; -import { FeedComponent } from './feeds/feed/feed.component'; -import { ItemComponent } from './feeds/item/item.component'; -import { SharedComponentsModule } from './shared/components/shared-components.module'; -import { PipesModule } from './shared/pipes/pipes.module'; -import { ServiceWorkerModule } from '@angular/service-worker'; -import { environment } from '../environments/environment'; -import { HackerNewsAPIService } from './shared/services/hackernews-api.service'; -import { SettingsService } from './shared/services/settings.service'; - -@NgModule({ - declarations: [AppComponent, FeedComponent, ItemComponent], - imports: [ - BrowserModule, - routing, - CoreModule, - SharedComponentsModule, - PipesModule, - ServiceWorkerModule.register('ngsw-worker.js', { - enabled: environment.production, - }), - ], - providers: [HackerNewsAPIService, SettingsService], - bootstrap: [AppComponent], -}) -export class AppModule {} diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts deleted file mode 100644 index 01df4707b..000000000 --- a/src/app/app.routes.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Routes, RouterModule } from '@angular/router'; - -import { FeedComponent } from './feeds/feed/feed.component'; - -const feedRoutes = [{ - path: ':page', - component: FeedComponent -}]; - -const routes: Routes = [ - {path: '', redirectTo: 'news/1', pathMatch: 'full'}, - { - path: 'news', - children: feedRoutes, - data: {feedType: 'news'} - }, - { - path: 'newest', - children: feedRoutes, - data: {feedType: 'newest'} - }, - { - path: 'show', - children: feedRoutes, - data: {feedType: 'show'} - }, - { - path: 'ask', - children: feedRoutes, - data: {feedType: 'ask'} - }, - { - path: 'jobs', - children: feedRoutes, - data: {feedType: 'jobs'} - }, - {path: 'item', loadChildren: () => import('./item-details/item-details.module').then(m => m.ItemDetailsModule)}, - {path: 'user', loadChildren: () => import('./user/user.module').then(m => m.UserModule)} -]; - - -// - Updated Export -export const routing = RouterModule.forRoot(routes); diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts deleted file mode 100644 index cd00a0c21..000000000 --- a/src/app/core/core.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; -import { SettingsComponent } from './settings/settings.component'; -import { HeaderComponent } from './header/header.component'; -import { FooterComponent } from './footer/footer.component'; - -@NgModule({ - imports: [CommonModule, RouterModule], - declarations: [HeaderComponent, FooterComponent, SettingsComponent], - exports: [HeaderComponent, FooterComponent] -}) -export class CoreModule { } diff --git a/src/app/core/footer/footer.component.html b/src/app/core/footer/footer.component.html deleted file mode 100644 index 68d0861e2..000000000 --- a/src/app/core/footer/footer.component.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/src/app/core/footer/footer.component.scss b/src/app/core/footer/footer.component.scss deleted file mode 100644 index 8adac775d..000000000 --- a/src/app/core/footer/footer.component.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import "../../shared/scss/media"; -@import "../../shared/scss/theme_variables"; - -#footer { - position: relative; - padding: 10px; - height: 60px; - letter-spacing: 0.7px; - text-align: center; - - a { - font-weight: bold; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - - @media #{$mobile-only} { - display: none; - } -} diff --git a/src/app/core/footer/footer.component.ts b/src/app/core/footer/footer.component.ts deleted file mode 100644 index da17d8242..000000000 --- a/src/app/core/footer/footer.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-footer', - templateUrl: './footer.component.html', - styleUrls: ['./footer.component.scss'] -}) -export class FooterComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } - -} diff --git a/src/app/core/header/header.component.html b/src/app/core/header/header.component.html deleted file mode 100644 index 8e01f33ee..000000000 --- a/src/app/core/header/header.component.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - -
diff --git a/src/app/core/header/header.component.scss b/src/app/core/header/header.component.scss deleted file mode 100644 index 75a262e76..000000000 --- a/src/app/core/header/header.component.scss +++ /dev/null @@ -1,149 +0,0 @@ -@import "../../shared/scss/media"; -@import "../../shared/scss/theme_variables"; - -#header { - color: #fff; - padding: 6px 0; - line-height: 18px; - vertical-align: middle; - position: relative; - z-index: 1; - width: 100%; - - @media #{$mobile-only} { - height: 50px; - position: fixed; - top: 0; - } - - a { - display: inline; - } -} - -.home-link { - width: 50px; - height: 66px; -} - -.logo-inner { - width: 32px; - position: absolute; - left: 17px; - top: 18px; - z-index: -1; - height: 32px; - border-radius: 50%; - - @media #{$mobile-only} { - left: 16px; - top: 12px; - } -} - -.logo { - width: 50px; - padding: 3px 8px 0; - - @media #{$mobile-only} { - width: 45px; - padding: 0 0 0 10px; - } -} - -h1 { - font-weight: normal; - display: inline-block; - vertical-align:middle; - margin: 0; - font-size: 16px; - - a { - color: #fff; - text-decoration: none; - } -} - -.name { - margin-right: 30px; - margin-bottom: 2px; - - @media #{$mobile-only} { - display: none; - } -} - -.header-text { - position: absolute; - width: inherit; - height: 20px; - left: 10px; - top: 27px; - z-index: -1; - - @media #{$mobile-only} { - top: 22px; - } -} - -.left { - position: absolute; - left: 60px; - font-size: 16px; - - @media #{$mobile-only} { - width: 100%; - left: 0; - } -} - -.header-nav { - display: inline-block; - margin-left: 20px; - - @media #{$mobile-only} { - margin-left: 60px; - } - - a { - color: hsla(0,0%,100%,.9); - text-decoration: none; - margin: 0 5px; - letter-spacing: 1.8px; - - &:hover { - color: #fff; - } - } - - .active { - color: #fff; - } -} - -.info { - position: absolute; - top: 0; - right: 20px; - height: 100%; - - @media #{$mobile-only} { - right: 10px; - } - - img { - opacity: 0.8; - width: 25px; - margin-top: 21.5px; - display: block; - - &:hover { - opacity: 1; - cursor: pointer; - } - - @media #{$mobile-only} { - margin-top: 15px; - } - } -} diff --git a/src/app/core/header/header.component.ts b/src/app/core/header/header.component.ts deleted file mode 100644 index 4c5181358..000000000 --- a/src/app/core/header/header.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -import { SettingsService } from '../../shared/services/settings.service'; -import { Settings } from '../../shared/models/settings'; - -@Component({ - selector: 'app-header', - templateUrl: './header.component.html', - styleUrls: ['./header.component.scss'] -}) -export class HeaderComponent implements OnInit { - settings: Settings; - - constructor(private _settingsService: SettingsService) { - this.settings = this._settingsService.settings; - } - - ngOnInit() { - } - - toggleSettings() { - this._settingsService.toggleSettings(); - } - - scrollTop() { - window.scrollTo(0, 0); - } -} diff --git a/src/app/core/settings/settings.component.html b/src/app/core/settings/settings.component.html deleted file mode 100644 index 0156eca53..000000000 --- a/src/app/core/settings/settings.component.html +++ /dev/null @@ -1,83 +0,0 @@ -
- -
diff --git a/src/app/core/settings/settings.component.scss b/src/app/core/settings/settings.component.scss deleted file mode 100644 index 689da6584..000000000 --- a/src/app/core/settings/settings.component.scss +++ /dev/null @@ -1,74 +0,0 @@ -@import "../../shared/scss/media"; -@import "../../shared/scss/theme_variables"; - -.overlay { - position: fixed; - top: 0; - bottom: 0; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.7); - opacity: 1; - z-index: 1; -} - -.popup { - margin: 70px auto; - padding: 30px; - border-radius: 5px; - width: 30%; - position: relative; - h1 { - margin-top: 0; - margin-bottom: 0px; - color: #fff; - text-align: center; - letter-spacing: 1px; - } - h2 { - padding-top: 10px; - } - hr { - width: 40%; - margin-bottom: 20px; - } - .close { - position: absolute; - top: 12px; - right: 20px; - font-size: 30px; - font-weight: bold; - text-decoration: none; - color: rgba(255,255,255,0.8); - &:hover { - color: #fff; - cursor: pointer; - } - } - .content { - max-height: 30%; - color: #fff; - letter-spacing: 1px; - overflow: auto; - } - input[type=number] { - display: block; - width: 80%; - height: 20px; - margin-bottom: 15px; - border-radius: 5px; - padding: 2px; - } -} - -.control-section { - margin-bottom: 15px; - padding-bottom: 15px; - border-bottom: 1px solid white; -} - -@media screen and (max-width: 700px) { - .box, .popup { - width: 70%; - } -} diff --git a/src/app/core/settings/settings.component.ts b/src/app/core/settings/settings.component.ts deleted file mode 100644 index a26f89d99..000000000 --- a/src/app/core/settings/settings.component.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -import { SettingsService } from '../../shared/services/settings.service'; -import { Settings } from '../../shared/models/settings'; - -@Component({ - selector: 'app-settings', - templateUrl: './settings.component.html', - styleUrls: ['./settings.component.scss'] -}) -export class SettingsComponent implements OnInit { - settings: Settings; - - constructor(private _settingsService: SettingsService) { - this.settings = this._settingsService.settings; - } - - ngOnInit() { - } - - closeSettings() { - this._settingsService.toggleSettings(); - } - - toggleOpenLinksInNewTab() { - this._settingsService.toggleOpenLinksInNewTab(); - } - - selectTheme(theme) { - this._settingsService.setTheme(theme); - } - - changeTitleFont(val){ - this._settingsService.setFont(val); - } - - changeSpacing(val){ - this._settingsService.setSpacing(val); - } -} diff --git a/src/app/feeds/feed/feed.component.html b/src/app/feeds/feed/feed.component.html deleted file mode 100644 index df9d45596..000000000 --- a/src/app/feeds/feed/feed.component.html +++ /dev/null @@ -1,24 +0,0 @@ -
- - - -
-

- These are jobs at startups that were funded by Y Combinator. - You can also get a job at a YC startup through Triplebyte. -

-
    -
  1. - -
  2. -
- -
-
diff --git a/src/app/feeds/feed/feed.component.scss b/src/app/feeds/feed/feed.component.scss deleted file mode 100644 index febcd8cdd..000000000 --- a/src/app/feeds/feed/feed.component.scss +++ /dev/null @@ -1,108 +0,0 @@ -@import "../../shared/scss/media"; -@import "../../shared/scss/theme_variables"; - -a { - text-decoration: none; - font-weight: bold; - - &:hover { - text-decoration: underline; - }; -} - -ol { - padding: 0 40px; - margin: 0; - - @media #{$mobile-only} { - box-sizing: border-box; - list-style: none; - padding: 0 10px; - } - - li { - position: relative; - -webkit-transition: background-color .2s ease; - transition: background-color .2s ease; - } -} - -.list-margin { - @media #{$mobile-only} { - margin-top: 55px; - } -} - -.main-content { - position: relative; - width: 100%; - min-height: 100vh; - -webkit-transition: opacity .2s ease; - transition: opacity .2s ease; - box-sizing: border-box; - padding: 8px 0; - z-index: 0; -} - -.post { - padding: 10px 0 10px 5px; - transition: background-color 0.2s ease; - border-bottom: 1px solid #CECECB; - - .itemNum { - color: #696969; - position: absolute; - width: 30px; - text-align: right; - left: 0; - top: 4px; - } -} - -.item-block { - display: block; -} - - -.nav { - padding: 10px 40px; - margin-top: 10px; - font-size: 17px; - - a { - @media #{$mobile-only} { - text-decoration: none; - } - } - - @media #{$mobile-only} { - margin: 20px 0; - text-align: center; - padding: 10px 80px; - height: 20px; - } - - .prev { - padding-right: 20px; - - @media #{$mobile-only} { - float: left; - padding-right: 0; - } - } - - .more { - @media #{$mobile-only} { - float: right; - } - } -} - -.job-header { - font-size: 15px; - padding: 0 40px 10px; - - @media #{$mobile-only} { - padding: 60px 15px 25px 15px; - } -} diff --git a/src/app/feeds/feed/feed.component.ts b/src/app/feeds/feed/feed.component.ts deleted file mode 100644 index 7550a0bb7..000000000 --- a/src/app/feeds/feed/feed.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { Subscription } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; - -import { HackerNewsAPIService } from '../../shared/services/hackernews-api.service'; -import { Story } from '../../shared/models/story'; - -@Component({ - selector: 'app-feed', - templateUrl: './feed.component.html', - styleUrls: ['./feed.component.scss'] -}) - -export class FeedComponent implements OnInit { - typeSub: Subscription; - pageSub: Subscription; - items: Story[]; - feedType: string; - pageNum: number; - listStart: number; - errorMessage = ''; - - constructor( - private _hackerNewsAPIService: HackerNewsAPIService, - private route: ActivatedRoute - ) { } - - ngOnInit() { - this.typeSub = this.route - .data - .subscribe(data => { - this.feedType = (data as any).feedType; - }); - - this.pageSub = this.route.params.subscribe(params => { - this.pageNum = params['page'] ? +params['page'] : 1; - this._hackerNewsAPIService.fetchFeed(this.feedType, this.pageNum) - .subscribe( - items => this.items = items, - error => this.errorMessage = 'Could not load ' + this.feedType + ' stories.', - () => { - this.listStart = ((this.pageNum - 1) * 30) + 1; - window.scrollTo(0, 0); - } - ); - }); - } -} diff --git a/src/app/feeds/item/item.component.html b/src/app/feeds/item/item.component.html deleted file mode 100644 index 1b1123196..000000000 --- a/src/app/feeds/item/item.component.html +++ /dev/null @@ -1,39 +0,0 @@ -
-

- - {{item.title}} - - ({{item.domain}}) -

-

- - {{item.title}} - -

-
-
- {{item.user}} - {{item.points}} ★ -
- -
-
- - {{item.points}} points by - {{item.user}} - - - {{ item.time_ago }} - | - - {{item.comments_count | comment }} - - - -
-
diff --git a/src/app/feeds/item/item.component.ts b/src/app/feeds/item/item.component.ts deleted file mode 100644 index 8a1bd9789..000000000 --- a/src/app/feeds/item/item.component.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Story } from '../../shared/models/story'; - -import { SettingsService } from '../../shared/services/settings.service'; -import { Settings } from '../../shared/models/settings'; - -@Component({ - selector: 'item', - templateUrl: './item.component.html', - styleUrls: ['./item.component.scss'] -}) -export class ItemComponent implements OnInit { - @Input() item: Story; - settings: Settings; - - constructor(private _settingsService: SettingsService) { - this.settings = this._settingsService.settings; - } - - ngOnInit() {} - - get hasUrl(): boolean { - return this.item.url.indexOf('http') === 0; - } - -} diff --git a/src/app/item-details/comment/comment.component.html b/src/app/item-details/comment/comment.component.html deleted file mode 100644 index 6137a8cd2..000000000 --- a/src/app/item-details/comment/comment.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
-
- [{{collapse ? '+' : '-'}}] - {{comment.user}} - {{comment.time_ago}} -
-
-
-

-
    -
  • - -
  • -
-
-
-
-
-
- [deleted] | Comment Deleted -
-
\ No newline at end of file diff --git a/src/app/item-details/comment/comment.component.scss b/src/app/item-details/comment/comment.component.scss deleted file mode 100644 index 83992b13c..000000000 --- a/src/app/item-details/comment/comment.component.scss +++ /dev/null @@ -1,85 +0,0 @@ -@import "../../shared/scss/media"; -@import "../../shared/scss/theme_variables"; - -:host >>> { - a { - font-weight: bold; - text-decoration: none; - &:hover { - text-decoration: underline; - } - } -} - -.meta { - font-size: 13px; - color: #696969; - font-weight: bold; - letter-spacing: 0.5px; - margin-bottom: 8px; - a { - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - .time { - padding-left: 5px; - } -} - -@media #{$mobile-only} { - .meta { - font-size: 14px; - margin-bottom: 10px; - .time { - padding: 0; - float: right; - } - } -} - -.meta-collapse { - margin-bottom: 20px; -} - -.deleted-meta { - font-size: 12px; - font-weight: bold; - letter-spacing: 0.5px; - margin: 30px 0; - a { - text-decoration: none; - } -} - -.collapse { - font-size: 13px; - letter-spacing: 2px; - cursor: pointer; -} - -.comment-tree { - margin-left: 24px; -} - -@media #{$tablet-only} { - .comment-tree { - margin-left: 8px; - } -} - -.comment-text { - font-size: 15px; - margin-top: 0; - margin-bottom: 20px; - word-wrap: break-word; - line-height: 1.5em; -} - -.subtree { - margin-left: 0; - padding: 0; - list-style-type: none; -} diff --git a/src/app/item-details/comment/comment.component.ts b/src/app/item-details/comment/comment.component.ts deleted file mode 100644 index c6b4da56c..000000000 --- a/src/app/item-details/comment/comment.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; - -import { Comment } from '../../shared/models/comment'; - -@Component({ - selector: 'app-comment', - templateUrl: './comment.component.html', - styleUrls: ['./comment.component.scss'] -}) -export class CommentComponent implements OnInit { - @Input() comment: Comment; - collapse: boolean; - - constructor() {} - - ngOnInit() { - this.collapse = false; - } -} diff --git a/src/app/item-details/item-details.component.html b/src/app/item-details/item-details.component.html deleted file mode 100644 index 0bf73ae0f..000000000 --- a/src/app/item-details/item-details.component.html +++ /dev/null @@ -1,59 +0,0 @@ -
- - - -
- -
-

- - {{item.title}} - - ({{item.domain}}) -

-

- - {{item.title}} - -

-
- - {{item.points}} points by - {{item.user}} - - - {{item.time_ago}} - | - - {{item.comments_count | comment }} - - - -
-
-
-
-
-
{{pollResult.points}} points
-
-
-
-

-
    -
  • - -
  • -
-
-
diff --git a/src/app/item-details/item-details.component.scss b/src/app/item-details/item-details.component.scss deleted file mode 100644 index adde0bd7c..000000000 --- a/src/app/item-details/item-details.component.scss +++ /dev/null @@ -1,151 +0,0 @@ -@import "../shared/scss/media"; -@import "../shared/scss/theme_variables"; - -.main-content { - position: relative; - width: 100%; - min-height: 100vh; - -webkit-transition: opacity .2s ease; - transition: opacity .2s ease; - box-sizing: border-box; - padding: 8px 0; - z-index: 0; -} - -.item { - box-sizing: border-box; - padding: 10px 40px 0 40px; - z-index: 0; -} - -@media #{$tablet-only} { - .item { - padding: 10px 20px 0 40px; - } -} - -@media #{$mobile-only} { - .item { - box-sizing: border-box; - padding: 110px 15px 0 15px; - } -} - -.head-margin { - margin-bottom: 15px; -} - -p { - margin: 2px 0; -} - -.subject { - word-wrap: break-word; - margin-top: 20px; -} - -a { - cursor: pointer; - text-decoration: none; -} - -@media #{$mobile-only} { - .laptop { - display: none; - } -} - -@media #{$laptop-only} { - .mobile { - display: none; - } -} - -.title { - font-size: 16px; - font-family: Verdana, Geneva, sans-serif; -} - -.title-block { - text-align: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - margin: 0 75px; -} - -@media #{$mobile-only} { - .title { - font-size: 15px; - } - .back-button { - position: absolute; - top: 52%; - width: 0.6rem; - height: 0.6rem; - background: transparent; - box-shadow: 0 0 0 lightgray; - transition: all 200ms ease; - left: 4%; - transform: translate3d(0, -50%, 0) rotate(-135deg); - } -} - -.subtext { - font-size: 12px; - font-weight: bold; - letter-spacing: 0.5px; -} - -.domain { - letter-spacing: 0.5px; -} - -.subtext a { - &:hover { - text-decoration: underline; - } -} - -.item-details { - padding: 10px; -} - -.item-header { - padding-bottom: 10px; -} - -@media #{$mobile-only} { - .item-header { - padding: 10px 0 10px 0; - position: fixed; - width: 100%; - left: 0; - top: 62px; - } -} - -.pollResults { - margin-bottom: 1em; -} - -.pollContent { - * { - padding-bottom: 0; - margin-bottom: -1em; - margin-top: 1em; - } - .pollBar { - height: 10px; - margin-bottom: 1em; - } -} - -ul { - list-style-type: none; - padding: 10px 0; -} - -li { - display: list-item; -} diff --git a/src/app/item-details/item-details.component.ts b/src/app/item-details/item-details.component.ts deleted file mode 100644 index 88e6199f1..000000000 --- a/src/app/item-details/item-details.component.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Location } from '@angular/common'; -import { Subscription } from 'rxjs/Subscription'; - -import { HackerNewsAPIService } from '../shared/services/hackernews-api.service'; -import { SettingsService } from '../shared/services/settings.service'; - -import { Story } from '../shared/models/story'; -import { Settings } from '../shared/models/settings'; - -@Component({ - selector: 'app-item-details', - templateUrl: './item-details.component.html', - styleUrls: ['./item-details.component.scss'] -}) -export class ItemDetailsComponent implements OnInit { - sub: Subscription; - item: Story; - errorMessage = ''; - settings: Settings; - - constructor( - private _hackerNewsAPIService: HackerNewsAPIService, - private _settingsService: SettingsService, - private route: ActivatedRoute, - private _location: Location - ) { - this.settings = this._settingsService.settings; - } - - ngOnInit() { - this.sub = this.route.params.subscribe(params => { - let itemID = +params['id']; - this._hackerNewsAPIService.fetchItemContent(itemID).subscribe(item => { - this.item = item; - }, error => this.errorMessage = 'Could not load item comments.'); - }); - window.scrollTo(0, 0); - } - - goBack() { - this._location.back(); - } - - get hasUrl(): boolean { - return this.item.url.indexOf('http') === 0; - } - -} diff --git a/src/app/item-details/item-details.module.ts b/src/app/item-details/item-details.module.ts deleted file mode 100644 index 31c982939..000000000 --- a/src/app/item-details/item-details.module.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { RouterModule, Routes } from '@angular/router'; -import { ItemDetailsComponent } from './item-details.component'; -import { CommentComponent } from './comment/comment.component'; -import { SharedComponentsModule } from '../shared/components/shared-components.module'; -import { PipesModule } from '../shared/pipes/pipes.module'; - - -const routes: Routes = [ - { - path: ':id', - component: ItemDetailsComponent - } -] - -@NgModule({ - imports: [ SharedComponentsModule, CommonModule, RouterModule, PipesModule, RouterModule.forChild(routes) ], - declarations: [ ItemDetailsComponent, CommentComponent ], - exports: [ ItemDetailsComponent, RouterModule] -}) -export class ItemDetailsModule {} diff --git a/src/app/shared/components/error-message/error-message.component.html b/src/app/shared/components/error-message/error-message.component.html deleted file mode 100644 index 3c8f9fb65..000000000 --- a/src/app/shared/components/error-message/error-message.component.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
-
-
-
-
-
-
-
-

{{ message }}

-

If you are offline viewing, you'll need to visit this page with a network connection first before it can work offline.

-
diff --git a/src/app/shared/components/error-message/error-message.component.scss b/src/app/shared/components/error-message/error-message.component.scss deleted file mode 100644 index eb5cc01e8..000000000 --- a/src/app/shared/components/error-message/error-message.component.scss +++ /dev/null @@ -1,115 +0,0 @@ -@import "../../scss/media"; -@import "../../scss/theme_variables"; - -.error-section { - height: 300px; - margin: 200px; - - @media #{$mobile-only} { - height: 0; - display: block; - position: relative; - margin: 30vh 0; - } - - p { - text-align: center; - padding: 0 25px; - - &.strong { - margin-top: 25px; - font-weight: bold; - } - } - - .skull { - width: $skull-size; - height: $skull-size; - position: relative; - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; - - .head { - width: 100%; - height: 75%; - border-radius: 15% / 20%; - position: absolute; - top: 0; - left: 0; - &:before, &:after { - content: ""; - position: absolute; - border-radius: 50%; - width: 20%; - height: 30%; - bottom: 10%; - } - &:before { - left: 10%; - } - &:after { - right: 10%; - } - .crack { - width: 10%; - height: 10%; - position: absolute; - top: 0; - right: 25%; - transform: skew(-15deg); - - &:before { - content: ""; - position: absolute; - top: 100%; - left: $skull-size / 15; - border-right: $skull-size / 20 solid transparent; - border-left: $skull-size / 40 solid transparent; - } - } - } - .mouth { - width: 40%; - height: 25%; - position: absolute; - top: 75%; - left: 30%; - border-radius: 0 0 $skull-size / 10 $skull-size / 10; - &:before { - content: ""; - position: absolute; - width: 15%; - height: 50%; - border-radius: 50% / 30%; - left: 42.5%; - top: -25%; - } - .teeth { - position: absolute; - bottom: 0; - left: 45%; - width: 10%; - height: 50%; - margin-bottom: -5%; - border-radius: 50% / 20%; - - &:before, &:after { - content: ""; - position: absolute; - width: 100%; - height: 100%; - border-radius: 50% / 20%; - } - &:before { - left: -250%; - } - &:after { - right: -250%; - } - } - } - } -} diff --git a/src/app/shared/components/error-message/error-message.component.ts b/src/app/shared/components/error-message/error-message.component.ts deleted file mode 100644 index cbd1219f3..000000000 --- a/src/app/shared/components/error-message/error-message.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-error-message', - templateUrl: './error-message.component.html', - styleUrls: ['./error-message.component.scss'] -}) -export class ErrorMessageComponent implements OnInit { - @Input() message: string; - - constructor() { } - - ngOnInit() { - } - -} diff --git a/src/app/shared/components/loader/loader.component.html b/src/app/shared/components/loader/loader.component.html deleted file mode 100644 index 0db7fc435..000000000 --- a/src/app/shared/components/loader/loader.component.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
- Loading... -
-
diff --git a/src/app/shared/components/loader/loader.component.scss b/src/app/shared/components/loader/loader.component.scss deleted file mode 100644 index 625d0fe8b..000000000 --- a/src/app/shared/components/loader/loader.component.scss +++ /dev/null @@ -1,109 +0,0 @@ -@import "../../scss/media"; -@import "../../scss/theme_variables"; - -.loader { - -webkit-animation: load1 1s infinite ease-in-out; - animation: load1 1s infinite ease-in-out; - width: 1em; - height: 4em; - &:before, &:after { - -webkit-animation: load1 1s infinite ease-in-out; - animation: load1 1s infinite ease-in-out; - width: 1em; - height: 4em; - } - &:before, &:after { - position: absolute; - top: 0; - content: ''; - } - &:before { - left: -1.5em; - -webkit-animation-delay: -0.32s; - animation-delay: -0.32s; - } -} - -.loading-section { - height: 70px; - margin: 40px 0 40px 40px; - - @media #{$mobile-only} { - display: block; - position: relative; - margin: 45vh 0; - } -} - -.loader { - text-indent: -9999em; - margin: 20px 20px; - position: relative; - font-size: 11px; - -webkit-transform: translateZ(0); - -ms-transform: translateZ(0); - transform: translateZ(0); - -webkit-animation-delay: -0.16s; - animation-delay: -0.16s; - &:after { - left: 1.5em; - } - - @media #{$mobile-only} { - margin: 20px auto; - } -} - -@-webkit-keyframes load1 { - 0%, - 80%, - 100% { - box-shadow: 0 0; - height: 2em; - } - 40% { - box-shadow: 0 -2em; - height: 3em; - } -} - -@keyframes load1 { - 0%, - 80%, - 100% { - box-shadow: 0 0; - height: 2em; - } - 40% { - box-shadow: 0 -2em; - height: 3em; - } -} - -@media #{$mobile-only} { - @-webkit-keyframes load1 { - 0%, - 80%, - 100% { - box-shadow: 0 0; - height: 4em; - } - 40% { - box-shadow: 0 -2em; - height: 5em; - } - } - - @keyframes load1 { - 0%, - 80%, - 100% { - box-shadow: 0 0; - height: 3em; - } - 40% { - box-shadow: 0 -2em; - height: 4em; - } - } -} diff --git a/src/app/shared/components/loader/loader.component.ts b/src/app/shared/components/loader/loader.component.ts deleted file mode 100644 index b1c2ed7c0..000000000 --- a/src/app/shared/components/loader/loader.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-loader', - templateUrl: './loader.component.html', - styleUrls: ['./loader.component.scss'] -}) -export class LoaderComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } - -} diff --git a/src/app/shared/components/shared-components.module.ts b/src/app/shared/components/shared-components.module.ts deleted file mode 100644 index 97577eef8..000000000 --- a/src/app/shared/components/shared-components.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { LoaderComponent } from './loader/loader.component'; -import { ErrorMessageComponent } from './error-message/error-message.component'; - -@NgModule({ - imports: [CommonModule], - declarations: [ LoaderComponent, ErrorMessageComponent ], - exports: [ LoaderComponent, ErrorMessageComponent ] -}) -export class SharedComponentsModule {} diff --git a/src/app/shared/models/comment.ts b/src/app/shared/models/comment.ts deleted file mode 100644 index afc289a2d..000000000 --- a/src/app/shared/models/comment.ts +++ /dev/null @@ -1,10 +0,0 @@ -export class Comment { - id: number; - level: number; - user: string; - time: number; - time_ago: string; - content: string; - deleted: boolean; - comments: Comment[]; -} diff --git a/src/app/shared/models/poll-result.ts b/src/app/shared/models/poll-result.ts deleted file mode 100644 index bd8977d46..000000000 --- a/src/app/shared/models/poll-result.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class PollResult { - points: number; - content: string; -} diff --git a/src/app/shared/models/settings.ts b/src/app/shared/models/settings.ts deleted file mode 100644 index 8a0f3ea15..000000000 --- a/src/app/shared/models/settings.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface Settings { - showSettings: boolean; - openLinkInNewTab: boolean; - theme: string; - titleFontSize: string; - listSpacing: string; -} diff --git a/src/app/shared/models/story.ts b/src/app/shared/models/story.ts deleted file mode 100644 index dab22e181..000000000 --- a/src/app/shared/models/story.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Comment } from './comment'; -import { FeedType } from './feed-type.type'; -import { PollResult } from './poll-result'; - -export class Story { - id: number; - title: string; - points: number; - user: string; - time: number; - time_ago: number; - type: FeedType; - url: string; - domain: string; - comments: Comment[]; - comments_count: number; - poll: PollResult[]; - poll_votes_count: number; - deleted: boolean; - dead: boolean; -} diff --git a/src/app/shared/models/user.ts b/src/app/shared/models/user.ts deleted file mode 100644 index 33f0a7aa9..000000000 --- a/src/app/shared/models/user.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class User { - id: string; - crated_time: number; - created: string; - karma: number; - avg: number; - about: string; -} diff --git a/src/app/shared/pipes/comment.pipe.ts b/src/app/shared/pipes/comment.pipe.ts deleted file mode 100644 index 42c91e4ec..000000000 --- a/src/app/shared/pipes/comment.pipe.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {Pipe, PipeTransform} from '@angular/core'; - -@Pipe({ - name: 'comment', - pure: true -}) -export class CommentPipe implements PipeTransform { - transform(comment: number): string { - if (comment > 0) { - let st = comment === 1 ? 'comment' : 'comments'; - return `${comment} ${st}`; - } - return 'discuss'; - } -} diff --git a/src/app/shared/pipes/pipes.module.ts b/src/app/shared/pipes/pipes.module.ts deleted file mode 100644 index 0720c237a..000000000 --- a/src/app/shared/pipes/pipes.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommentPipe } from './comment.pipe'; - -@NgModule({ - declarations: [CommentPipe], - exports: [CommentPipe] -}) -export class PipesModule {} diff --git a/src/app/shared/scss/_media.scss b/src/app/shared/scss/_media.scss deleted file mode 100644 index b04b71a92..000000000 --- a/src/app/shared/scss/_media.scss +++ /dev/null @@ -1,3 +0,0 @@ -$mobile-only: "only screen and (max-width : 768px)"; -$laptop-only: "only screen and (min-width : 769px)"; -$tablet-only: "only screen and (max-width : 1024px)"; diff --git a/src/app/shared/scss/_theme_variables.scss b/src/app/shared/scss/_theme_variables.scss deleted file mode 100644 index 41dae5eea..000000000 --- a/src/app/shared/scss/_theme_variables.scss +++ /dev/null @@ -1,37 +0,0 @@ -$skull-size: 200px; - -// ------------------------------------------------------------------------------------------- -// Theme Engine -// ------------------------------------------------------------------------------------------- - -// Day theme colors -$theme-day-body-background-color: #fff; -$theme-day-wrapper-background-color: #f5f5f5; -$theme-day-wrapper-mobile-background-color: #fff; -$theme-day-text-color: #000; -$theme-day-subtext-color: #696969; -$theme-day-secondary-color: #b92b27; -$theme-day-header-background-color: $theme-day-secondary-color; -$theme-day-logo-inner: #fff; - -// Day theme extras -$theme-day-border: 2px solid #b92b27; - -// Night theme colors -$theme-night-body-background-color: #37474F; -$theme-night-wrapper-background-color: #263238; -$theme-night-wrapper-mobile-background-color: $theme-night-wrapper-background-color; -$theme-night-text-color: rgba(255, 255, 255, 0.7); -$theme-night-subtext-color: #999; -$theme-night-secondary-color: #00c0ff; -$theme-night-header-background-color: #263238; -$theme-night-logo-inner: $theme-night-header-background-color; - -// Night theme extras -$theme-night-border: 2px solid #00c0ff; - -// Black theme colors -$theme-amoledblack-body-background-color: #000; -$theme-amoledblack-text-color: rgba(255, 255, 255, 0.75); -$theme-amoledblack-subtext-color: rgba(255, 255, 255, 0.5); -$theme-amoledblack-secondary-color: rgba(255, 255, 255, 0.6); diff --git a/src/app/shared/scss/_themes.scss b/src/app/shared/scss/_themes.scss deleted file mode 100644 index 231ed7cf9..000000000 --- a/src/app/shared/scss/_themes.scss +++ /dev/null @@ -1,245 +0,0 @@ -@import "./media"; -@import "./theme_variables"; - -/* ---------------------------------- - - Want a new theme? Add your new theme variables here! - ----------------------------------- */ - -@mixin theme( - $name, - $body-background-color, - $wrapper-background-color, - $wrapper-mobile-background-color, - $wrapper-color, - $item-a-color, - $item-a-visited-color, - $header-background-color, - $subtext-color, - $secondary-link-color, - $logo-inner-color, - $border -) { - .#{$name} { - .body-cover { - background: $body-background-color; - - @media #{$mobile-only} { - background: $wrapper-mobile-background-color; - } - } - - .wrapper { - background: $wrapper-background-color; - color: $wrapper-color; - - @media #{$mobile-only} { - background: $wrapper-mobile-background-color; - } - - a { - color: $item-a-color; - - &:visited { - color: $item-a-visited-color; - } - } - - #header { - background: $header-background-color; - border-bottom: $border; - } - - .logo-inner { - background: $logo-inner-color; - } - - .nav { - a { - color: $secondary-link-color; - - @media #{$mobile-only} { - color: $secondary-link-color; - } - } - } - - #footer { - border-top: $border; - - a { - color: $secondary-link-color; - } - } - - .subtext, .subtext-palm, .subtext-laptop, .domain, .meta, .deleted-meta{ - color: $subtext-color; - - a { - color: $secondary-link-color; - } - } - - .popup { - background: $header-background-color; - } - - .item-header { - border-bottom: $border; - - @media #{$mobile-only} { - background: $wrapper-mobile-background-color; - } - } - - .pollContent { - .pollBar { - background: $secondary-link-color; - } - } - - .loader { - color: $secondary-link-color; - background: $secondary-link-color; - - &:before, &:after { - background: $secondary-link-color; - } - } - - .job-header { - @media #{$mobile-only} { - border-bottom: $border; - } - } - - .back-button { - @media #{$mobile-only} { - border-top: .3rem solid $secondary-link-color; - border-right: .3rem solid $secondary-link-color; - } - } - - .error-section { - .skull { - .head { - background-color: $secondary-link-color; - - &:before, &:after { - background-color: $wrapper-background-color; - - @media #{$mobile-only} { - background-color: $wrapper-mobile-background-color; - } - } - - .crack { - background-color: $wrapper-background-color; - - @media #{$mobile-only} { - background-color: $wrapper-mobile-background-color; - } - - &:before { - border-top: $skull-size / 8 solid $wrapper-background-color; - - @media #{$mobile-only} { - border-top: $skull-size / 8 solid $wrapper-mobile-background-color; - } - } - } - } - - .mouth { - background-color: $secondary-link-color; - - &:before { - background-color: $wrapper-background-color; - - @media #{$mobile-only} { - background-color: $wrapper-mobile-background-color; - } - } - } - - .teeth { - background-color: $wrapper-background-color; - - @media #{$mobile-only} { - background-color: $wrapper-mobile-background-color; - } - - &:before, &:after { - background-color: $wrapper-background-color; - - @media #{$mobile-only} { - background-color: $wrapper-mobile-background-color; - } - } - } - } - } - - .main-details { - .name { - color: $secondary-link-color; - } - .right { - color: $secondary-link-color; - } - } - } - } -} - -@include theme( - default, - $theme-day-body-background-color, - $theme-day-wrapper-background-color, - $theme-day-wrapper-mobile-background-color, - $theme-day-text-color, - $theme-day-text-color, - $theme-day-subtext-color, - $theme-day-header-background-color, - $theme-day-subtext-color, - $theme-day-secondary-color, - $theme-day-logo-inner, - $theme-day-border -); - -@include theme( - night, - $theme-night-body-background-color, - $theme-night-wrapper-background-color, - $theme-night-wrapper-mobile-background-color, - $theme-night-text-color, - $theme-night-text-color, - $theme-night-subtext-color, - $theme-night-header-background-color, - $theme-night-subtext-color, - $theme-night-secondary-color, - $theme-night-logo-inner, - $theme-night-border -); - -@include theme( - amoledblack, - $theme-amoledblack-body-background-color, - $theme-amoledblack-body-background-color, - $theme-amoledblack-body-background-color, - $theme-amoledblack-text-color, - $theme-amoledblack-text-color, - darken($theme-amoledblack-text-color, 33%), - $theme-amoledblack-body-background-color, - $theme-amoledblack-subtext-color, - $theme-amoledblack-secondary-color, - $theme-amoledblack-body-background-color, - $theme-amoledblack-secondary-color -); - -/* ---------------------------------- - - Include your new theme here as well as the settings component - ----------------------------------- */ diff --git a/src/app/shared/services/hackernews-api.service.ts b/src/app/shared/services/hackernews-api.service.ts deleted file mode 100644 index e5c565364..000000000 --- a/src/app/shared/services/hackernews-api.service.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs/Observable'; -import fetch from 'unfetch'; -import {map } from 'rxjs/operators'; - -import { Story } from '../models/story'; -import { User } from '../models/user'; -import { PollResult } from '../models/poll-result'; - -// wrap fetch in observable so we can keep it chill -@Injectable() -export class HackerNewsAPIService { - baseUrl: string; - - constructor() { - this.baseUrl = 'https://node-hnapi.herokuapp.com'; - } - - fetchFeed(feedType: string, page: number): Observable { - return lazyFetch(`${this.baseUrl}/${feedType}?page=${page}`); - } - - fetchItemContent(id: number): Observable { - return lazyFetch(`${this.baseUrl}/item/${id}`).pipe(map((story: Story) => { - if (story.type === 'poll') { - let numberOfPollOptions = story.poll.length; - story.poll_votes_count = 0; - for (let i = 1; i <= numberOfPollOptions; i++) { - this.fetchPollContent(story.id + i).subscribe(pollResults => { - story.poll[i - 1] = pollResults; - story.poll_votes_count += pollResults.points; - }); - } - } - return story; - })); - } - - fetchPollContent(id: number): Observable { - return lazyFetch(`${this.baseUrl}/item/${id}`); - } - - fetchUser(id: string): Observable { - return lazyFetch(`${this.baseUrl}/user/${id}`); - } -} - -function lazyFetch(url, options?) { - return new Observable(fetchObserver => { - let cancelToken = false; - fetch(url, options) - .then(res => { - if (!cancelToken) { - return res.json() - .then(data => { - fetchObserver.next(data); - fetchObserver.complete(); - }); - } - }).catch(err => fetchObserver.error(err)); - return () => { - cancelToken = true; - }; - }); -} - diff --git a/src/app/shared/services/settings.service.ts b/src/app/shared/services/settings.service.ts deleted file mode 100644 index 0ad7ade52..000000000 --- a/src/app/shared/services/settings.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Injectable } from '@angular/core'; - -import { Settings } from '../models/settings'; - -@Injectable({ - providedIn: 'root' -}) -export class SettingsService { - settings: Settings = { - showSettings : false, - openLinkInNewTab: localStorage.getItem("openLinkInNewTab") ? JSON.parse(localStorage.getItem("openLinkInNewTab")) : false, - theme: 'default', - titleFontSize: localStorage.getItem("titleFontSize") ? localStorage.getItem("titleFontSize") : '16', - listSpacing: localStorage.getItem("listSpacing") ? localStorage.getItem("listSpacing") : '0', - }; - - darkColorSchemeMedia = window.matchMedia('(prefers-color-scheme: dark)'); - - constructor() { - this.subscribeToSystemPreferredColorScheme(); - this.initTheme(); - } - - ngOnDestroy() { - this.unSubscribeToSystemPrefferedColorScheme(); - } - - handleSystemPreferredColorSchemeChange(event: MediaQueryListEvent) { - let theme; - if (event.matches) { - theme = 'night'; - } else { - theme = 'default'; - } - this.setTheme(theme); - } - - subscribeToSystemPreferredColorScheme() { - this.darkColorSchemeMedia.addEventListener( - 'change', - this.handleSystemPreferredColorSchemeChange.bind(this) - ); - } - - initTheme() { - const savedTheme = localStorage.getItem("theme"); - if (savedTheme) { - this.settings.theme = savedTheme; - } else { - this.darkColorSchemeMedia.dispatchEvent( - new MediaQueryListEvent('change', { - media: this.darkColorSchemeMedia.media, - matches: this.darkColorSchemeMedia.matches - }) - ); - } - } - - unSubscribeToSystemPrefferedColorScheme() { - this.darkColorSchemeMedia.removeEventListener( - 'change', - this.handleSystemPreferredColorSchemeChange.bind(this) - ); - } - - toggleSettings() { - this.settings.showSettings = !this.settings.showSettings; - } - - toggleOpenLinksInNewTab() { - this.settings.openLinkInNewTab = !this.settings.openLinkInNewTab; - localStorage.setItem("openLinkInNewTab", JSON.stringify(this.settings.openLinkInNewTab)); - } - - setTheme(theme) { - this.settings.theme = theme; - localStorage.setItem("theme", this.settings.theme); - } - - setFont(fontSize){ - this.settings.titleFontSize = fontSize; - localStorage.setItem("titleFontSize", this.settings.titleFontSize); - } - - setSpacing(listSpace){ - this.settings.listSpacing = listSpace; - localStorage.setItem("listSpacing", this.settings.listSpacing); - } -} diff --git a/src/app/user/user.component.html b/src/app/user/user.component.html deleted file mode 100644 index bd6f0493d..000000000 --- a/src/app/user/user.component.html +++ /dev/null @@ -1,19 +0,0 @@ - - - -
-
-

- - Profile: {{ user.id }} -

-
-
- {{ user.id }} - {{ user.karma }} ★ -

Created {{ user.created }}

-
-
-

-
-
diff --git a/src/app/user/user.component.scss b/src/app/user/user.component.scss deleted file mode 100644 index c5fc9af57..000000000 --- a/src/app/user/user.component.scss +++ /dev/null @@ -1,89 +0,0 @@ -@import "../shared/scss/media"; -@import "../shared/scss/theme_variables"; - -:host >>> pre { - white-space: pre-wrap; -} - -.profile { - padding: 30px; -} - -@media #{$mobile-only} { - .profile { - padding: 110px 15px 0 15px; - } - .title-block { - font-size: 15px; - text-align: center; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - margin: 0 75px; - } - .back-button { - position: absolute; - top: 52%; - width: 0.6rem; - height: 0.6rem; - background: transparent; - box-shadow: 0 0 0 lightgray; - transition: all 200ms ease; - left: 4%; - transform: translate3d(0, -50%, 0) rotate(-135deg); - } - .item-header { - padding-bottom: 10px; - background-color: #fff; - padding: 10px 0 10px 0; - position: fixed; - width: 100%; - left: 0; - top: 62px; - height: 20px; - } -} - -@media #{$laptop-only} { - .mobile { - display: none; - } -} - -.main-details { - .name { - font-weight: bold; - font-size: 32px; - letter-spacing: 2px; - } - .age { - font-weight: bold; - color: #696969; - padding-bottom: 0; - } - .right { - float: right; - font-weight: bold; - font-size: 32px; - letter-spacing: 2px; - } -} - -@media #{$mobile-only} { - .main-details { - margin-top: 20px; - .name { - font-size: 18px; - } - } -} - -@media #{$mobile-only} { - .main-details .right { - font-size: 18px; - } -} - -.other-details { - word-wrap: break-word; -} diff --git a/src/app/user/user.component.ts b/src/app/user/user.component.ts deleted file mode 100644 index 28e90a4b0..000000000 --- a/src/app/user/user.component.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Location } from '@angular/common'; -import { Subscription } from 'rxjs/Subscription'; - -import { HackerNewsAPIService } from '../shared/services/hackernews-api.service'; -import { User } from '../shared/models/user'; - -@Component({ - selector: 'app-user', - templateUrl: './user.component.html', - styleUrls: ['./user.component.scss'] -}) -export class UserComponent implements OnInit { - sub: Subscription; - user: User; - errorMessage = ''; - - constructor( - private _hackerNewsAPIService: HackerNewsAPIService, - private route: ActivatedRoute, - private _location: Location - ) {} - - ngOnInit() { - this.sub = this.route.params.subscribe(params => { - let userID = params['id']; - this._hackerNewsAPIService.fetchUser(userID).subscribe(data => { - this.user = data; - }, error => this.errorMessage = 'Could not load user ' + userID + '.'); - }); - } - - goBack() { - this._location.back(); - } -} diff --git a/src/app/user/user.module.ts b/src/app/user/user.module.ts deleted file mode 100644 index 79615f52e..000000000 --- a/src/app/user/user.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { Routes, RouterModule } from '@angular/router'; -import { UserComponent } from './user.component'; -import { SharedComponentsModule } from '../shared/components/shared-components.module'; - - -const routes: Routes = [ - { - path: ':id', - component: UserComponent - } -] - - -@NgModule({ - imports: [CommonModule, SharedComponentsModule, RouterModule.forChild(routes)], - declarations: [UserComponent], - exports: [UserComponent, RouterModule] -}) -export class UserModule {} diff --git a/web/src/components/ErrorMessage.test.tsx b/src/components/ErrorMessage.test.tsx similarity index 100% rename from web/src/components/ErrorMessage.test.tsx rename to src/components/ErrorMessage.test.tsx diff --git a/web/src/components/ErrorMessage.tsx b/src/components/ErrorMessage.tsx similarity index 100% rename from web/src/components/ErrorMessage.tsx rename to src/components/ErrorMessage.tsx diff --git a/web/src/components/Loader.test.tsx b/src/components/Loader.test.tsx similarity index 100% rename from web/src/components/Loader.test.tsx rename to src/components/Loader.test.tsx diff --git a/web/src/components/Loader.tsx b/src/components/Loader.tsx similarity index 100% rename from web/src/components/Loader.tsx rename to src/components/Loader.tsx diff --git a/web/src/components/errorMessage.scss b/src/components/errorMessage.scss similarity index 100% rename from web/src/components/errorMessage.scss rename to src/components/errorMessage.scss diff --git a/web/src/components/loader.scss b/src/components/loader.scss similarity index 100% rename from web/src/components/loader.scss rename to src/components/loader.scss diff --git a/web/src/context/SettingsContext.test.tsx b/src/context/SettingsContext.test.tsx similarity index 100% rename from web/src/context/SettingsContext.test.tsx rename to src/context/SettingsContext.test.tsx diff --git a/web/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx similarity index 100% rename from web/src/context/SettingsContext.tsx rename to src/context/SettingsContext.tsx diff --git a/src/core/Footer.test.tsx b/src/core/Footer.test.tsx new file mode 100644 index 000000000..91e7c892e --- /dev/null +++ b/src/core/Footer.test.tsx @@ -0,0 +1,17 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { Footer } from './Footer'; + +describe('Footer', () => { + it('links to the project on GitHub', () => { + const { container } = render(