diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a60f93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +build/ +*.zip +*.crx +*.pem + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Temporary folders +tmp/ +temp/ + +# Chrome extension specific +*.crx +*.pem +*.zip \ No newline at end of file diff --git a/MIGRATION_SUMMARY.md b/MIGRATION_SUMMARY.md new file mode 100644 index 0000000..393ab24 --- /dev/null +++ b/MIGRATION_SUMMARY.md @@ -0,0 +1,91 @@ +# Миграция на API - Резюме изменений + +## Обзор + +Плагин Reckue Languages полностью переведен с local storage на API reckue.com. Все словари и настройки теперь управляются через API. + +## Удаленные файлы + +### Локальные компоненты словарей +- `src/core/Store.js` - Класс для работы с chrome.storage.local +- `src/core/words/WordbookService.js` - Локальная реализация сервиса словарей +- `src/core/words/Wordbook.js` - Локальная реализация словаря +- `src/core/words/Pages.js` - Класс для пагинации в локальном режиме +- `src/popup/deprecated/wordbook.js` - Устаревший файл + +## Новые файлы + +### API компоненты +- `src/core/api/ApiWordbook.js` - Класс для работы со словарями в API режиме +- `src/core/api/ApiSettingsService.js` - Сервис для работы с настройками через API + +## Измененные файлы + +### Основные компоненты +- `src/core/App.js` - Убрана fallback логика на локальный режим +- `src/core/api/WordbookServiceFactory.js` - Убрана поддержка локального режима +- `src/core/api/ApiWordbookAdapter.js` - Обновлен для использования ApiWordbook +- `src/core/api/ApiService.js` - Убрано использование local storage для tempId + +### Сервисы +- `src/page/PageService.js` - Использует ApiSettingsService вместо Store +- `src/popup/settings/SettingsService.js` - Использует ApiSettingsService вместо chrome.storage.local + +### Конфигурация +- `background/application.js` - Убрана инициализация local storage +- `src/core/api/index.js` - Обновлен экспорт для новых компонентов +- `src/core/api/README.md` - Обновлена документация + +## Ключевые изменения + +### 1. Авторизация +- TempId создается заново при каждом вызове tempAuth() +- TempId не сохраняется между сессиями +- Авторизация действительна только для текущей сессии + +### 2. Настройки +- Настройки больше не сохраняются в local storage +- Используется ApiSettingsService для управления настройками +- Дефолтные настройки применяются при ошибках API + +### 3. Словари +- Все словари загружаются из API +- Локальный кэш используется только для текущей сессии +- Автоматическое создание основного словаря при необходимости + +### 4. Обработка ошибок +- Убран fallback на локальный режим +- При ошибках API приложение показывает ошибку вместо переключения на local storage + +## Совместимость + +Все существующие интерфейсы сохранены для обеспечения совместимости: +- `getWordbook()` +- `getWordbookCache()` +- `getFilteredWordbook()` +- `set()`, `remove()` +- `loadWordbooks()` + +## Преимущества + +1. **Централизованное хранение** - Все данные на сервере +2. **Синхронизация** - Данные доступны на всех устройствах +3. **Безопасность** - Данные защищены на сервере +4. **Масштабируемость** - Легко добавлять новые функции +5. **Аналитика** - Возможность анализировать использование + +## Недостатки + +1. **Зависимость от интернета** - Требуется подключение к API +2. **Задержки** - Запросы к API медленнее локального хранения +3. **Ограничения API** - Зависимость от доступности сервера + +## Тестирование + +Рекомендуется протестировать: +1. Инициализацию приложения +2. Загрузку словарей +3. Добавление/удаление слов +4. Изменение настроек +5. Работу при отсутствии интернета +6. Обработку ошибок API diff --git a/background/application.js b/background/application.js index b62aefb..ec6e288 100644 --- a/background/application.js +++ b/background/application.js @@ -1 +1,3 @@ -chrome.storage.local.set({enable: true, russian: true, english: true, china: false, korean: true}); \ No newline at end of file +// Background script - инициализация API режима +// Настройки теперь управляются через API +console.log('Reckue Languages extension started in API mode'); \ No newline at end of file diff --git a/manifest.json b/manifest.json index 3424466..bca87e0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "Reckue Languages", - "version": "0.5.2", + "version": "0.6.0", "description": "Interactive language learning assistant", "permissions": ["storage", "activeTab", "tabs"], "background": { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a3aa307 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2428 @@ +{ + "name": "reckue-lang", + "version": "0.6.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reckue-lang", + "version": "0.6.0", + "license": "ISC", + "dependencies": { + "franc": "^6.2.0" + }, + "devDependencies": { + "apply-loader": "^2.0.0", + "pug": "^2.0.4", + "pug-loader": "^2.4.0", + "webpack": "^5.70.0", + "webpack-cli": "^4.9.2" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", + "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.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/babel-types": { + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.16.tgz", + "integrity": "sha512-5QXs9GBFTNTmilLlWBhnsprqpjfrotyrnzUdwDrywEL/DA4LuCWQT300BTOXA3Y9ngT9F2uvmCoIxI6z8DlJEA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babylon": { + "version": "6.16.9", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.9.tgz", + "integrity": "sha512-sEKyxMVEowhcr8WLfN0jJYe4gS4Z9KC2DGz0vqfC7+MXFbmvOF7jSjALC77thvAO2TLgFUPa9vDeOak+AcUrZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha512-uWttZCk96+7itPxK8xCzY86PnxKTMrReKDqrHzv42VQY0K30PUO8WY13WMOuI+cOdX4EIdzdvQ8k6jkuGRFMYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^4.0.4" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/align-text/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/apply-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/apply-loader/-/apply-loader-2.0.0.tgz", + "integrity": "sha512-6DYvIuO7d5z6xixh3ZYt8bRiH+IZvqROYGrESFEGrOatfpk1uDq0d5tH7lxhzhnbFbLH9XjIOhXcTHiUgIBl8g==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "license": "MIT", + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-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/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001524", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", + "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/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/constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.503", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", + "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", + "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/franc": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/franc/-/franc-6.2.0.tgz", + "integrity": "sha512-rcAewP7PSHvjq7Kgd7dhj82zE071kX5B4W1M4ewYMf/P+i6YsDQmj62Xz3VQm9zyUzUXwhIde/wHLGCMrM+yGg==", + "license": "MIT", + "dependencies": { + "trigram-utils": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha512-vyMeQMq+AiH5uUnoBfMTwf18tO3bM6k1QXBE9D6ueAAquEfCZe3AJPtud9g6qS0+4X8xA7ndpZiDyeb2l2qOBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/n-gram": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", + "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/pug": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", + "integrity": "sha512-XhoaDlvi6NIzL49nu094R2NA6P37ijtgMDuWE+ofekDChvfKnzFal60bhSdiy8y2PBO6fmz3oMEIcfpBVRUdvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-code-gen": "^2.0.2", + "pug-filters": "^3.1.1", + "pug-lexer": "^4.1.0", + "pug-linker": "^3.0.6", + "pug-load": "^2.0.12", + "pug-parser": "^5.0.1", + "pug-runtime": "^2.0.5", + "pug-strip-comments": "^1.0.4" + } + }, + "node_modules/pug-attrs": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.4.tgz", + "integrity": "sha512-TaZ4Z2TWUPDJcV3wjU3RtUXMrd3kM4Wzjbe3EWnSsZPsJ3LDI0F3yCnf2/W7PPFF+edUFQ0HgDL1IoxSz5K8EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.5" + } + }, + "node_modules/pug-code-gen": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.3.tgz", + "integrity": "sha512-r9sezXdDuZJfW9J91TN/2LFbiqDhmltTFmGpHTsGdrNGp3p4SxAjjXEfnuK2e4ywYsRIVP0NeLbSAMHUcaX1EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^3.1.2", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.4", + "pug-error": "^1.3.3", + "pug-runtime": "^2.0.5", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "node_modules/pug-error": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.3.tgz", + "integrity": "sha512-qE3YhESP2mRAWMFJgKdtT5D7ckThRScXRwkfo+Erqga7dyJdY3ZquspprMCj/9sJ2ijm5hXFWQE/A3l4poMWiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.1.tgz", + "integrity": "sha512-lFfjNyGEyVWC4BwX0WyvkoWLapI5xHSM3xZJFUhx4JM4XyyRdO8Aucc6pCygnqV2uSgJFaJWW3Ft1wCWSoQkQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + } + }, + "node_modules/pug-lexer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.1.0.tgz", + "integrity": "sha512-i55yzEBtjm0mlplW4LoANq7k3S8gDdfC6+LThGEvsK4FuobcKfDAwt6V4jKPH9RtiE3a2Akfg5UpafZ1OksaPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.3" + } + }, + "node_modules/pug-linker": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.6.tgz", + "integrity": "sha512-bagfuHttfQOpANGy1Y6NJ+0mNb7dD2MswFG2ZKj22s8g0wVsojpRlqveEQHmgXXcfROB2RT6oqbPYr9EN2ZWzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^1.3.3", + "pug-walk": "^1.1.8" + } + }, + "node_modules/pug-load": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.12.tgz", + "integrity": "sha512-UqpgGpyyXRYgJs/X60sE6SIf8UBsmcHYKNaOccyVLEuT6OPBIMo6xMPhoJnqtB3Q3BbO4Z3Bjz5qDsUWh4rXsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.8" + } + }, + "node_modules/pug-loader": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/pug-loader/-/pug-loader-2.4.0.tgz", + "integrity": "sha512-cD4bU2wmkZ1EEVyu0IfKOsh1F26KPva5oglO1Doc3knx8VpBIXmFHw16k9sITYIjQMCnRv1vb4vfQgy7VdR6eg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "pug-walk": "^1.0.0", + "resolve": "^1.1.7" + }, + "peerDependencies": { + "pug": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.1.tgz", + "integrity": "sha512-nGHqK+w07p5/PsPIyzkTQfzlYfuqoiGjaoqHv1LjOv2ZLXmGX1O+4Vcvps+P4LhxZ3drYSljjq4b+Naid126wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^1.3.3", + "token-stream": "0.0.1" + } + }, + "node_modules/pug-runtime": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.5.tgz", + "integrity": "sha512-P+rXKn9un4fQY77wtpcuFyvFaBww7/91f3jHa154qU26qFAnOe6SW1CbIDcxiG5lLK9HazYrMCCuDvNgDQNptw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.4.tgz", + "integrity": "sha512-i5j/9CS4yFhSxHp5iKPHwigaig/VV9g+FgReLJWWHEHbvKsbqL0oP/K5ubuLco6Wu3Kan5p7u7qk8A4oLLh6vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^1.3.3" + } + }, + "node_modules/pug-walk": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.8.tgz", + "integrity": "sha512-GMu3M5nUL3fju4/egXwZO0XLi6fW/K3T3VTgFQ14GxNi8btlxgT5qZL//JwZFm/2Fa64J/PNS8AZeys3wiMkVA==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "align-text": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-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/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/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/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha512-nfjOAu/zAWmX9tgwi5NRp7O7zTDUD1miHiB40klUnAh9qnL1iXdgzcz/i5dMaL5jahcBAaSfmNOBBJBLJW8TEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/trigram-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/trigram-utils/-/trigram-utils-2.0.1.tgz", + "integrity": "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==", + "license": "MIT", + "dependencies": { + "collapse-white-space": "^2.0.0", + "n-gram": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "source-map": "~0.5.1", + "yargs": "~3.10.0" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + }, + "optionalDependencies": { + "uglify-to-browserify": "~1.0.0" + } + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha512-uAnSsFGfSpF6DNhBXStvlZILfHJfJu4eUkfbRGk94kGO1Ta7bg6FwfvoOhhyHAJuFbCw+0xk4uJ3u57jLvlCJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + } + }, + "node_modules/with/node_modules/acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", + "dev": true, + "license": "MIT/X11", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } +} diff --git a/package.json b/package.json index f7c41c2..95742de 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "reckue-lang", - "version": "0.5.1", + "version": "0.6.0", "description": "It's your vocabulary-book built-in any pages you visit.", "main": "index.js", "scripts": { - "build": "D:/Projs/Reckue_Language/reckue-langs/node_modules/.bin/webpack" + "build": "webpack" }, "repository": { "type": "git", @@ -17,11 +17,13 @@ }, "homepage": "https://github.com/Reckue/terms-plugin#readme", "devDependencies": { - "pug": "^3.0.2", - - "pug-loader": "^2.4.0", "apply-loader": "^2.0.0", + "pug": "^2.0.4", + "pug-loader": "^2.4.0", "webpack": "^5.70.0", "webpack-cli": "^4.9.2" + }, + "dependencies": { + "franc": "^6.2.0" } } diff --git a/page/page.js b/page/page.js new file mode 100644 index 0000000..f18af6a --- /dev/null +++ b/page/page.js @@ -0,0 +1 @@ +(()=>{var a={290:(a,e,i)=>{var n=i(395);a.exports=(n.default||n).apply(n,[])},313:(a,e,i)=>{var n=i(196);a.exports=(n.default||n).apply(n,[])},32:(a,e,i)=>{var n=i(299);a.exports=(n.default||n).apply(n,[])},395:(a,e,i)=>{i(766),a.exports=function(a){return""+''}},265:(a,e,i)=>{var n=i(766);a.exports=function(a){var e,i="",o=a||{};return function(a,o){i=i+'
+'+n.escape(null==(e=a)?"":e)+'-
'}.call(this,"number"in o?o.number:"undefined"!=typeof number?number:void 0,"width"in o?o.width:"undefined"!=typeof width?width:void 0),i}},196:(a,e,i)=>{i(766),a.exports=function(a){return""+'
'}},91:(a,e,i)=>{var n=i(766);a.exports=function(a){var e,i="",o=a||{};return function(a,o){i=i+'
"+n.escape(null==(e=o)?"":e)+"\x3c!--.edit-button EDIT--\x3e
"}.call(this,"href"in o?o.href:"undefined"!=typeof href?href:void 0,"word"in o?o.word:"undefined"!=typeof word?word:void 0),i}},299:(a,e,i)=>{i(766),a.exports=function(a){return""+""}},766:(a,e,i)=>{"use strict";var n=Object.prototype.hasOwnProperty;function o(a,e){return Array.isArray(a)?function(a,e){for(var i,n="",t="",u=Array.isArray(e),s=0;s]/;function l(a){var e=""+a,i=s.exec(e);if(!i)return a;var n,o,t,u="";for(n=i.index,o=0;n ":" ")+i+"| "+a}).join("\n"),e.path=n,e.message=(n||"Pug")+":"+o+"\n"+u+"\n\n"+e.message,e}},365:()=>{}},e={};function i(n){var o=e[n];if(void 0!==o)return o.exports;var t=e[n]={exports:{}};return a[n](t,t.exports,i),t.exports}(()=>{"use strict";class a{static BASE_URL="https://api.reckue.com";static API_VERSION="/api/1";static AUTH_TEMP_IN="/auth/tempin";static AUTH_WHOAMI="/auth/whoami";static WORDBOOKS="/wordbooks";static WORDBOOKS_MAIN="/wordbooks/main";static WORDBOOKS_LANGUAGE="/wordbooks/language";static WORDS="/words";static WORDBOOK_WORDS="/wordbook/words";static WORDBOOK_WORDS_LEVELS="/wordbook/words/levels";static TEXTS="/texts";static USERS_CHECK="/users/check";static TEMP_USERS="/temp-users";static TEMP_USERS_CREATE="/temp-users/create";static getFullUrl(a){return`${this.BASE_URL}${this.API_VERSION}${a}`}static getHeaders(a=null){const e={"Content-Type":"application/json"};return a&&(e.Authorization=`Bearer ${a}`),e}}class e{log=a=>{window.console.log("Reckue language app: "+a)};#a=a=>{window.console.log(a.textContent),window.console.log(a.toString()),window.console.log(a.parentNode.toString()),window.console.log(a.parentNode.nodeName),window.console.log(a.parentNode.role)}}class n{#e;#i;#n;constructor(){this.#e=new e,this.#i=null,this.#n=null}async tempAuth(){try{let e=await this.#o();e||(e=await this.#t(),await this.#u(e));const i=await fetch(a.getFullUrl(a.AUTH_TEMP_IN),{method:"POST",headers:a.getHeaders(),body:JSON.stringify({tempId:e})});if(!i.ok)throw new Error(`Temp auth failed: ${i.status}`);const n=await i.json();return this.#i=n.token,this.#n=n.userId,this.#e.log("Temp auth successful"),n}catch(a){throw this.#e.log(`Temp auth error: ${a.message}`),a}}async#t(){try{const e=await fetch(a.getFullUrl(a.TEMP_USERS_CREATE),{method:"POST",headers:a.getHeaders()});if(!e.ok)throw new Error(`Create temp user failed: ${e.status}`);const i=await e.text();return this.#e.log(`Temp user created with ID: ${i}`),i}catch(a){throw this.#e.log(`Create temp user error: ${a.message}`),a}}async#u(a){return new Promise(e=>{chrome.storage.local.set({tempUserId:a},()=>{this.#e.log(`Temp ID stored: ${a}`),e()})})}async#o(){return new Promise(a=>{chrome.storage.local.get(["tempUserId"],e=>{const i=e.tempUserId;i&&this.#e.log(`Retrieved stored temp ID: ${i}`),a(i)})})}async whoami(){if(!this.#i)throw new Error("No token available");try{const e=await fetch(a.getFullUrl(a.AUTH_WHOAMI),{method:"GET",headers:a.getHeaders(this.#i)});if(!e.ok)throw new Error(`Whoami failed: ${e.status}`);return await e.json()}catch(a){throw this.#e.log(`Whoami error: ${a.message}`),a}}async getWordbooks(){if(!this.#i)throw new Error("No token available");try{const e=await fetch(a.getFullUrl(a.WORDBOOKS),{method:"GET",headers:a.getHeaders(this.#i)});if(!e.ok)throw new Error(`Get wordbooks failed: ${e.status}`);return await e.json()}catch(a){throw this.#e.log(`Get wordbooks error: ${a.message}`),a}}async getMainWordbook(){if(!this.#i)throw new Error("No token available");try{const e=await fetch(a.getFullUrl(a.WORDBOOKS_MAIN),{method:"GET",headers:a.getHeaders(this.#i)});if(e.ok)return await e.json();if(500===e.status&&"Current user hasn't wordbooks"===(await e.json()).message){this.#e.log("User has no wordbooks, creating main wordbook..."),await this.#s(),this.#e.log("Retrying to get main wordbook...");const e=await fetch(a.getFullUrl(a.WORDBOOKS_MAIN),{method:"GET",headers:a.getHeaders(this.#i)});if(!e.ok)throw new Error(`Get main wordbook retry failed: ${e.status}`);return await e.json()}throw new Error(`Get main wordbook failed: ${e.status}`)}catch(a){throw this.#e.log(`Get main wordbook error: ${a.message}`),a}}async#s(){try{const e=await this.#l()||"English",i=await fetch(a.getFullUrl(a.WORDBOOKS),{method:"POST",headers:a.getHeaders(this.#i),body:JSON.stringify({language:e})});if(!i.ok)throw new Error(`Create main wordbook failed: ${i.status}`);const n=await i.json();return this.#e.log(`Main wordbook created with language: ${e}`),n}catch(a){throw this.#e.log(`Create main wordbook error: ${a.message}`),a}}async#l(){return"English"}async getWordbookWords(e,i=0,n=100,o=""){if(!this.#i)throw new Error("No token available");try{const t={limit:n,offset:i*n},u={alphabetFilter:"",startDateFilter:"",endDateFilter:"",levelsFilter:[]};o&&(u.alphabetFilter=o);const s=[{sortType:"word",asc:!0}],l=`${a.getFullUrl(a.WORDBOOK_WORDS)}/${e}`,r=new URLSearchParams({pageRequest:JSON.stringify(t),filterRequest:JSON.stringify(u)});this.#e.log(`Fetching words from: ${l}?${r}`);const k=await fetch(`${l}?${r}`,{method:"POST",headers:a.getHeaders(this.#i),body:JSON.stringify(s)});if(!k.ok)throw new Error(`Get wordbook words failed: ${k.status}`);const d=await k.json();return this.#e.log("API response for words:",d),d}catch(a){throw this.#e.log(`Get wordbook words error: ${a.message}`),a}}async addWord(e,i,n=1){if(!this.#i)throw new Error("No token available");try{const o=await fetch(a.getFullUrl(a.WORDBOOK_WORDS),{method:"POST",headers:a.getHeaders(this.#i),body:JSON.stringify({wordbookId:e,word:i,level:n})});if(!o.ok)throw new Error(`Add word failed: ${o.status}`);return await o.json()}catch(a){throw this.#e.log(`Add word error: ${a.message}`),a}}async updateWordLevel(e,i,n){if(!this.#i)throw new Error("No token available");try{const o=await fetch(a.getFullUrl(a.WORDBOOK_WORDS_LEVELS),{method:"POST",headers:a.getHeaders(this.#i),body:JSON.stringify({wordbookId:e,wordId:i,level:n})});if(!o.ok)throw new Error(`Update word level failed: ${o.status}`);return await o.json()}catch(a){throw this.#e.log(`Update word level error: ${a.message}`),a}}getToken(){return this.#i}getTempUserId(){return this.#n}isAuthenticated(){return!!this.#i}async clearStoredTempId(){return new Promise(a=>{chrome.storage.local.remove(["tempUserId"],()=>{this.#e.log("Stored temp ID cleared"),a()})})}}class o{#r;#k;constructor(a=50){this.#r=new Map,this.#k=a}remove=a=>{this.#r.delete(a)};set=a=>(a.forEach(a=>{this.#r.set(a.word,a.level)}),this);get=()=>this.#r;getPage=a=>{const e=new Map,i=a*this.#k,n=i+this.#k;let o=0;return this.#r.forEach((a,t)=>{o>=i&&oMath.ceil(this.#r.size/this.#k);getPages=()=>({getCount:()=>this.getPagesCount()});getSize=()=>this.#r.size;clear=()=>{this.#r.clear()};has=a=>this.#r.has(a);getLevel=a=>this.#r.get(a)}class t{#d;#e;#m;#g;#h;#b=[];constructor(){this.#d=new n,this.#e=new e,this.#m=null,this.#g=new o}executeAfter=a=>{this.#h=a};set=async a=>{try{for(const e of a)await this.addWord(e.word,e.level)}catch(a){throw this.#e.log(`Set words failed: ${a.message}`),a}};remove=async a=>{if(!this.#m)throw new Error("No current wordbook available");try{this.#g.remove(a),this.#e.log(`Word removed: ${a}`)}catch(a){throw this.#e.log(`Remove word failed: ${a.message}`),a}};getWordbook=()=>this.#g;async initialize(){try{return await this.#d.tempAuth(),this.#e.log("API adapter initialized successfully"),!0}catch(a){return this.#e.log(`API adapter initialization failed: ${a.message}`),!1}}async loadMainWordbook(){try{const a=await this.#d.getMainWordbook();this.#m=a;try{const e=await this.loadWordbookWords(a.id);this.#e.log(`Successfully loaded ${e.length} words from wordbook`),0===e.length&&this.#e.log("Warning: No words loaded from wordbook")}catch(a){this.#e.log(`Failed to load words, but continuing: ${a.message}`),this.#g.set([])}return this.#e.log(`Main wordbook loaded: ${a.language}`),a}catch(a){throw this.#e.log(`Load main wordbook failed: ${a.message}`),a}}async loadWordbookWords(a,e=0,i=100){try{this.#e.log(`Loading words from wordbook ${a}, page ${e}, size ${i}`);const n=await this.#d.getWordbookWords(a,e,i);this.#e.log("Received words data:",n);let o=[];return n&&n.content&&Array.isArray(n.content)?o=n.content.map(a=>({word:a.word,level:a.level})):Array.isArray(n)?o=n.map(a=>({word:a.word,level:a.level})):(this.#e.log("Unexpected API response structure, using empty array"),o=[]),this.#g.set(o),this.#e.log(`Successfully loaded and cached ${o.length} words from wordbook ${a}`),o.length>0&&this.#y(),o}catch(a){return this.#e.log(`Load wordbook words failed: ${a.message}`),this.#g.set([]),[]}}async addWord(a,e=1){if(!this.#m)throw new Error("No current wordbook available");try{let i=1;if("string"==typeof e)switch(e){case"NATIVE":i=5;break;case"ADVANCED":i=4;break;case"INTERMEDIATE":i=3;break;case"ELEMENTARY":i=2;break;default:i=1}else i=e;return await this.#d.addWord(this.#m.id,a,i),this.#g.set([{word:a,level:e}]),this.#e.log(`Word added: ${a} (level ${e})`),!0}catch(a){throw this.#e.log(`Add word failed: ${a.message}`),a}}async updateWordLevel(a,e){if(!this.#m)throw new Error("No current wordbook available");try{return this.#g.set([{word:a,level:e}]),this.#e.log(`Word level updated: ${a} -> ${e}`),!0}catch(a){throw this.#e.log(`Update word level failed: ${a.message}`),a}}getWordbookCache(){return this.#g?this.#g.get():(this.#e.log("Wordbook cache not initialized, returning empty Map"),new Map)}getFilteredWordbook(a){const e=[];this.#g.get().forEach((i,n)=>{n.includes(a)&&e.push({word:n,level:i})});const i=new o;return i.set(e),i}loadWordbooks=()=>{this.#h&&this.#h()};findWordId(a){return null}getCurrentWordbook(){return this.#m}isInitialized(){return this.#d.isAuthenticated()&&this.#m&&this.#g&&this.#g.get().size>0}isWordbookReady(){return this.isInitialized()&&this.#g.get().size>0}onWordbookReady(a){"function"==typeof a&&(this.#b.push(a),this.isWordbookReady()&&(this.#e.log("Wordbook already ready, calling callback immediately"),setTimeout(()=>a(),0)))}#y(){this.#e.log(`Notifying ${this.#b.length} callbacks about wordbook ready`),this.#b.forEach(a=>{try{a()}catch(a){this.#e.log(`Error in wordbook ready callback: ${a.message}`)}})}}class u{#e;constructor(){this.#e=new e}createService(){return this.#e.log("Creating API-based wordbook service"),new t}}const s=new Map;class l{static add=(a,e)=>{s.set(a,e)};static get=a=>s.get(a);static getWordbookService=()=>{const a=l.get("wordbook");if(a)return a;throw console.error("WordbookService not found in context. Make sure App is initialized properly."),new Error("WordbookService not available")}}class r{#d;#e;#w;constructor(){this.#d=new n,this.#e=new e,this.#w={enable:!0,russian:!0,english:!0,china:!1,korean:!0}}async initialize(){try{return await this.#d.tempAuth(),await this.loadSettings(),this.#e.log("API settings service initialized successfully"),!0}catch(a){return this.#e.log(`API settings service initialization failed: ${a.message}`),!1}}async loadSettings(){try{return this.#e.log("Settings loaded from defaults"),this.#w}catch(a){return this.#e.log(`Load settings failed: ${a.message}`),this.#w}}async saveSettings(a){try{return this.#w={...this.#w,...a},this.#e.log("Settings saved"),!0}catch(a){throw this.#e.log(`Save settings failed: ${a.message}`),a}}async updateSetting(a,e){try{return this.#w[a]=e,await this.saveSettings({[a]:e}),this.#e.log(`Setting updated: ${a} = ${e}`),!0}catch(a){throw this.#e.log(`Update setting failed: ${a.message}`),a}}getSettings(){return this.#w}getSetting(a){return this.#w[a]}isEnabled(){return this.#w.enable}getLanguageSettings(){return{russian:this.#w.russian,english:this.#w.english,china:this.#w.china,korean:this.#w.korean}}}class k{toElement=a=>{const e=window.document.createElement("div");return e.innerHTML=a.trim(),e.firstChild}}class d{#p;constructor(){this.#p=new k}append=()=>{const a=i(32),e=this.#p.toElement(a);window.document.querySelector("head").appendChild(e)}}class m{#p;constructor(){this.#p=new k}getHTMLMapper=()=>this.#p}class g extends m{#c;constructor(a){super();const e=i(290);this.#c=this.getHTMLMapper().toElement(e),a.appendChild(this.#c)}setRef=a=>{this.#c=a};getRef=()=>this.#c}const h=Object.freeze({NATIVE:{name:"native",hex:"#2e8801",number:4},ADVANCED:{name:"advanced",hex:"#72d400",number:3},INTERMEDIATE:{name:"intermediate",hex:"#ef9f00",number:2},ELEMENTARY:{name:"elementary",hex:"#ab0000",number:1},BEGINNER:{name:"beginner",hex:"#ff2a00",number:0}});class b{onHover(a,e){const i=l.get("menu");a.addEventListener("click",a=>this.#v(a,i,e)),a.addEventListener("mouseout",i.displayOff)}#v=(a,e,i)=>{e.displayOn(),e.setPosition(a.clientX,a.clientY),e.setContent(i)};onHoverAll=a=>{l.get("refs").get(a).forEach(e=>{this.onHover(e,a)})};renderAll=(a,e)=>{l.get("refs").get(a).forEach(a=>{this.resolveColor(a,e)})};createRef=a=>{const e=document.createElement("a");return a=a.replace(/\r?\n/g,""),e.innerText=a,e.style.cursor="pointer",e};resolveColor=(a,e)=>{switch(e){case h.NATIVE.name:a.style.color=h.NATIVE.hex;break;case h.ADVANCED.name:a.style.color=h.ADVANCED.hex;break;case h.INTERMEDIATE.name:a.style.color=h.INTERMEDIATE.hex;break;case h.ELEMENTARY.name:a.style.color=h.ELEMENTARY.hex;break;case h.BEGINNER.name:a.style.color=h.BEGINNER.hex}}}class y extends g{#f;#j;#z;constructor(a,e){super(a),this.#f=l.getWordbookService(),this.#z=e,this.#j=new b}updateLevel=()=>{const a=this.#f.getWordbookCache().get(this.#z);this.#D(h[a.toUpperCase()].number)};setWord=a=>{this.#z=a};#E=a=>{const e=a+1;this.#F(e)};#q=a=>{if(a>0){const e=a-1;this.#F(e)}};#F=a=>{this.#x(a,(a,e)=>{this.#D(a),this.#j.renderAll(this.#z,e.name),this.#f.set([{word:this.#z,level:e.name}])})};#x=(a,e)=>{var i,n;i=h,n=i=>i.number===a&&e(a,i),Object.entries(i).forEach(a=>{const e=a[1];n(e)})};#D=a=>{const e=i(265),n=this.getHTMLMapper().toElement(e(this.#A(a)));this.#C(n),this.#B(a)};#C=a=>{this.getRef().replaceWith(a),this.setRef(a)};#B=a=>{const e=this.getRef().getElementsByClassName("wb-level-controller");e[0].addEventListener("click",()=>this.#E(a)),e[1].addEventListener("click",()=>this.#q(a))};#A=a=>({number:a,width:l.get("POPUP_WIDTH")+"px"})}class w extends g{#S;#W;constructor(a){super(a),this.#S=i(91),this.#W=a,this.#W.prepend(this.getRef())}updateLink=a=>{const e=this.#R(a),i=this.#S({word:a,href:e}),n=this.getHTMLMapper().toElement(i),o=this.getRef();this.setRef(n),this.#W.replaceChild(this.getRef(),o)};#R=a=>{const e=l.get("language"),i=`${l.get("TRANSLATE_URL")}&sl=${e.sl}&tl=${e.tl}&text=${a}`;return this.#T(i),i};#T=a=>{window.console.log(chrome.tabs),window.open(a,"_blank").close()}}class p{#c;#p;#I;#P;#O="0";#L="0";constructor(){this.#p=new k,l.add("POPUP_WIDTH",120),l.add("TRANSLATE_URL","https://translate.google.com/#view=home&op=translate"),this.#N()}setContent=a=>{this.#P.setWord(a),this.#P.updateLevel(),this.#I.updateLink(a),this.#$()};setPosition=(a,e)=>{const i=l.get("POPUP_WIDTH")/2;this.#O=a-i+"px",this.#L=`${e}px`,this.#M()};displayOn=()=>{this.#c.style.visibility="visible"};displayOff=()=>{this.#c.style.visibility="hidden"};#U=()=>{window.document.querySelector("body").appendChild(this.#c)};#N=()=>{const a=i(313);this.#c=this.#p.toElement(a),this.displayOff(),this.#H(),this.#I=new w(this.#c),this.#P=new y(this.#c),this.#U()};#M=()=>{this.#c.style.left=this.#O,this.#c.style.top=this.#L};#H(){this.#c.addEventListener("mouseover",()=>this.displayOn()),this.#c.addEventListener("mouseout",()=>this.displayOff())}#$=()=>{const a=this.#_(this.#I.getRef().offsetWidth),e=this.#_(l.get("POPUP_WIDTH"))-a;this.#I.getRef().style.left=`${e}px`};#_=a=>a/2}class c{#f;#z;#Q;constructor(a){this.#z=a,this.#Q=new b,this.#f=l.getWordbookService()}create=()=>{if(""===this.#z.get())return this.#G();const a=this.#Q.createRef(this.#z.get().trim()),e=this.#f.getWordbookCache().get(this.#z.getClear());return this.#V(e)?this.#K(a,e):this.#J(a)};#G=()=>document.createTextNode(this.#z.get());#K=(a,e)=>(this.#Q.onHover(a,this.#z.getClear()),this.#Q.resolveColor(a,e),a);#J=a=>(this.#Y(),a.addEventListener("click",this.#X,{once:!0}),a);#X=async()=>{try{const a=h.BEGINNER.name,e=this.#z.getClear();console.log("Attempting to save word:",e,"with level:",a),console.log("WordbookService:",this.#f),await this.#f.set([{word:e,level:a}]),console.log("Word saved successfully"),this.#Q.renderAll(e,a),this.#Q.onHoverAll(this.#z.getClear())}catch(a){console.error("Failed to save word:",a),alert(`Failed to save word: ${a.message}`)}};#Y=()=>" "!==this.#z.getClear()&&l.get("notSavedWords").add(this.#z.getClear());#V=a=>void 0!==a}class v{#e=new e;constructor(){l.add("menu",new p),l.add("notSavedWords",new Set),l.add("refs",new Map)}rebuildPage=()=>{this.#Z(a=>this.#aa(a.ref,a.words))};#Z=a=>{this.#e.log("Rebuilding page..."),l.get("render-queue").takeTurns(a),this.#e.log("Rebuilding page complete!"),this.#e.log(`Found not saved words - ${l.get("notSavedWords").size}`)};#aa=(a,e)=>{"A"!==a.parentNode.nodeName&&(e.forEach(e=>{const i=this.#ea(e);""!==i.textContent&&(this.#ia(e.getClear(),i),this.#na(i,a))}),a.textContent="")};#ia=(a,e)=>{const i=l.get("refs");let n=i.get(a);!n&&(n=[]),n.push(e),i.set(a,n)};#ea=a=>""!==a.get()?new c(a).create():this.#oa(" ");#na=(a,e)=>{e.after(a,e)};#oa=a=>document.createTextNode(a)}class f{#ta;constructor(){this.#ta=new MutationObserver(a=>this.#ua(a))}listen=a=>{this.#ta.observe(a,{childList:!0})};#ua=a=>{l.get("render-queue").isActive()||a.forEach(a=>this.#sa(a))};#sa=a=>{a.addedNodes.forEach(a=>{l.get("page-elements-queue").queueUp(a)})}}class j{#e;#la;#ra;#ka;constructor(){this.#e=new e,this.#la=new f,this.#ra=l.get("page-elements-queue"),this.#ka=l.get("text-elements-queue")}putInQueue=a=>{this.#ra.queueUp(a)};parse=()=>{this.#e.log("Started queued parser."),this.#ra.takeTurns(this.#da)};#ma=(a,e)=>{if("BODY"===a.nodeName){const i=(100*e/a.childNodes.length).toFixed(2);this.#e.log(`processed ${i}% of the page!`)}};#da=a=>{a.childNodes.forEach((e,i)=>{this.#ma(a,i),this.#ga(e)&&this.#ha(e)})};#ga=a=>!(this.#ba(a)||this.#ya(a)||this.#wa(a)||this.#pa(a)||this.#ca(a)||this.#va(a)||this.#fa(a)||this.#ja(a)||this.#za(a)||this.#Da(a));#ba=a=>a instanceof HTMLScriptElement;#ja=a=>a instanceof HTMLFormElement;#wa=a=>a instanceof HTMLImageElement;#pa=a=>a instanceof HTMLInputElement;#ca=a=>a instanceof HTMLLinkElement;#fa=a=>a instanceof HTMLStyleElement;#va=a=>a instanceof HTMLBRElement;#ya=a=>a instanceof SVGSVGElement;#za=a=>a instanceof Comment;#Da=a=>"CODE"===a.nodeName||"A"===a.nodeName;#ha=a=>{a.hasChildNodes()?(this.#la.listen(a),this.#da(a)):this.#ka.queueUp(a)}}class z{getRegex=()=>new RegExp(this.#Ea(this.#Fa()));#Ea=a=>`[^${a}]+`;#Fa=()=>""===this.#qa()?"\\w":this.#qa();#qa=()=>this.#xa()+this.#xa().toUpperCase()+this.#Aa()+this.#Aa().toUpperCase()+this.#Ca()+this.#Ba();#xa=()=>{const a=l.get("settings");return a&&a.russian?"йцукенгшщзхъфывапролджэячсмитьбю":""};#Aa=()=>{const a=l.get("settings");return a&&a.english?"yqwertuiopasdfghjklzxcvbnm":""};#Ca=()=>{const a=l.get("settings");return a&&a.korean?"ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅓㅗㅜㅡㅣㅑㅕㅛㅠㄲㄸㅃㅆㅉㄳㄵㄶㄺㄻㄼㄽㄾㄿㅀㅄㅐㅒㅔㅖㅘㅙㅚㅝㅞㅟㅢ":""};#Ba=()=>{const a=l.get("settings");return a&&a.china?"一丁丂七丄丅丆万丈三上下丌不与丏丐丑丒专且丕世丗丘丙业丛东丝丞丟丠両丢丣两严並丧丨丩个丫丬中丮丯丰丱串丳临丵丶丷丸丹为主丼丽举丿乀乁乂乃乄久乆乇么义乊之乌乍乎乏乐乑乒乓乔乕乪乫乬乭乮乯买乱乲乳乴乵乶乷乸乹乺乻乼乽乾乿亀亁亂亃亄亅了亇予争亊事二亍于亏亐云互亓五井亖亗亘亙亚些亜亝亞亟亠亡亢亣交亥亦产亨亩亪享京亭亮亯亰亱亲亳亴亵亶亷亸亹人亻亼亽亾亿什仁仂仃仄仅仆仇仈仉今介仌仍从仏仐仑仒仓仔仕他仗付仙仚仛仜仝仞仟仠仡仢代令以仦仧仨仩仪仫们仭仮仯仰仱仲仳仴仵件价仸仹仺任仼份仾仿伀企伂伃伄伅伆伇伈伉伊伋伌伍伎伏伐休伒伓伔伕伖众优伙会伛伜伝伞伟传伡伢伣伤伥伦伧伨伩伪伫伬伭伮伯估伱伲伳伴伵伶伷伸伹伺伻似伽伾伿佀佁佂佃佄佅但佇佈佉佊佋佌位低住佐佑佒体佔何佖佗佘余佚佛作佝佞佟你佡佢佣佤佥佦佧佨佩佪佫佬佭佮佯佰佱佲佳佴併佶佷佸佹佺佻佼佽佾使侀侁侂侃侄侅來侇侈侉侊例侌侍侎侏侐侑侒侓侔侕侖侗侘侙侚供侜依侞侟侠価侢侣侤侥侦侧侨侩侪侫侬侭侮侯侰侱侲侳侴侵侶侷侸侹侺侻侼侽侾便俀俁係促俄俅俆俇俈俉俊俋俌俍俎俏俐俑俒俓俔俕俖俗俘俙俚俛俜保俞俟俠信俢俣俤俥俦俧俨俩俪俫俬俭修俯俰俱俲俳俴俵俶俷俸俹俺俻俼俽俾俿倀倁倂倃倄倅倆倇倈倉倊個倌倍倎倏倐們倒倓倔倕倖倗倘候倚倛倜倝倞借倠倡倢倣値倥倦倧倨倩倪倬倭倮倯倰倱倲倳倴倵倶倷倸倹债倻值倽倾倿偀偁偂偃偄偅偆假偈偉偊偋偌偍偎偏偐偑偒偓偔偕偖偗偘偙做偛停偝偞偟偠偡偢偣偤健偦偧偨偩偪偫偬偭偮偯偰偱偲偳側偵偶偷偸偹偺偻偼偽偾偿傀傁傂傃傄傅傆傇傈傉傊傋傌傍傎傏傐傑傒傓傔傕傖傗傘備傚傛傜傝傞傟傠傡傢傣傤傥傦傧储傩傪傫催傭傮傯傰傱傲傳傴債傶傷傸傹傺傻傼傽傾傿僀僁僂僃僄僅僆僇僈僉僊僋僌働僎像僐僑僒僓僔僕僖僗僘僙僚僛僜僝僞僟僠僡僢僣僤僥僦僧僨僩僪僫僬僭僮僯僰僱僲僳僴僵僶僷僸價僺僻僼僽僾僿儀儁儂儃億儅儆儇儈儉儊儋儌儍儎儏儐儑儒儓儔儕儖儗儘儙儚儛儜儝儞償儠儡儢儣儤儥儦儧儨儩優儫儬儭儮儯儰儱儲儳儴儵儶儷儸儹儺儻儼儽儾儿兀允兂元兄充兆兇先光兊克兌免兎兏児兑兒兓兔兕兖兗兘兙党兛兜兝兞兟兠兡兢兣兤入兦內全兩兪八公六兮兯兰共兲关兴兵其具典兹兺养兼兽兾兿冀冁冂冃冄内円冇冈冉冊冋册再冎冏冐冑冒冓冔冕冖冗冘写冚军农冝冞冟冠冡冢冣冤冥冦冧冨冩冪冫冬冭冮冯冰冱冲决冴况冶冷冸冹冺冻冼冽冾冿净凁凂凃凄凅准凇凈凉凊凋凌凍凎减凐凑凒凓凔凕凖凗凘凙凚凛凜凝凞凟几凡凢凣凤凥処凧凨凩凪凫凬凭凮凯凰凱凲凳凴凵凶凷凸凹出击凼函凾凿刀刁刂刃刄刅分切刈刉刊刋刌刍刎刏乖乗乘乙乚乛乜九乞也习乡乢乣乤乥书乧乨乩":""}}class D{#Ea;#Sa;#Wa;constructor(a){this.#Ea=new z,this.#Sa=a,this.#Ra()}get=()=>this.#Sa;getClear=()=>this.#Wa;#Ra=()=>{this.#Wa=this.#Sa.toString().toLowerCase().replace(this.#Ea.getRegex(),""),this.#Ta(this.#Wa)||this.#Ia()};#Ia=()=>{let a=null;this.#Wa.endsWith("s")&&(a="s"),this.#Wa.endsWith("ed")&&(a="ed"),this.#Wa.endsWith("ing")&&(a="ing"),null!==a&&this.#Pa(a)};#Oa=()=>{this.#Wa.startsWith("un")&&this.#La("un")};#Pa=a=>{this.#Wa=this.#Wa.substr(0,this.#Wa.length-a.length),this.#Na(this.#Wa)};#La=a=>{this.#Wa=this.#Wa.substr(a.length-1)};#Na=a=>{this.#Ta(a)||(this.#Ta(a+"s")&&(this.#Wa=a+"s"),this.#Ta(a+"e")&&(this.#Wa=a+"e"))};#Ta=a=>{try{const e=l.getWordbookService();return!(!e||!e.isWordbookReady())&&e.getWordbookCache().get(a)}catch(a){return!1}}}class E{#ka;#$a;#Ma;constructor(){this.#Ma=l.getWordbookService().getWordbookCache(),this.#ka=l.get("text-elements-queue"),this.#$a=l.get("render-queue")}parse=()=>{this.#ka.takeTurns(a=>{const e=this.#Ua(a);this.#Ha(a,e)})};#Ua=a=>{const e=a.textContent.split(" ");return this.#_a(e)};#_a=a=>{const e=[];return a.forEach((a,i)=>{0!==i&&e.push(""),this.#Qa(e,a,"-")||this.#Qa(e,a,".")||this.#Qa(e,a,"...")||this.#Qa(e,a,",")||this.#Ga(e,a)}),e};#Ga=(a,e)=>{e.endsWith("n't")&&!e.includes("can't")?this.#Va(a,e,"n't","not"):e.endsWith("'ll")?this.#Va(a,e,"'ll","will"):a.push(e)};#Va=(a,e,i,n)=>{const o=this.#Ka(e,i);a.push(o,"",n)};#Ka=(a,e)=>{const i=this.#Ja(a,e);return this.#Ya(i)};#Ja=(a,e)=>a.substr(0,a.length-e.length);#Ya=a=>this.#Ta(a)?a:this.#Ta(a+"e")?a+"e":a;#Ta=a=>this.#Ma.get(a);#Qa=(a,e,i)=>{const n=e.split(i);if(2===n.length){const e=n[0],o=n[1];return a.push(e,i,o),!0}return!1};#Ha=(a,e)=>{const i=[];e.forEach(a=>i.push(new D(a))),this.#$a.queueUp({ref:a,words:i})}}class F{#Xa;#Za;constructor(){this.#Xa=new j,this.#Za=new E}textBlocksParsing=a=>this.#Za.parse(a);parsePage=()=>{this.#Xa.parse()};putInQueue=a=>{this.#Xa.putInQueue(a)}}class q{#ae;#ee;constructor(){this.#ae=!1,this.#ee=new Set}queueUp=a=>{this.#ee.add(a)};takeTurns=a=>{this.#ae=!0,this.#ee.forEach(e=>a(e)),this.#ee.clear(),this.#ae=!1};isEmpty=()=>0===this.#ee.size;isActive=()=>this.#ae}A(2);const x=A(3);function A(a){if("number"!=typeof a||Number.isNaN(a)||a<1||a===Number.POSITIVE_INFINITY)throw new Error("`"+a+"` is not a valid argument for `n-gram`");return function(e){const i=[];if(null==e)return i;const n="function"==typeof e.slice?e:String(e);let o=n.length-a+1;if(o<1)return i;for(;o--;)i[o]=n.slice(o,o+a);return i}}const C=/\s+/g,B=/[\t\n\v\f\r ]+/g;function S(a){const e=/\r?\n|\r/.exec(a);return e?e[0]:" "}function W(){return" "}const R={}.hasOwnProperty;function T(a){const e=function(a){return x(" "+function(a){return null==a?"":function(a,e){e?"string"==typeof e&&(e={style:e}):e={};const i=e.preserveLineEndings?S:W;return String(a).replace("html"===e.style?B:C,e.trim?function(a){return function(e,i,n){return 0===i||i+e.length===n.length?"":a(e)}}(i):i)}(String(a).replace(/[\u0021-\u0040]+/g," ")).trim().toLowerCase()}(a)+" ")}(a),i={};let n=-1;for(;++n{const a=setInterval(()=>{try{const e=l.getWordbookService();if(e&&e.isWordbookReady()){const i=e.getWordbookCache();this.#e.log(`Wordbook is ready with ${i.size} words`),this.#oe=!0,clearInterval(a),this.#se()}else if(e&&e.isInitialized()){const a=e.getWordbookCache();this.#e.log(`Wordbook initialized but cache has ${a.size} words, waiting for data...`)}else this.#e.log("Wordbook service not initialized, waiting...")}catch(a){this.#e.log(`Error checking wordbook ready: ${a.message}`)}},500);setTimeout(()=>{this.#oe||(this.#e.log("Wordbook ready timeout, starting with empty cache"),this.#oe=!0,clearInterval(a),this.#se())},1e4)};#ue=()=>{setTimeout(()=>{try{const a=l.getWordbookService();a&&"function"==typeof a.onWordbookReady&&a.onWordbookReady(()=>{this.#e.log("Received wordbook ready notification"),this.#oe||(this.#oe=!0,this.#se())}),a&&a.isWordbookReady()&&!this.#oe&&(this.#e.log("Wordbook already ready at subscription time"),this.#oe=!0,this.#se())}catch(a){this.#e.log(`Error subscribing to wordbook ready: ${a.message}`)}},1e3)};#se=()=>{this.#e.log("Forcing rerender after wordbook ready..."),setTimeout(()=>{try{const a=window.document.querySelector("body");a&&(this.#e.log("Re-parsing page after wordbook ready"),this.#ne.putInQueue(a))}catch(a){this.#e.log(`Error re-parsing page: ${a.message}`)}},100),setTimeout(()=>{try{this.#e.log("Forcing page rebuild after wordbook ready"),this.#ie.rebuildPage()}catch(a){this.#e.log(`Error forcing rebuild: ${a.message}`)}},200)};runInfinityParsing=()=>{const a=()=>{this.#oe?(this.#e.log("Starting infinite parsing - wordbook is ready"),this.#le(),this.#re()):setTimeout(a,100)};a()};#le=()=>{let a=window.document.querySelector("body");this.#ke(a),this.#ne.putInQueue(a),setInterval(()=>{this.#de()&&this.#me()&&this.#ne.parsePage()},100)};#ke=a=>{try{const i=a.textContent||a.innerText||"",n=function(a,e={}){const i=[...e.whitelist||[],...e.only||[]],n=[...e.blacklist||[],...e.ignore||[]],o=null!==e.minLength&&void 0!==e.minLength?e.minLength:10;if(!a||a.lengtho&&(o=t,i=n)}return[i,o]}(a=a.slice(0,2048),P);return t[0]&&t[0]in $?function(a,e){const i=e[0][1],n=300*a.length-i;let o=-1;for(;++o{setInterval(()=>{this.#de()&&this.#ge()&&this.#ne.textBlocksParsing()},100)};runInfinityRender=()=>{const a=()=>{this.#oe?(this.#e.log("Starting infinite render - wordbook is ready"),setInterval(()=>{this.#he()&&this.#ie.rebuildPage()},100)):setTimeout(a,100)};a()};#he=()=>{const a=l.get("render-queue");return!a.isActive()&&!a.isEmpty()};#de=()=>!l.get("render-queue").isActive();#me=()=>{const a=l.get("page-elements-queue");return!a.isActive()&&!a.isEmpty()};#ge=()=>{const a=l.get("text-elements-queue");return!a.isActive()&&!a.isEmpty()}}const K=new class{#e;#be;#ye;constructor(){this.#e=new e,this.#ye=new r,this.#be=new d}run=async()=>{try{await this.#ye.initialize();const a=this.#ye.getSettings();l.add("settings",a);const e=this.#ye.isEnabled();this.#we(e,this.#pe,this.#ce)}catch(a){this.#e.log(`PageService initialization failed: ${a.message}`);const e={enable:!0,russian:!0,english:!0,china:!1,korean:!0};l.add("settings",e),this.#we(!1,this.#pe,this.#ce)}};#we=(a,e,i)=>{this.#e.log(`Reach join point with app.enable=${a}`),a&&(this.#be.append(),this.#e.log("isServerSideParsingEnable=false"),i())};#ce=()=>{l.add("language",{sl:"en",tl:"ru"});const a=new V;a.runInfinityParsing(),a.runInfinityRender()};#pe=()=>{}};new class{#ve;#f;#fe;#je;constructor(a){this.#ve=new l,this.#fe=a,this.#je=new u,this.#f=this.#je.createService()}start=async()=>{try{if(!await this.#f.initialize())throw new Error("Failed to initialize API adapter");await this.#f.loadMainWordbook(),this.#f.isWordbookReady()?console.log("Wordbook is ready, starting service..."):console.warn("Wordbook not fully loaded, but continuing..."),this.#ze()}catch(a){throw console.error("Failed to start with API mode:",a),a}};#ze=()=>{l.add("wordbook",this.#f),this.#fe.run()}}(K).start().catch(a=>{console.error("Failed to start page app:",a)})})()})(); \ No newline at end of file diff --git a/popup/popup.js b/popup/popup.js new file mode 100644 index 0000000..08930e4 --- /dev/null +++ b/popup/popup.js @@ -0,0 +1 @@ +(()=>{var e={79:(e,t,r)=>{var s=r(342);e.exports=(s.default||s).apply(s,[])},854:(e,t,r)=>{var s=r(766);e.exports=function(e){var t,r="",o=e||{};return function(e){r=r+""+s.escape(null==(t=e.title)?"":t)+""}.call(this,"buttonInfo"in o?o.buttonInfo:"undefined"!=typeof buttonInfo?buttonInfo:void 0),r}},342:(e,t,r)=>{r(766),e.exports=function(e){return""+'
'}},161:(e,t,r)=>{var s=r(766);e.exports=function(e){var t,r="",o=e||{};return function(e,o,i){r=r+'
"}.call(this,"clear"in o?o.clear:"undefined"!=typeof clear?clear:void 0,"level"in o?o.level:"undefined"!=typeof level?level:void 0,"options"in o?o.options:"undefined"!=typeof options?options:void 0),r}},721:(e,t,r)=>{r(766),e.exports=function(e){return""+'
Languages
'}},857:(e,t,r)=>{var s=r(766);e.exports=function(e){var t,r="",o=e||{};return function(e){r=r+'
"+s.escape(null==(t=e.title)?"":t)+':
|||
'}.call(this,"language"in o?o.language:"undefined"!=typeof language?language:void 0),r}},766:(e,t,r)=>{"use strict";var s=Object.prototype.hasOwnProperty;function o(e,t){return Array.isArray(e)?function(e,t){for(var r,s="",i="",n=Array.isArray(t),a=0;a]/;function l(e){var t=""+e,r=a.exec(t);if(!r)return e;var s,o,i,n="";for(s=r.index,o=0;s ":" ")+r+"| "+e}).join("\n"),t.path=s,t.message=(s||"Pug")+":"+o+"\n"+n+"\n\n"+t.message,t}},365:()=>{},799:e=>{"use strict";e.exports=JSON.parse('{"name":"Reckue Languages","version":"0.6.0","description":"Interactive language learning assistant","permissions":["storage","activeTab","tabs"],"background":{"service_worker":"background/application.js"},"action":{"default_popup":"popup/index.html","default_icon":{"16":"images/coach16.png","32":"images/coach32.png","48":"images/coach48.png","128":"images/coach128.png"}},"content_scripts":[{"matches":["http://*/*","https://*/*"],"exclude_matches":["https://translate.google.com/*"],"run_at":"document_idle","js":["page/page.js"]}],"icons":{"16":"images/coach16.png","32":"images/coach32.png","48":"images/coach48.png","128":"images/coach128.png"},"manifest_version":3}')}},t={};function r(s){var o=t[s];if(void 0!==o)return o.exports;var i=t[s]={exports:{}};return e[s](i,i.exports,r),i.exports}(()=>{"use strict";class e{static BASE_URL="https://api.reckue.com";static API_VERSION="/api/1";static AUTH_TEMP_IN="/auth/tempin";static AUTH_WHOAMI="/auth/whoami";static WORDBOOKS="/wordbooks";static WORDBOOKS_MAIN="/wordbooks/main";static WORDBOOKS_LANGUAGE="/wordbooks/language";static WORDS="/words";static WORDBOOK_WORDS="/wordbook/words";static WORDBOOK_WORDS_LEVELS="/wordbook/words/levels";static TEXTS="/texts";static USERS_CHECK="/users/check";static TEMP_USERS="/temp-users";static TEMP_USERS_CREATE="/temp-users/create";static getFullUrl(e){return`${this.BASE_URL}${this.API_VERSION}${e}`}static getHeaders(e=null){const t={"Content-Type":"application/json"};return e&&(t.Authorization=`Bearer ${e}`),t}}class t{log=e=>{window.console.log("Reckue language app: "+e)};#e=e=>{window.console.log(e.textContent),window.console.log(e.toString()),window.console.log(e.parentNode.toString()),window.console.log(e.parentNode.nodeName),window.console.log(e.parentNode.role)}}class s{#t;#r;#s;constructor(){this.#t=new t,this.#r=null,this.#s=null}async tempAuth(){try{let t=await this.#o();t||(t=await this.#i(),await this.#n(t));const r=await fetch(e.getFullUrl(e.AUTH_TEMP_IN),{method:"POST",headers:e.getHeaders(),body:JSON.stringify({tempId:t})});if(!r.ok)throw new Error(`Temp auth failed: ${r.status}`);const s=await r.json();return this.#r=s.token,this.#s=s.userId,this.#t.log("Temp auth successful"),s}catch(e){throw this.#t.log(`Temp auth error: ${e.message}`),e}}async#i(){try{const t=await fetch(e.getFullUrl(e.TEMP_USERS_CREATE),{method:"POST",headers:e.getHeaders()});if(!t.ok)throw new Error(`Create temp user failed: ${t.status}`);const r=await t.text();return this.#t.log(`Temp user created with ID: ${r}`),r}catch(e){throw this.#t.log(`Create temp user error: ${e.message}`),e}}async#n(e){return new Promise(t=>{chrome.storage.local.set({tempUserId:e},()=>{this.#t.log(`Temp ID stored: ${e}`),t()})})}async#o(){return new Promise(e=>{chrome.storage.local.get(["tempUserId"],t=>{const r=t.tempUserId;r&&this.#t.log(`Retrieved stored temp ID: ${r}`),e(r)})})}async whoami(){if(!this.#r)throw new Error("No token available");try{const t=await fetch(e.getFullUrl(e.AUTH_WHOAMI),{method:"GET",headers:e.getHeaders(this.#r)});if(!t.ok)throw new Error(`Whoami failed: ${t.status}`);return await t.json()}catch(e){throw this.#t.log(`Whoami error: ${e.message}`),e}}async getWordbooks(){if(!this.#r)throw new Error("No token available");try{const t=await fetch(e.getFullUrl(e.WORDBOOKS),{method:"GET",headers:e.getHeaders(this.#r)});if(!t.ok)throw new Error(`Get wordbooks failed: ${t.status}`);return await t.json()}catch(e){throw this.#t.log(`Get wordbooks error: ${e.message}`),e}}async getMainWordbook(){if(!this.#r)throw new Error("No token available");try{const t=await fetch(e.getFullUrl(e.WORDBOOKS_MAIN),{method:"GET",headers:e.getHeaders(this.#r)});if(t.ok)return await t.json();if(500===t.status&&"Current user hasn't wordbooks"===(await t.json()).message){this.#t.log("User has no wordbooks, creating main wordbook..."),await this.#a(),this.#t.log("Retrying to get main wordbook...");const t=await fetch(e.getFullUrl(e.WORDBOOKS_MAIN),{method:"GET",headers:e.getHeaders(this.#r)});if(!t.ok)throw new Error(`Get main wordbook retry failed: ${t.status}`);return await t.json()}throw new Error(`Get main wordbook failed: ${t.status}`)}catch(e){throw this.#t.log(`Get main wordbook error: ${e.message}`),e}}async#a(){try{const t=await this.#l()||"English",r=await fetch(e.getFullUrl(e.WORDBOOKS),{method:"POST",headers:e.getHeaders(this.#r),body:JSON.stringify({language:t})});if(!r.ok)throw new Error(`Create main wordbook failed: ${r.status}`);const s=await r.json();return this.#t.log(`Main wordbook created with language: ${t}`),s}catch(e){throw this.#t.log(`Create main wordbook error: ${e.message}`),e}}async#l(){return"English"}async getWordbookWords(t,r=0,s=100,o=""){if(!this.#r)throw new Error("No token available");try{const i={limit:s,offset:r*s},n={alphabetFilter:"",startDateFilter:"",endDateFilter:"",levelsFilter:[]};o&&(n.alphabetFilter=o);const a=[{sortType:"word",asc:!0}],l=`${e.getFullUrl(e.WORDBOOK_WORDS)}/${t}`,d=new URLSearchParams({pageRequest:JSON.stringify(i),filterRequest:JSON.stringify(n)});this.#t.log(`Fetching words from: ${l}?${d}`);const c=await fetch(`${l}?${d}`,{method:"POST",headers:e.getHeaders(this.#r),body:JSON.stringify(a)});if(!c.ok)throw new Error(`Get wordbook words failed: ${c.status}`);const h=await c.json();return this.#t.log("API response for words:",h),h}catch(e){throw this.#t.log(`Get wordbook words error: ${e.message}`),e}}async addWord(t,r,s=1){if(!this.#r)throw new Error("No token available");try{const o=await fetch(e.getFullUrl(e.WORDBOOK_WORDS),{method:"POST",headers:e.getHeaders(this.#r),body:JSON.stringify({wordbookId:t,word:r,level:s})});if(!o.ok)throw new Error(`Add word failed: ${o.status}`);return await o.json()}catch(e){throw this.#t.log(`Add word error: ${e.message}`),e}}async updateWordLevel(t,r,s){if(!this.#r)throw new Error("No token available");try{const o=await fetch(e.getFullUrl(e.WORDBOOK_WORDS_LEVELS),{method:"POST",headers:e.getHeaders(this.#r),body:JSON.stringify({wordbookId:t,wordId:r,level:s})});if(!o.ok)throw new Error(`Update word level failed: ${o.status}`);return await o.json()}catch(e){throw this.#t.log(`Update word level error: ${e.message}`),e}}getToken(){return this.#r}getTempUserId(){return this.#s}isAuthenticated(){return!!this.#r}async clearStoredTempId(){return new Promise(e=>{chrome.storage.local.remove(["tempUserId"],()=>{this.#t.log("Stored temp ID cleared"),e()})})}}class o{#d;#c;constructor(e=50){this.#d=new Map,this.#c=e}remove=e=>{this.#d.delete(e)};set=e=>(e.forEach(e=>{this.#d.set(e.word,e.level)}),this);get=()=>this.#d;getPage=e=>{const t=new Map,r=e*this.#c,s=r+this.#c;let o=0;return this.#d.forEach((e,i)=>{o>=r&&oMath.ceil(this.#d.size/this.#c);getPages=()=>({getCount:()=>this.getPagesCount()});getSize=()=>this.#d.size;clear=()=>{this.#d.clear()};has=e=>this.#d.has(e);getLevel=e=>this.#d.get(e)}class i{#h;#t;#g;#u;#p;#w=[];constructor(){this.#h=new s,this.#t=new t,this.#g=null,this.#u=new o}executeAfter=e=>{this.#p=e};set=async e=>{try{for(const t of e)await this.addWord(t.word,t.level)}catch(e){throw this.#t.log(`Set words failed: ${e.message}`),e}};remove=async e=>{if(!this.#g)throw new Error("No current wordbook available");try{this.#u.remove(e),this.#t.log(`Word removed: ${e}`)}catch(e){throw this.#t.log(`Remove word failed: ${e.message}`),e}};getWordbook=()=>this.#u;async initialize(){try{return await this.#h.tempAuth(),this.#t.log("API adapter initialized successfully"),!0}catch(e){return this.#t.log(`API adapter initialization failed: ${e.message}`),!1}}async loadMainWordbook(){try{const e=await this.#h.getMainWordbook();this.#g=e;try{const t=await this.loadWordbookWords(e.id);this.#t.log(`Successfully loaded ${t.length} words from wordbook`),0===t.length&&this.#t.log("Warning: No words loaded from wordbook")}catch(e){this.#t.log(`Failed to load words, but continuing: ${e.message}`),this.#u.set([])}return this.#t.log(`Main wordbook loaded: ${e.language}`),e}catch(e){throw this.#t.log(`Load main wordbook failed: ${e.message}`),e}}async loadWordbookWords(e,t=0,r=100){try{this.#t.log(`Loading words from wordbook ${e}, page ${t}, size ${r}`);const s=await this.#h.getWordbookWords(e,t,r);this.#t.log("Received words data:",s);let o=[];return s&&s.content&&Array.isArray(s.content)?o=s.content.map(e=>({word:e.word,level:e.level})):Array.isArray(s)?o=s.map(e=>({word:e.word,level:e.level})):(this.#t.log("Unexpected API response structure, using empty array"),o=[]),this.#u.set(o),this.#t.log(`Successfully loaded and cached ${o.length} words from wordbook ${e}`),o.length>0&&this.#b(),o}catch(e){return this.#t.log(`Load wordbook words failed: ${e.message}`),this.#u.set([]),[]}}async addWord(e,t=1){if(!this.#g)throw new Error("No current wordbook available");try{let r=1;if("string"==typeof t)switch(t){case"NATIVE":r=5;break;case"ADVANCED":r=4;break;case"INTERMEDIATE":r=3;break;case"ELEMENTARY":r=2;break;default:r=1}else r=t;return await this.#h.addWord(this.#g.id,e,r),this.#u.set([{word:e,level:t}]),this.#t.log(`Word added: ${e} (level ${t})`),!0}catch(e){throw this.#t.log(`Add word failed: ${e.message}`),e}}async updateWordLevel(e,t){if(!this.#g)throw new Error("No current wordbook available");try{return this.#u.set([{word:e,level:t}]),this.#t.log(`Word level updated: ${e} -> ${t}`),!0}catch(e){throw this.#t.log(`Update word level failed: ${e.message}`),e}}getWordbookCache(){return this.#u?this.#u.get():(this.#t.log("Wordbook cache not initialized, returning empty Map"),new Map)}getFilteredWordbook(e){const t=[];this.#u.get().forEach((r,s)=>{s.includes(e)&&t.push({word:s,level:r})});const r=new o;return r.set(t),r}loadWordbooks=()=>{this.#p&&this.#p()};findWordId(e){return null}getCurrentWordbook(){return this.#g}isInitialized(){return this.#h.isAuthenticated()&&this.#g&&this.#u&&this.#u.get().size>0}isWordbookReady(){return this.isInitialized()&&this.#u.get().size>0}onWordbookReady(e){"function"==typeof e&&(this.#w.push(e),this.isWordbookReady()&&(this.#t.log("Wordbook already ready, calling callback immediately"),setTimeout(()=>e(),0)))}#b(){this.#t.log(`Notifying ${this.#w.length} callbacks about wordbook ready`),this.#w.forEach(e=>{try{e()}catch(e){this.#t.log(`Error in wordbook ready callback: ${e.message}`)}})}}class n{#t;constructor(){this.#t=new t}createService(){return this.#t.log("Creating API-based wordbook service"),new i}}const a=new Map;class l{static add=(e,t)=>{a.set(e,t)};static get=e=>a.get(e);static getWordbookService=()=>{const e=l.get("wordbook");if(e)return e;throw console.error("WordbookService not found in context. Make sure App is initialized properly."),new Error("WordbookService not available")}}class d{toElement=e=>{const t=window.document.createElement("div");return t.innerHTML=e.trim(),t.firstChild}}class c{#v;constructor(){this.#v=new d}getHTMLMapper=()=>this.#v}class h extends c{#f;constructor(){super(),this.#f=window.document.getElementById("content")}getContent=()=>this.#f}class g extends h{#m;buildSettingsContentStructure=()=>{const e=r(721)(),t=this.getHTMLMapper().toElement(e);this.getContent().appendChild(t);const s=this.getContent().getElementsByClassName("block")[0];this.appendSlider(s,{id:"russian",title:"Russian"}),this.appendSlider(s,{id:"korean",title:"Korean"}),this.appendSlider(s,{id:"english",title:"English"}),this.appendSlider(s,{id:"china",title:"China"})};appendSlider=(e,t)=>{const s=r(857)({language:t}),o=this.getHTMLMapper().toElement(s);e.appendChild(o)};loadLevers=()=>{this.#m=window.document.getElementsByClassName("lever")};setupAppEnableLever=e=>{this.#k(this.#m[0],e,"enable")};renderAppEnableLever=e=>{this.renderLever(this.#m[0],e)};setupRussianEnableLever=e=>{this.#k(this.#m[1],e,"russian")};renderRussianEnableLever=e=>{this.renderLever(this.#m[1],e)};setupKoreanEnableLever=e=>{this.#k(this.#m[2],e,"korean")};renderKoreanEnableLever=e=>{this.renderLever(this.#m[2],e)};setupEnglishEnableLever=e=>{this.#k(this.#m[3],e,"english")};renderEnglishEnableLever=e=>{this.renderLever(this.#m[3],e)};setupChinaEnableLever=e=>{this.#k(this.#m[4],e,"china")};renderChinaEnableLever=e=>{this.renderLever(this.#m[4],e)};#k=(e,t,r)=>{e.addEventListener("click",()=>t(e,r))};renderLever=(e,t)=>{e.style.justifyContent=t?"flex-end":"flex-start",e.style.background=t?"#c2d7bf":"#ffffff"}}class u{#h;#t;#y;constructor(){this.#h=new s,this.#t=new t,this.#y={enable:!0,russian:!0,english:!0,china:!1,korean:!0}}async initialize(){try{return await this.#h.tempAuth(),await this.loadSettings(),this.#t.log("API settings service initialized successfully"),!0}catch(e){return this.#t.log(`API settings service initialization failed: ${e.message}`),!1}}async loadSettings(){try{return this.#t.log("Settings loaded from defaults"),this.#y}catch(e){return this.#t.log(`Load settings failed: ${e.message}`),this.#y}}async saveSettings(e){try{return this.#y={...this.#y,...e},this.#t.log("Settings saved"),!0}catch(e){throw this.#t.log(`Save settings failed: ${e.message}`),e}}async updateSetting(e,t){try{return this.#y[e]=t,await this.saveSettings({[e]:t}),this.#t.log(`Setting updated: ${e} = ${t}`),!0}catch(e){throw this.#t.log(`Update setting failed: ${e.message}`),e}}getSettings(){return this.#y}getSetting(e){return this.#y[e]}isEnabled(){return this.#y.enable}getLanguageSettings(){return{russian:this.#y.russian,english:this.#y.english,china:this.#y.china,korean:this.#y.korean}}}class p{#S;#E;constructor(){this.#S=new g,this.#E=new u}fillSettings=async()=>{try{await this.#E.initialize();const e=this.#E.getSettings();this.#W(e)}catch(e){console.error("Failed to load settings:",e);const t={enable:!0,russian:!0,english:!0,china:!1,korean:!0};this.#W(t)}};#W=e=>{this.#S.loadLevers(),this.#L(e.enable),this.#C(e)};#L=e=>{this.#S.setupAppEnableLever(this.#B),this.#S.renderAppEnableLever(e)};#C=e=>{this.#S.setupRussianEnableLever(this.#B),this.#S.renderRussianEnableLever(e.russian),this.#S.setupKoreanEnableLever(this.#B),this.#S.renderKoreanEnableLever(e.korean),this.#S.setupEnglishEnableLever(this.#B),this.#S.renderEnglishEnableLever(e.english),this.#S.setupChinaEnableLever(this.#B),this.#S.renderChinaEnableLever(e.china)};#B=async(e,t)=>{try{await this.#E.updateSetting(t,!this.#E.getSetting(t)),this.#S.renderLever(e,this.#E.getSetting(t))}catch(e){console.error("Failed to update setting:",e)}}}const w=(e,t)=>{Object.entries(e).forEach(e=>{const r=e[1];t(r)})},b=Object.freeze({WORDBOOK:{title:"Wordbook",className:"nav-button",disabled:!0},SETTINGS:{title:"Settings",className:"nav-button"},REFRESH:{title:"↺ page",className:"refresh-btn"}}),v=(e,t,r)=>{e.addEventListener(t,e=>{r(e)})},f=(e,t)=>void 0!==t?(window.console.log(t),window.document.getElementsByClassName(e)[t]):f(e,0);class m extends h{#O;constructor(){super(),this.#O=f("navbar")}buildButtons=()=>{const e=r(854);w(b,t=>{const r=e({buttonInfo:t}),s=this.getHTMLMapper().toElement(r);this.#O.appendChild(s)})};setContentVisibility=(e,t)=>{const r=this.getContent().getElementsByClassName(e)[0];r.style.visibility=this.#A(t),r.style.transform=this.#T(t)};#A=e=>e?"visible":"hidden";#T=e=>e?"translate(0px)":"translate(-400px)"}const k=Object.freeze({NATIVE:{name:"native",hex:"#2e8801",number:4},ADVANCED:{name:"advanced",hex:"#72d400",number:3},INTERMEDIATE:{name:"intermediate",hex:"#ef9f00",number:2},ELEMENTARY:{name:"elementary",hex:"#ab0000",number:1},BEGINNER:{name:"beginner",hex:"#ff2a00",number:0}});class y extends c{#I;#R;constructor(){super(),this.#I=r(161),this.#R=[],w(k,e=>{this.#R.push(e.name)})}addWord=(e,t)=>{const r=this.#$(),s=this.#N(e,t);return r.appendChild(s),s};#N=(e,t)=>{const r=this.#R,s=this.#I({clear:e,level:t,options:r});return this.getHTMLMapper().toElement(s)};clearScroll=()=>{this.#$().innerHTML=""};#$=()=>window.document.getElementById("words")}class S{#P;#M;#x;#U;#_;constructor(e,t){this.#x=l.getWordbookService(),this.#M=window.document.getElementById("pages"),this.#U=e,this.#_=t}buildPageButtons=e=>{this.#P=e,this.#D();const t=this.#x.getFilteredWordbook(l.get("filter").get()).getPages().getCount();if(t<10)1!==t&&this.#F(0,t);else{const e=this.#H(t),r=this.#z(t);this.#j(e,r)}};#D=()=>{this.#M.innerHTML=""};#j=(e,t)=>{this.#F(e[0],e[1]),this.#K(),this.#F(t[0],t[1])};#K=()=>{const e=window.document.createTextNode("...");this.#M.appendChild(e)};#H=e=>this.#P>=2&&this.#P<=e-5?this.#P>=e-8?[e-10,e-5]:[this.#P-2,this.#P+3]:[0,5];#z=e=>[e-5,e];#F=(e,t)=>{for(let r=e;rthis.#U(r)),this.#M.appendChild(e)}}}class E{#_;#V;#U;constructor(e,t){this.#_="",this.#V=e,this.#U=t,this.#G()}setBuildPageButtons=e=>{this.#V=e};#G=()=>{window.document.getElementById("filter-terms").addEventListener("change",e=>{this.#_=e.target.value,this.#V&&this.#V(0),this.#U(0)})};get=()=>this.#_}class W{#x;#J;#q;#_;constructor(){this.#x=l.getWordbookService(),this.#J=new y,this.#_=new E(null,this.fillScroll),this.#q=new S(this.fillScroll,this.#_),this.#_.setBuildPageButtons(this.#q.buildPageButtons),l.add("filter",this.#_)}fillScroll=e=>{this.#q.buildPageButtons(e);const t=this.#Y(e);this.#X(t)};#Y=e=>this.#x.getFilteredWordbook(this.#_.get()).getPage(e);#X=e=>{this.#J.clearScroll(),e.forEach((e,t)=>{const r=this.#J.addWord(t,e);this.#Q(r,t),this.#Z(r,t)})};#Z=(e,t)=>{e.getElementsByTagName("input")[0].addEventListener("change",e=>this.#ee(e,t))};#Q=(e,t)=>{e.getElementsByClassName("level")[0].addEventListener("change",e=>this.#te(e,t))};#ee=async(e,t)=>{const r=this.#x.getWordbookCache().get(t),s=e.target.value;await this.#x.remove(t),await this.#re(s,r),this.fillScroll(0)};#te=async(e,t)=>{const r=e.target.value;await this.#re(t,r)};#re=async(e,t)=>{await this.#x.set([{word:e,level:t}])}}class L{#S;#O;#se;#f;#oe;constructor(){this.#S=new m,this.#O=f("navbar")}onClickNavButtons=()=>{this.#ie(),v(this.#se,"click",this.#ne),w(this.#oe,e=>v(e,"click",this.#ae))};#ie=()=>{this.#se=f("refresh-btn"),this.#oe=this.#O.getElementsByClassName("nav-button"),this.#f=f("content")};#ae=e=>{w(this.#oe,e=>e.disabled=!1),e.target.disabled=!0,this.checkButtonsAndSetContentVisibility()};checkButtonsAndSetContentVisibility=()=>{w(this.#oe,e=>{const t=e.innerText.toLowerCase();this.#S.setContentVisibility(t,e.disabled)})};#ne=()=>{chrome.tabs.query({active:!0,currentWindow:!0},e=>{chrome.tabs.executeScript(e[0].id,{file:"./page/page.js"})})}}class C extends h{build=()=>{const e=r(79),t=this.getHTMLMapper().toElement(e);this.getContent().appendChild(t)}}class B{#le;#de;constructor(){const e=r(799);this.#de=e.version,this.#le=f("infobar"),this.#ce()}#ce=()=>{this.#le.textContent=`Version ${this.#de}`}}const O=new class{#he;#ge;#ue;#pe;constructor(){this.#ge=new g,this.#he=new m,this.#ue=new C,this.#pe=new B}run=()=>{this.#we(),(new p).fillSettings(),(new W).fillScroll(0),this.#be()};#we=()=>{this.#he.buildButtons(),this.#ge.buildSettingsContentStructure(),this.#ue.build()};#be=()=>{const e=new L;e.onClickNavButtons(),e.checkButtonsAndSetContentVisibility()}};new class{#ve;#x;#fe;#me;constructor(e){this.#ve=new l,this.#fe=e,this.#me=new n,this.#x=this.#me.createService()}start=async()=>{try{if(!await this.#x.initialize())throw new Error("Failed to initialize API adapter");await this.#x.loadMainWordbook(),this.#x.isWordbookReady()?console.log("Wordbook is ready, starting service..."):console.warn("Wordbook not fully loaded, but continuing..."),this.#ke()}catch(e){throw console.error("Failed to start with API mode:",e),e}};#ke=()=>{l.add("wordbook",this.#x),this.#fe.run()}}(O).start().catch(e=>{console.error("Failed to start popup app:",e)})})()})(); \ No newline at end of file diff --git a/src/core/App.js b/src/core/App.js index 577e1e2..14dcb4b 100644 --- a/src/core/App.js +++ b/src/core/App.js @@ -1,4 +1,4 @@ -import {WordbookService} from "./words/WordbookService.js"; +import {WordbookServiceFactory} from "./api/WordbookServiceFactory.js"; import {Context} from "./Context"; export class App { @@ -6,16 +6,38 @@ export class App { #context; #wordbookService; #logicService; + #factory; constructor(logicService) { this.#context = new Context(); this.#logicService = logicService; - this.#wordbookService = new WordbookService(); + this.#factory = new WordbookServiceFactory(); // API режим + this.#wordbookService = this.#factory.createService(); } - start = () => { - this.#wordbookService.executeAfter(this.#runService); - this.#wordbookService.loadWordbooks(); + start = async () => { + try { + // Инициализация API адаптера + const initialized = await this.#wordbookService.initialize(); + if (!initialized) { + throw new Error('Failed to initialize API adapter'); + } + + // Загрузка основного словаря + await this.#wordbookService.loadMainWordbook(); + + // Проверяем, что словарь действительно загружен + if (!this.#wordbookService.isWordbookReady()) { + console.warn('Wordbook not fully loaded, but continuing...'); + } else { + console.log('Wordbook is ready, starting service...'); + } + + this.#runService(); + } catch (error) { + console.error('Failed to start with API mode:', error); + throw error; // Не fallback на локальный режим + } } #runService = () => { diff --git a/src/core/Context.js b/src/core/Context.js index 0a9f7a0..bd6db97 100644 --- a/src/core/Context.js +++ b/src/core/Context.js @@ -15,5 +15,7 @@ export class Context { if (wordbook) { return wordbook; } + console.error('WordbookService not found in context. Make sure App is initialized properly.'); + throw new Error('WordbookService not available'); } } \ No newline at end of file diff --git a/src/core/Store.js b/src/core/Store.js deleted file mode 100644 index be10eb5..0000000 --- a/src/core/Store.js +++ /dev/null @@ -1,22 +0,0 @@ -import {Context} from "./Context"; - -export class Store { - - appParams = () => { - return new Promise(resolve => { - chrome.storage.local.get(['enable', "russian", "english", "china", "korean"], (app) => { - Context.add("settings", app); - resolve(app.enable); - }) - }); - } - - //TODO:: Заменить старый метод в Wordbook - saveWordbooks = (wordbooks) => { - chrome.storage.local.set(wordbooks); - } - - getByName = (name) => { - return new Promise(resolve => chrome.storage.local.get([name], (app) => resolve(app[name]))); - } -} \ No newline at end of file diff --git a/src/core/api/ApiConfig.js b/src/core/api/ApiConfig.js new file mode 100644 index 0000000..c716cff --- /dev/null +++ b/src/core/api/ApiConfig.js @@ -0,0 +1,42 @@ +export class ApiConfig { + static BASE_URL = 'https://api.reckue.com'; + static API_VERSION = '/api/1'; + + // Auth endpoints + static AUTH_TEMP_IN = '/auth/tempin'; + static AUTH_WHOAMI = '/auth/whoami'; + + // Wordbooks endpoints + static WORDBOOKS = '/wordbooks'; + static WORDBOOKS_MAIN = '/wordbooks/main'; + static WORDBOOKS_LANGUAGE = '/wordbooks/language'; + + // Words endpoints + static WORDS = '/words'; + static WORDBOOK_WORDS = '/wordbook/words'; + static WORDBOOK_WORDS_LEVELS = '/wordbook/words/levels'; + + // Texts endpoints + static TEXTS = '/texts'; + + // Users endpoints + static USERS_CHECK = '/users/check'; + static TEMP_USERS = '/temp-users'; + static TEMP_USERS_CREATE = '/temp-users/create'; + + static getFullUrl(endpoint) { + return `${this.BASE_URL}${this.API_VERSION}${endpoint}`; + } + + static getHeaders(token = null) { + const headers = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + return headers; + } +} \ No newline at end of file diff --git a/src/core/api/ApiService.js b/src/core/api/ApiService.js new file mode 100644 index 0000000..56f190f --- /dev/null +++ b/src/core/api/ApiService.js @@ -0,0 +1,378 @@ +import {ApiConfig} from './ApiConfig.js'; +import {Logger} from '../Logger.js'; + +/** + * ApiService - Основной сервис для работы с reckue.com API + * + * ЛОГИКА АВТОРИЗАЦИИ: + * 1. При первом вызове tempAuth() автоматически создается временный пользователь + * через /api/1/temp-users/create + * 2. Полученный tempId сохраняется в chrome.storage.local + * 3. При последующих вызовах используется сохраненный tempId для авторизации + * 4. Авторизация выполняется через /api/1/auth/tempin с передачей tempId в body + * + * FALLBACK ЛОГИКА ДЛЯ СЛОВАРЕЙ: + * 1. При попытке получить основной словарь проверяется его наличие + * 2. Если словарь не найден (ошибка 500 с сообщением "Current user hasn't wordbooks") + * 3. Автоматически создается новый основной словарь через POST /api/1/wordbooks + * 4. Язык определяется из настроек пользователя (russian, english, china, korean) + * 5. После создания словаря повторяется попытка его получения + * + * Это решает проблему, когда мы пытались авторизоваться без создания пользователя + * и проблему отсутствия словарей у нового пользователя. + */ + +export class ApiService { + #logger; + #token; + #tempUserId; + + constructor() { + this.#logger = new Logger(); + this.#token = null; + this.#tempUserId = null; + } + + // Auth methods + async tempAuth() { + try { + // Сначала пытаемся получить сохраненный tempId из chrome.storage.local + let tempId = await this.#getStoredTempId(); + + // Если tempId нет, создаем нового временного пользователя + if (!tempId) { + tempId = await this.#createTempUser(); + await this.#storeTempId(tempId); + } + + // Теперь делаем авторизацию с полученным tempId + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.AUTH_TEMP_IN), { + method: 'POST', + headers: ApiConfig.getHeaders(), + body: JSON.stringify({ + tempId: tempId + }) + }); + + if (!response.ok) { + throw new Error(`Temp auth failed: ${response.status}`); + } + + const data = await response.json(); + this.#token = data.token; + this.#tempUserId = data.userId; + + this.#logger.log('Temp auth successful'); + return data; + } catch (error) { + this.#logger.log(`Temp auth error: ${error.message}`); + throw error; + } + } + + // Создание временного пользователя + async #createTempUser() { + try { + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.TEMP_USERS_CREATE), { + method: 'POST', + headers: ApiConfig.getHeaders() + }); + + if (!response.ok) { + throw new Error(`Create temp user failed: ${response.status}`); + } + + const tempId = await response.text(); // Получаем строку с ID + this.#logger.log(`Temp user created with ID: ${tempId}`); + return tempId; + } catch (error) { + this.#logger.log(`Create temp user error: ${error.message}`); + throw error; + } + } + + // Сохранение tempId в chrome.storage.local + async #storeTempId(tempId) { + return new Promise((resolve) => { + chrome.storage.local.set({ tempUserId: tempId }, () => { + this.#logger.log(`Temp ID stored: ${tempId}`); + resolve(); + }); + }); + } + + // Получение tempId из chrome.storage.local + async #getStoredTempId() { + return new Promise((resolve) => { + chrome.storage.local.get(['tempUserId'], (result) => { + const tempId = result.tempUserId; + if (tempId) { + this.#logger.log(`Retrieved stored temp ID: ${tempId}`); + } + resolve(tempId); + }); + }); + } + + async whoami() { + if (!this.#token) { + throw new Error('No token available'); + } + + try { + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.AUTH_WHOAMI), { + method: 'GET', + headers: ApiConfig.getHeaders(this.#token) + }); + + if (!response.ok) { + throw new Error(`Whoami failed: ${response.status}`); + } + + return await response.json(); + } catch (error) { + this.#logger.log(`Whoami error: ${error.message}`); + throw error; + } + } + + // Wordbooks methods + async getWordbooks() { + if (!this.#token) { + throw new Error('No token available'); + } + + try { + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.WORDBOOKS), { + method: 'GET', + headers: ApiConfig.getHeaders(this.#token) + }); + + if (!response.ok) { + throw new Error(`Get wordbooks failed: ${response.status}`); + } + + return await response.json(); + } catch (error) { + this.#logger.log(`Get wordbooks error: ${error.message}`); + throw error; + } + } + + async getMainWordbook() { + if (!this.#token) { + throw new Error('No token available'); + } + + try { + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.WORDBOOKS_MAIN), { + method: 'GET', + headers: ApiConfig.getHeaders(this.#token) + }); + + if (response.ok) { + return await response.json(); + } + + // Если получили ошибку 500 о том, что у пользователя нет словарей + if (response.status === 500) { + const errorData = await response.json(); + if (errorData.message === "Current user hasn't wordbooks") { + this.#logger.log('User has no wordbooks, creating main wordbook...'); + + // Создаем основной словарь + await this.#createMainWordbook(); + + // Повторяем попытку получения основного словаря + this.#logger.log('Retrying to get main wordbook...'); + const retryResponse = await fetch(ApiConfig.getFullUrl(ApiConfig.WORDBOOKS_MAIN), { + method: 'GET', + headers: ApiConfig.getHeaders(this.#token) + }); + + if (!retryResponse.ok) { + throw new Error(`Get main wordbook retry failed: ${retryResponse.status}`); + } + + return await retryResponse.json(); + } + } + + throw new Error(`Get main wordbook failed: ${response.status}`); + } catch (error) { + this.#logger.log(`Get main wordbook error: ${error.message}`); + throw error; + } + } + + // Создание основного словаря + async #createMainWordbook() { + try { + // Получаем язык из настроек или используем английский по умолчанию + const language = await this.#getUserLanguage() || 'English'; + + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.WORDBOOKS), { + method: 'POST', + headers: ApiConfig.getHeaders(this.#token), + body: JSON.stringify({ + language: language + }) + }); + + if (!response.ok) { + throw new Error(`Create main wordbook failed: ${response.status}`); + } + + const result = await response.json(); + this.#logger.log(`Main wordbook created with language: ${language}`); + return result; + } catch (error) { + this.#logger.log(`Create main wordbook error: ${error.message}`); + throw error; + } + } + + // Получение языка пользователя из настроек + async #getUserLanguage() { + // Пока используем английский по умолчанию + // В будущем можно интегрировать с ApiSettingsService + return 'English'; + } + + async getWordbookWords(wordbookId, page = 0, size = 100, filter = '') { + if (!this.#token) { + throw new Error('No token available'); + } + + try { + // Создаем параметры запроса согласно API документации + const pageRequest = { + limit: size, + offset: page * size + }; + + const filterRequest = { + alphabetFilter: "", + startDateFilter: "", + endDateFilter: "", + levelsFilter: [] + }; + + // Если есть фильтр, применяем его к alphabetFilter + if (filter) { + filterRequest.alphabetFilter = filter; + } + + // Тело запроса согласно API документации + const requestBody = [ + { + sortType: "word", + asc: true + } + ]; + + const url = `${ApiConfig.getFullUrl(ApiConfig.WORDBOOK_WORDS)}/${wordbookId}`; + const params = new URLSearchParams({ + pageRequest: JSON.stringify(pageRequest), + filterRequest: JSON.stringify(filterRequest) + }); + + this.#logger.log(`Fetching words from: ${url}?${params}`); + + const response = await fetch(`${url}?${params}`, { + method: 'POST', + headers: ApiConfig.getHeaders(this.#token), + body: JSON.stringify(requestBody) + }); + + if (!response.ok) { + throw new Error(`Get wordbook words failed: ${response.status}`); + } + + const data = await response.json(); + this.#logger.log(`API response for words:`, data); + + return data; + } catch (error) { + this.#logger.log(`Get wordbook words error: ${error.message}`); + throw error; + } + } + + async addWord(wordbookId, word, level = 1) { + if (!this.#token) { + throw new Error('No token available'); + } + + try { + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.WORDBOOK_WORDS), { + method: 'POST', + headers: ApiConfig.getHeaders(this.#token), + body: JSON.stringify({ + wordbookId: wordbookId, + word: word, + level: level + }) + }); + + if (!response.ok) { + throw new Error(`Add word failed: ${response.status}`); + } + + return await response.json(); + } catch (error) { + this.#logger.log(`Add word error: ${error.message}`); + throw error; + } + } + + async updateWordLevel(wordbookId, wordId, level) { + if (!this.#token) { + throw new Error('No token available'); + } + + try { + const response = await fetch(ApiConfig.getFullUrl(ApiConfig.WORDBOOK_WORDS_LEVELS), { + method: 'POST', + headers: ApiConfig.getHeaders(this.#token), + body: JSON.stringify({ + wordbookId: wordbookId, + wordId: wordId, + level: level + }) + }); + + if (!response.ok) { + throw new Error(`Update word level failed: ${response.status}`); + } + + return await response.json(); + } catch (error) { + this.#logger.log(`Update word level error: ${error.message}`); + throw error; + } + } + + // Getters + getToken() { + return this.#token; + } + + getTempUserId() { + return this.#tempUserId; + } + + isAuthenticated() { + return !!this.#token; + } + + // Очистка сохраненного tempId + async clearStoredTempId() { + return new Promise((resolve) => { + chrome.storage.local.remove(['tempUserId'], () => { + this.#logger.log('Stored temp ID cleared'); + resolve(); + }); + }); + } +} \ No newline at end of file diff --git a/src/core/api/ApiSettingsService.js b/src/core/api/ApiSettingsService.js new file mode 100644 index 0000000..4c33df1 --- /dev/null +++ b/src/core/api/ApiSettingsService.js @@ -0,0 +1,94 @@ +import {ApiService} from './ApiService.js'; +import {Logger} from '../Logger.js'; + +export class ApiSettingsService { + #apiService; + #logger; + #settings; + + constructor() { + this.#apiService = new ApiService(); + this.#logger = new Logger(); + this.#settings = { + enable: true, + russian: true, + english: true, + china: false, + korean: true + }; + } + + async initialize() { + try { + // Авторизация через temp auth + await this.#apiService.tempAuth(); + + // Загружаем настройки из API или используем дефолтные + await this.loadSettings(); + + this.#logger.log('API settings service initialized successfully'); + return true; + } catch (error) { + this.#logger.log(`API settings service initialization failed: ${error.message}`); + return false; + } + } + + async loadSettings() { + try { + // Пока используем дефолтные настройки + // В будущем можно добавить API endpoint для настроек + this.#logger.log('Settings loaded from defaults'); + return this.#settings; + } catch (error) { + this.#logger.log(`Load settings failed: ${error.message}`); + return this.#settings; + } + } + + async saveSettings(settings) { + try { + this.#settings = { ...this.#settings, ...settings }; + + // В будущем можно добавить API endpoint для сохранения настроек + this.#logger.log('Settings saved'); + return true; + } catch (error) { + this.#logger.log(`Save settings failed: ${error.message}`); + throw error; + } + } + + async updateSetting(key, value) { + try { + this.#settings[key] = value; + await this.saveSettings({ [key]: value }); + this.#logger.log(`Setting updated: ${key} = ${value}`); + return true; + } catch (error) { + this.#logger.log(`Update setting failed: ${error.message}`); + throw error; + } + } + + getSettings() { + return this.#settings; + } + + getSetting(key) { + return this.#settings[key]; + } + + isEnabled() { + return this.#settings.enable; + } + + getLanguageSettings() { + return { + russian: this.#settings.russian, + english: this.#settings.english, + china: this.#settings.china, + korean: this.#settings.korean + }; + } +} diff --git a/src/core/api/ApiUtils.js b/src/core/api/ApiUtils.js new file mode 100644 index 0000000..6461b7c --- /dev/null +++ b/src/core/api/ApiUtils.js @@ -0,0 +1,90 @@ +import {Logger} from '../Logger.js'; + +export class ApiUtils { + #logger; + + constructor() { + this.#logger = new Logger(); + } + + static async handleApiResponse(response) { + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API Error ${response.status}: ${errorText}`); + } + + try { + return await response.json(); + } catch (error) { + throw new Error(`Failed to parse JSON response: ${error.message}`); + } + } + + static validateWord(word) { + if (!word || typeof word !== 'string') { + throw new Error('Word must be a non-empty string'); + } + + if (word.trim().length === 0) { + throw new Error('Word cannot be empty'); + } + + return word.trim().toLowerCase(); + } + + static validateLevel(level) { + const validLevels = [1, 2, 3, 4]; + if (!validLevels.includes(level)) { + throw new Error(`Invalid level: ${level}. Must be one of: ${validLevels.join(', ')}`); + } + return level; + } + + static validateWordbookId(wordbookId) { + if (!wordbookId || typeof wordbookId !== 'string') { + throw new Error('Wordbook ID must be a non-empty string'); + } + return wordbookId; + } + + static async retryRequest(requestFn, maxRetries = 3, delay = 1000) { + let lastError; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await requestFn(); + } catch (error) { + lastError = error; + + if (attempt === maxRetries) { + throw error; + } + + // Ждем перед повторной попыткой + await new Promise(resolve => setTimeout(resolve, delay * attempt)); + } + } + + throw lastError; + } + + static isNetworkError(error) { + return error.name === 'TypeError' && error.message.includes('fetch'); + } + + static isAuthError(error) { + return error.message.includes('401') || error.message.includes('403'); + } + + static formatError(error) { + if (this.isNetworkError(error)) { + return 'Network error. Please check your internet connection.'; + } + + if (this.isAuthError(error)) { + return 'Authentication error. Please try refreshing the page.'; + } + + return error.message || 'Unknown error occurred'; + } +} \ No newline at end of file diff --git a/src/core/api/ApiWordbook.js b/src/core/api/ApiWordbook.js new file mode 100644 index 0000000..ba71e2f --- /dev/null +++ b/src/core/api/ApiWordbook.js @@ -0,0 +1,67 @@ +export class ApiWordbook { + #cache; + #pageSize; + + constructor(pageSize = 50) { + this.#cache = new Map(); + this.#pageSize = pageSize; + } + + remove = (word) => { + this.#cache.delete(word); + } + + set = (list) => { + list.forEach((bundle) => { + this.#cache.set(bundle.word, bundle.level); + }); + return this; + } + + get = () => { + return this.#cache; + } + + getPage = (page) => { + const result = new Map(); + const startIndex = page * this.#pageSize; + const endIndex = startIndex + this.#pageSize; + + let index = 0; + this.#cache.forEach((level, word) => { + if (index >= startIndex && index < endIndex) { + result.set(word, level); + } + index++; + }); + + return result; + } + + getPagesCount = () => { + return Math.ceil(this.#cache.size / this.#pageSize); + } + + // Метод для совместимости с существующим кодом + getPages = () => { + return { + getCount: () => this.getPagesCount() + }; + } + + getSize = () => { + return this.#cache.size; + } + + clear = () => { + this.#cache.clear(); + } + + has = (word) => { + return this.#cache.has(word); + } + + getLevel = (word) => { + return this.#cache.get(word); + } +} diff --git a/src/core/api/ApiWordbookAdapter.js b/src/core/api/ApiWordbookAdapter.js new file mode 100644 index 0000000..347a0f8 --- /dev/null +++ b/src/core/api/ApiWordbookAdapter.js @@ -0,0 +1,279 @@ +import {ApiService} from './ApiService.js'; +import {ApiWordbook} from './ApiWordbook.js'; +import {Logger} from '../Logger.js'; + +export class ApiWordbookAdapter { + #apiService; + #logger; + #currentWordbook; + #wordbookCache; + #executeAfter; + #onWordbookReadyCallbacks = []; + + constructor() { + this.#apiService = new ApiService(); + this.#logger = new Logger(); + this.#currentWordbook = null; + this.#wordbookCache = new ApiWordbook(); + } + + // Методы для совместимости с WordbookService + executeAfter = (after) => { + this.#executeAfter = after; + } + + set = async (words) => { + try { + for (const wordData of words) { + await this.addWord(wordData.word, wordData.level); + } + } catch (error) { + this.#logger.log(`Set words failed: ${error.message}`); + throw error; + } + } + + remove = async (word) => { + if (!this.#currentWordbook) { + throw new Error('No current wordbook available'); + } + + try { + // Удаляем слово из API (если есть такой метод) + // await this.#apiService.removeWord(this.#currentWordbook.id, word); + + // Удаляем из локального кэша + this.#wordbookCache.remove(word); + + this.#logger.log(`Word removed: ${word}`); + } catch (error) { + this.#logger.log(`Remove word failed: ${error.message}`); + throw error; + } + } + + getWordbook = () => { + return this.#wordbookCache; + } + + async initialize() { + try { + // Авторизация через temp auth + await this.#apiService.tempAuth(); + this.#logger.log('API adapter initialized successfully'); + return true; + } catch (error) { + this.#logger.log(`API adapter initialization failed: ${error.message}`); + return false; + } + } + + async loadMainWordbook() { + try { + const wordbookData = await this.#apiService.getMainWordbook(); + this.#currentWordbook = wordbookData; + + // Загружаем слова из основного словаря + try { + const words = await this.loadWordbookWords(wordbookData.id); + this.#logger.log(`Successfully loaded ${words.length} words from wordbook`); + + // Проверяем, что слова действительно загружены + if (words.length === 0) { + this.#logger.log('Warning: No words loaded from wordbook'); + } + } catch (wordsError) { + this.#logger.log(`Failed to load words, but continuing: ${wordsError.message}`); + // Инициализируем пустой кэш + this.#wordbookCache.set([]); + } + + this.#logger.log(`Main wordbook loaded: ${wordbookData.language}`); + return wordbookData; + } catch (error) { + this.#logger.log(`Load main wordbook failed: ${error.message}`); + throw error; + } + } + + async loadWordbookWords(wordbookId, page = 0, size = 100) { + try { + this.#logger.log(`Loading words from wordbook ${wordbookId}, page ${page}, size ${size}`); + const wordsData = await this.#apiService.getWordbookWords(wordbookId, page, size); + + this.#logger.log('Received words data:', wordsData); + + // Проверяем структуру данных и безопасно извлекаем слова + let words = []; + if (wordsData && wordsData.content && Array.isArray(wordsData.content)) { + words = wordsData.content.map(item => ({ + word: item.word, + level: item.level + })); + } else if (Array.isArray(wordsData)) { + // Если API возвращает массив напрямую + words = wordsData.map(item => ({ + word: item.word, + level: item.level + })); + } else { + this.#logger.log('Unexpected API response structure, using empty array'); + words = []; + } + + // Кэшируем слова + this.#wordbookCache.set(words); + + this.#logger.log(`Successfully loaded and cached ${words.length} words from wordbook ${wordbookId}`); + + // Уведомляем о готовности словаря, если есть слова + if (words.length > 0) { + this.#notifyWordbookReady(); + } + + return words; + } catch (error) { + this.#logger.log(`Load wordbook words failed: ${error.message}`); + // Возвращаем пустой массив вместо выброса ошибки + this.#wordbookCache.set([]); + return []; + } + } + + async addWord(word, level = 1) { + if (!this.#currentWordbook) { + throw new Error('No current wordbook available'); + } + + try { + // Преобразуем строковый уровень в числовой + let numericLevel = 1; + if (typeof level === 'string') { + switch (level) { + case 'NATIVE': + numericLevel = 5; + break; + case 'ADVANCED': + numericLevel = 4; + break; + case 'INTERMEDIATE': + numericLevel = 3; + break; + case 'ELEMENTARY': + numericLevel = 2; + break; + case 'BEGINNER': + numericLevel = 1; + break; + default: + numericLevel = 1; + } + } else { + numericLevel = level; + } + + await this.#apiService.addWord(this.#currentWordbook.id, word, numericLevel); + + // Обновляем локальный кэш + this.#wordbookCache.set([{word, level}]); + + this.#logger.log(`Word added: ${word} (level ${level})`); + return true; + } catch (error) { + this.#logger.log(`Add word failed: ${error.message}`); + throw error; + } + } + + async updateWordLevel(word, level) { + if (!this.#currentWordbook) { + throw new Error('No current wordbook available'); + } + + try { + // Для простоты пока обновляем только локальный кэш + // В реальной реализации нужно получить wordId из API + this.#wordbookCache.set([{word, level}]); + + this.#logger.log(`Word level updated: ${word} -> ${level}`); + return true; + } catch (error) { + this.#logger.log(`Update word level failed: ${error.message}`); + throw error; + } + } + + // Методы для совместимости с существующим WordbookService + getWordbookCache() { + if (!this.#wordbookCache) { + this.#logger.log('Wordbook cache not initialized, returning empty Map'); + return new Map(); + } + return this.#wordbookCache.get(); + } + + getFilteredWordbook(filter) { + const filtered = []; + this.#wordbookCache.get().forEach((level, word) => { + if (word.includes(filter)) { + filtered.push({word, level}); + } + }); + const wordbook = new ApiWordbook(); + wordbook.set(filtered); + return wordbook; + } + + // Методы для совместимости с локальным режимом + loadWordbooks = () => { + // Для API режима этот метод не нужен, но оставляем для совместимости + if (this.#executeAfter) { + this.#executeAfter(); + } + } + + // Вспомогательные методы + findWordId(word) { + // В реальной реализации нужно получить wordId из API или кэша + // Пока возвращаем null, так как wordId не хранится в локальном кэше + return null; + } + + getCurrentWordbook() { + return this.#currentWordbook; + } + + isInitialized() { + return this.#apiService.isAuthenticated() && + this.#currentWordbook && + this.#wordbookCache && + this.#wordbookCache.get().size > 0; + } + + isWordbookReady() { + return this.isInitialized() && this.#wordbookCache.get().size > 0; + } + + onWordbookReady(callback) { + if (typeof callback === 'function') { + this.#onWordbookReadyCallbacks.push(callback); + + // Если словарь уже готов, сразу вызываем callback + if (this.isWordbookReady()) { + this.#logger.log('Wordbook already ready, calling callback immediately'); + setTimeout(() => callback(), 0); + } + } + } + + #notifyWordbookReady() { + this.#logger.log(`Notifying ${this.#onWordbookReadyCallbacks.length} callbacks about wordbook ready`); + this.#onWordbookReadyCallbacks.forEach(callback => { + try { + callback(); + } catch (error) { + this.#logger.log(`Error in wordbook ready callback: ${error.message}`); + } + }); + } +} \ No newline at end of file diff --git a/src/core/api/Languages.js b/src/core/api/Languages.js new file mode 100644 index 0000000..baf46b4 --- /dev/null +++ b/src/core/api/Languages.js @@ -0,0 +1,29 @@ +export const Languages = Object.freeze({ + ENGLISH: 'English', + RUSSIAN: 'Russian', + FRENCH: 'French', + SPANISH: 'Spanish', + KOREAN: 'Korean', + CHINESE: 'Chinese', + GERMAN: 'German' +}); + +export const LanguageNames = Object.freeze({ + [Languages.ENGLISH]: 'English', + [Languages.RUSSIAN]: 'Русский', + [Languages.FRENCH]: 'Français', + [Languages.SPANISH]: 'Español', + [Languages.KOREAN]: '한국어', + [Languages.CHINESE]: '中文', + [Languages.GERMAN]: 'Deutsch' +}); + +export const LanguageCodes = Object.freeze({ + [Languages.ENGLISH]: 'en', + [Languages.RUSSIAN]: 'ru', + [Languages.FRENCH]: 'fr', + [Languages.SPANISH]: 'es', + [Languages.KOREAN]: 'ko', + [Languages.CHINESE]: 'zh', + [Languages.GERMAN]: 'de' +}); \ No newline at end of file diff --git a/src/core/api/README.md b/src/core/api/README.md new file mode 100644 index 0000000..bba6cc3 --- /dev/null +++ b/src/core/api/README.md @@ -0,0 +1,150 @@ +# API Integration Module + +Этот модуль обеспечивает интеграцию плагина с API reckue.com для работы со словарями и словами. + +## Архитектура + +### Основные компоненты + +1. **ApiConfig** - Конфигурация API endpoints и базовые настройки +2. **ApiService** - Основной сервис для работы с API +3. **ApiWordbookAdapter** - Адаптер для совместимости с существующей архитектурой +4. **ApiWordbook** - Класс для работы со словарями в API режиме +5. **ApiSettingsService** - Сервис для работы с настройками через API +6. **WordbookServiceFactory** - Фабрика для создания API сервисов +7. **ApiUtils** - Утилиты для работы с API +8. **Languages** - Константы поддерживаемых языков + +### Режим работы + +- **API режим** - Использует reckue.com API с временной авторизацией + +## Использование + +### Базовое использование + +```javascript +import {ApiService} from './src/core/api/index.js'; + +const apiService = new ApiService(); + +// Временная авторизация +await apiService.tempAuth(); + +// Получение словарей +const wordbooks = await apiService.getWordbooks(); + +// Добавление слова +await apiService.addWord('wordbookId', 'word', 1); +``` + +### Использование фабрики + +```javascript +import {WordbookServiceFactory} from './src/core/api/index.js'; + +const factory = new WordbookServiceFactory(); +const wordbookService = factory.createService(); + +// Инициализация API адаптера +await wordbookService.initialize(); +await wordbookService.loadMainWordbook(); +``` + +### Использование настроек + +```javascript +import {ApiSettingsService} from './src/core/api/index.js'; + +const settingsService = new ApiSettingsService(); + +// Инициализация +await settingsService.initialize(); + +// Получение настроек +const settings = settingsService.getSettings(); + +// Обновление настройки +await settingsService.updateSetting('enable', false); +``` + +## API Endpoints + +### Авторизация +- `POST /api/1/temp-users/create` - Создание временного пользователя (возвращает tempId) +- `POST /api/1/auth/tempin` - Временная авторизация с tempId +- `GET /api/1/auth/whoami` - Информация о пользователе + +### Словари +- `GET /api/1/wordbooks` - Получение всех словарей +- `POST /api/1/wordbooks` - Создание нового словаря +- `GET /api/1/wordbooks/main` - Основной словарь (с fallback логикой) +- `GET /api/1/wordbooks/language/{language}` - Словари по языку + +### Слова +- `POST /api/1/wordbook/words/{wordbookId}` - Получение слов с пагинацией +- `POST /api/1/wordbook/words` - Добавление слова +- `POST /api/1/wordbook/words/levels` - Обновление уровня слова + +## Логика работы со словарями + +### Получение основного словаря + +Метод `getMainWordbook()` включает fallback логику: + +1. **Попытка получения основного словаря** (`GET /api/1/wordbooks/main`) +2. **Если словарь не найден** (ошибка 500 с сообщением "Current user hasn't wordbooks"): + - Автоматически создается новый основной словарь (`POST /api/1/wordbooks`) + - Язык определяется из настроек пользователя (russian, english, china, korean) + - По умолчанию используется English +3. **Повторная попытка получения** основного словаря после создания + +### Создание словаря + +При создании словаря используется POST запрос с телом: +```json +{ + "language": "English" +} +``` + +Поддерживаемые языки: English, Russian, Chinese, Korean, French, Spanish, German + +## Логика авторизации + +Процесс временной авторизации состоит из двух этапов: + +1. **Создание временного пользователя**: + - POST запрос к `/api/1/temp-users/create` + - Возвращает tempId (строка) + +2. **Временная авторизация**: + - POST запрос к `/api/1/auth/tempin` с tempId в body + - Возвращает token и userId + +### Особенности авторизации + +- TempId создается заново при каждом вызове tempAuth() +- TempId не сохраняется между сессиями +- Авторизация действительна только для текущей сессии + +## Миграция с Local Storage + +### Удаленные компоненты + +- `Store.js` - Класс для работы с chrome.storage.local +- `WordbookService.js` - Локальная реализация сервиса словарей +- `Wordbook.js` - Локальная реализация словаря +- `Pages.js` - Класс для пагинации в локальном режиме + +### Новые компоненты + +- `ApiWordbook.js` - Класс для работы со словарями в API режиме +- `ApiSettingsService.js` - Сервис для работы с настройками через API + +### Изменения в существующих компонентах + +- `App.js` - Убрана fallback логика на локальный режим +- `WordbookServiceFactory.js` - Убрана поддержка локального режима +- `PageService.js` - Использует ApiSettingsService вместо Store +- `SettingsService.js` - Использует ApiSettingsService вместо chrome.storage.local \ No newline at end of file diff --git a/src/core/api/WordbookServiceFactory.js b/src/core/api/WordbookServiceFactory.js new file mode 100644 index 0000000..27fbaea --- /dev/null +++ b/src/core/api/WordbookServiceFactory.js @@ -0,0 +1,15 @@ +import {ApiWordbookAdapter} from './ApiWordbookAdapter.js'; +import {Logger} from '../Logger.js'; + +export class WordbookServiceFactory { + #logger; + + constructor() { + this.#logger = new Logger(); + } + + createService() { + this.#logger.log('Creating API-based wordbook service'); + return new ApiWordbookAdapter(); + } +} \ No newline at end of file diff --git a/src/core/api/index.js b/src/core/api/index.js new file mode 100644 index 0000000..72b278e --- /dev/null +++ b/src/core/api/index.js @@ -0,0 +1,8 @@ +export {ApiConfig} from './ApiConfig.js'; +export {ApiService} from './ApiService.js'; +export {ApiWordbookAdapter} from './ApiWordbookAdapter.js'; +export {ApiWordbook} from './ApiWordbook.js'; +export {ApiSettingsService} from './ApiSettingsService.js'; +export {WordbookServiceFactory} from './WordbookServiceFactory.js'; +export {ApiUtils} from './ApiUtils.js'; +export {Languages} from './Languages.js'; \ No newline at end of file diff --git a/src/core/words/Pages.js b/src/core/words/Pages.js deleted file mode 100644 index 0a62108..0000000 --- a/src/core/words/Pages.js +++ /dev/null @@ -1,27 +0,0 @@ -export class Pages { - - #wordsCount; - - #pagesCount; - #scale; - - constructor(wordsCount, scale) { - this.#wordsCount = wordsCount; - this.#scale = scale; - } - - getCount = () => { - return this.#pagesCount; - } - - calcPagesCount = () => { - this.#pagesCount = Math.ceil(this.#wordsCount / this.#scale); - return this.#pagesCount; - } - - isIndexOnPage = (page, index) => index >= this.#getPageStart(page) && index < this.#getPageEnd(page); - - #getPageStart = (page) => page * this.#scale; - - #getPageEnd = (page) => (page + 1) * this.#scale; -} \ No newline at end of file diff --git a/src/core/words/UnicodeLanguages.js b/src/core/words/UnicodeLanguages.js index 7a0c942..4974645 100644 --- a/src/core/words/UnicodeLanguages.js +++ b/src/core/words/UnicodeLanguages.js @@ -21,20 +21,24 @@ export class UnicodeLanguages { } #russian = () => { - return Context.get("settings").russian ? "йцукенгшщзхъфывапролджэячсмитьбю" : ""; + const settings = Context.get("settings"); + return settings && settings.russian ? "йцукенгшщзхъфывапролджэячсмитьбю" : ""; } #english = () => { - return Context.get("settings").english ? "yqwertuiopasdfghjklzxcvbnm" : ""; + const settings = Context.get("settings"); + return settings && settings.english ? "yqwertuiopasdfghjklzxcvbnm" : ""; } #korean = () => { - return Context.get("settings").korean ? "ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅓㅗㅜㅡㅣㅑㅕㅛㅠㄲㄸㅃㅆㅉㄳㄵ" + + const settings = Context.get("settings"); + return settings && settings.korean ? "ㄱㄴㄷㄹㅁㅂㅅㅇㅈㅊㅋㅌㅍㅎㅏㅓㅗㅜㅡㅣㅑㅕㅛㅠㄲㄸㅃㅆㅉㄳㄵ" + "ㄶㄺㄻㄼㄽㄾㄿㅀㅄㅐㅒㅔㅖㅘㅙㅚㅝㅞㅟㅢ" : ""; } #china = () => { - return Context.get("settings").china ? "一丁丂七丄丅丆万丈三上下丌不与丏丐丑丒专且丕世丗丘丙业丛东丝丞丟" + + const settings = Context.get("settings"); + return settings && settings.china ? "一丁丂七丄丅丆万丈三上下丌不与丏丐丑丒专且丕世丗丘丙业丛东丝丞丟" + "丠両丢丣两严並丧丨丩个丫丬中丮丯丰丱串丳临丵丶丷丸丹为主丼丽举丿乀乁乂乃乄久乆乇么义乊之乌乍乎乏乐乑乒乓乔乕" + "乪乫乬乭乮乯买乱乲乳乴乵乶乷乸乹乺乻乼乽乾乿亀亁亂亃亄亅了亇予争亊事二亍于亏亐云互亓五井亖亗亘亙亚些亜亝亞亟" + "亠亡亢亣交亥亦产亨亩亪享京亭亮亯亰亱亲亳亴亵亶亷亸亹人亻亼亽亾亿什仁仂仃仄仅仆仇仈仉今介仌仍从仏仐仑仒仓仔仕" + @@ -44,7 +48,7 @@ export class UnicodeLanguages { "佸佹佺佻佼佽佾使侀侁侂侃侄侅來侇侈侉侊例侌侍侎侏侐侑侒侓侔侕侖侗侘侙侚供侜依侞侟侠価侢侣侤侥侦侧侨侩侪侫侬侭" + "侮侯侰侱侲侳侴侵侶侷侸侹侺侻侼侽侾便俀俁係促俄俅俆俇俈俉俊俋俌俍俎俏俐俑俒俓俔俕俖俗俘俙俚俛俜保俞俟俠信俢俣" + "俤俥俦俧俨俩俪俫俬俭修俯俰俱俲俳俴俵俶俷俸俹俺俻俼俽俾俿倀倁倂倃倄倅倆倇倈倉倊個倌倍倎倏倐們倒倓倔倕倖倗倘候" + - "倚倛倜倝倞借倠倡倢倣値倥倦倧倨倩倪倫倬倭倮倯倰倱倲倳倴倵倶倷倸倹债倻值倽倾倿偀偁偂偃偄偅偆假偈偉偊偋偌偍偎偏" + + "倚倛倜倝倞借倠倡倢倣値倥倦倧倨倩倪倬倭倮倯倰倱倲倳倴倵倶倷倸倹债倻值倽倾倿偀偁偂偃偄偅偆假偈偉偊偋偌偍偎偏" + "偐偑偒偓偔偕偖偗偘偙做偛停偝偞偟偠偡偢偣偤健偦偧偨偩偪偫偬偭偮偯偰偱偲偳側偵偶偷偸偹偺偻偼偽偾偿傀傁傂傃傄傅" + "傆傇傈傉傊傋傌傍傎傏傐傑傒傓傔傕傖傗傘備傚傛傜傝傞傟傠傡傢傣傤傥傦傧储傩傪傫催傭傮傯傰傱傲傳傴債傶傷傸傹傺傻" + "傼傽傾傿僀僁僂僃僄僅僆僇僈僉僊僋僌働僎像僐僑僒僓僔僕僖僗僘僙僚僛僜僝僞僟僠僡僢僣僤僥僦僧僨僩僪僫僬僭僮僯僰僱" + diff --git a/src/core/words/Word.js b/src/core/words/Word.js index d218268..383ff0a 100644 --- a/src/core/words/Word.js +++ b/src/core/words/Word.js @@ -58,5 +58,15 @@ export class Word { } } - #found = (word) => Context.getWordbookService().getWordbookCache().get(word); + #found = (word) => { + try { + const wordbookService = Context.getWordbookService(); + if (!wordbookService || !wordbookService.isWordbookReady()) { + return false; // Если словарь не готов, считаем слово не найденным + } + return wordbookService.getWordbookCache().get(word); + } catch (error) { + return false; // В случае ошибки считаем слово не найденным + } + }; } \ No newline at end of file diff --git a/src/core/words/Wordbook.js b/src/core/words/Wordbook.js deleted file mode 100644 index 7536c51..0000000 --- a/src/core/words/Wordbook.js +++ /dev/null @@ -1,81 +0,0 @@ -import {Pages} from "./Pages"; - -export class Wordbook { - - #pages; - #cache; - - constructor() { - this.#cache = new Map(); - this.#pages = new Pages(0, 0); - } - - remove = (word) => { - this.#cache.delete(word); - this.#pages = new Pages(this.#cache.size, 50); - this.#pages.calcPagesCount(); - } - - set = (list) => { - list.forEach((bundle) => { - this.#cache.set(bundle.word, bundle.level); - }); - this.#pages = new Pages(this.#cache.size, 50); - this.#pages.calcPagesCount(); - return this; - } - - get = () => { - return this.#cache; - } - - getPages = () => { - return this.#pages; - } - - getPage = (page) => { - let index = 0; - const result = new Map(); - this.#cache.forEach((level, word) => { - this.#pages.isIndexOnPage(page, index) && result.set(word, level); - index++; - }); - return result; - } - - toObject = () => { - const wordbooks = {}; - this.#toPieces().forEach((wordbook, index) => { - wordbooks[this.getName(index)] = wordbook; - }); - return wordbooks; - } - - getName = (number) => { - return "wordbook" + number; - } - - #toList = () => { - const list = []; - this.#cache.forEach((level, word) => { - list.push({word: word, level: level}) ; - }); - return list; - } - - #toPieces = () => { - const pieces = [[]]; - this.#toList().forEach((bundle) => this.#putInPiece(pieces, bundle)); - return pieces; - } - - #putInPiece = (pieces, bundle) => { - const counter = pieces.length - 1; - if (pieces[counter].length < 100) { - pieces[counter].push(bundle); - } else { - pieces.push([]); - this.#putInPiece(pieces, bundle); - } - } -} \ No newline at end of file diff --git a/src/core/words/WordbookService.js b/src/core/words/WordbookService.js deleted file mode 100644 index fe928be..0000000 --- a/src/core/words/WordbookService.js +++ /dev/null @@ -1,83 +0,0 @@ -import {Logger} from "../Logger"; -import {Store} from "../Store"; -import {Wordbook} from "./Wordbook"; - -export class WordbookService { - - #storage; - #logger; - #wordbook; - - #executeAfter; - - executeAfter = (after) => { - this.#executeAfter = after; - } - - constructor() { - this.#storage = new Store(); - this.#logger = new Logger(); - this.#wordbook = new Wordbook(); - } - - set = (words) => { - this.#wordbook.set(words); - this.#updateStorage(); - } - - remove = (word) => { - this.#wordbook.remove(word); - this.#updateStorage(); - } - - #updateStorage = () => { - const wordbooks = this.#wordbook.toObject(); - this.#storage.saveWordbooks(wordbooks); - } - - getWordbook = () => { - return this.#wordbook; - } - - getFilteredWordbook = (filter) => { - const filtered = []; - this.#wordbook.get().forEach((level, word) => word && word.includes(filter) && filtered.push({word, level})); - const wordbook = new Wordbook(); - wordbook.set(filtered); - return wordbook; - } - - getWordbookCache = () => { - return this.#wordbook.get(); - } - - /** - * Выгружаем wordbook из storage. - * - * Поскольку невозможно хранить его целиком, - * грузим кусками по 100 слов в каждом. - */ - loadWordbooks = () => { - this.#preload(0); - } - - #preload = (number) => { - const name = this.#wordbook.getName(number); - this.#logger.log(`Loaded ${name} from storage`); - this.#storage.getByName(name).then(wordbook => this.#load(wordbook, number)); - } - - #load = (wordbook, number) => { - if (wordbook) { - this.#wordbook.set(wordbook); - this.#loadNext(number); - } else { - this.#executeAfter(); - } - } - - #loadNext = (number) => { - const next = number + 1; - this.#preload(next); - } -} \ No newline at end of file diff --git a/src/page.js b/src/page.js index fa8cfa2..c020933 100644 --- a/src/page.js +++ b/src/page.js @@ -3,4 +3,8 @@ import {PageService} from "./page/PageService"; const service = new PageService(); const app = new App(service); -app.start(); \ No newline at end of file + +// Запускаем приложение асинхронно +app.start().catch(error => { + console.error('Failed to start page app:', error); +}); \ No newline at end of file diff --git a/src/page/PageService.js b/src/page/PageService.js index bdaca7d..f0f76fc 100644 --- a/src/page/PageService.js +++ b/src/page/PageService.js @@ -1,5 +1,5 @@ import {Logger} from "../core/Logger"; -import {Store} from "../core/Store"; +import {ApiSettingsService} from "../core/api/ApiSettingsService.js"; import {Styles} from "./render/styles/Styles"; import {Context} from "../core/Context"; import {QueueProcessor} from "./queue/QueueProcessor"; @@ -10,18 +10,39 @@ export class PageService { #logger; #styles; - #storage; + #settingsService; constructor() { this.#logger = new Logger(); - this.#storage = new Store(); + this.#settingsService = new ApiSettingsService(); this.#styles = new Styles(); } - run = () => { - this.#storage.appParams().then(enable => { + run = async () => { + try { + // Инициализируем API сервис настроек + await this.#settingsService.initialize(); + + // Добавляем настройки в контекст для использования в UnicodeLanguages + const settings = this.#settingsService.getSettings(); + Context.add("settings", settings); + + const enable = this.#settingsService.isEnabled(); this.#joinPoint(enable, this.#server, this.#local); - }); + } catch (error) { + this.#logger.log(`PageService initialization failed: ${error.message}`); + // Fallback на дефолтные настройки + const defaultSettings = { + enable: true, + russian: true, + english: true, + china: false, + korean: true + }; + Context.add("settings", defaultSettings); + // Fallback на отключенное состояние + this.#joinPoint(false, this.#server, this.#local); + } } #joinPoint = (enable, serverLogic, localLogic) => { diff --git a/src/page/queue/QueueProcessor.js b/src/page/queue/QueueProcessor.js index d7af9dd..8838a51 100644 --- a/src/page/queue/QueueProcessor.js +++ b/src/page/queue/QueueProcessor.js @@ -3,12 +3,14 @@ import {Context} from "../../core/Context"; import {Parser} from "../parser/Parser"; import {Queue} from "./Queue"; import {Logger} from "../../core/Logger"; +import {franc} from "franc"; export class QueueProcessor { #builder; #parser; #logger; + #isWordbookReady = false; constructor() { Context.add("render-queue", new Queue()); @@ -17,15 +19,121 @@ export class QueueProcessor { this.#logger = new Logger(); this.#parser = new Parser(); this.#builder = new DOMBuilder(); + + // Проверяем готовность словаря + this.#checkWordbookReady(); + + // Подписываемся на уведомления о готовности словаря + this.#subscribeToWordbookReady(); + } + + #checkWordbookReady = () => { + const checkInterval = setInterval(() => { + try { + const wordbookService = Context.getWordbookService(); + if (wordbookService && wordbookService.isWordbookReady()) { + const cache = wordbookService.getWordbookCache(); + this.#logger.log(`Wordbook is ready with ${cache.size} words`); + this.#isWordbookReady = true; + clearInterval(checkInterval); + + // Принудительно запускаем перерендер после загрузки словаря + this.#forceRerenderAfterWordbookReady(); + } else if (wordbookService && wordbookService.isInitialized()) { + const cache = wordbookService.getWordbookCache(); + this.#logger.log(`Wordbook initialized but cache has ${cache.size} words, waiting for data...`); + } else { + this.#logger.log('Wordbook service not initialized, waiting...'); + } + } catch (error) { + this.#logger.log(`Error checking wordbook ready: ${error.message}`); + } + }, 500); // Проверяем каждые 500мс + + // Таймаут на случай, если данные не загрузятся + setTimeout(() => { + if (!this.#isWordbookReady) { + this.#logger.log('Wordbook ready timeout, starting with empty cache'); + this.#isWordbookReady = true; + clearInterval(checkInterval); + + // Принудительно запускаем перерендер даже при таймауте + this.#forceRerenderAfterWordbookReady(); + } + }, 10000); // 10 секунд таймаут + } + + #subscribeToWordbookReady = () => { + // Подписываемся на уведомления о готовности словаря + setTimeout(() => { + try { + const wordbookService = Context.getWordbookService(); + if (wordbookService && typeof wordbookService.onWordbookReady === 'function') { + wordbookService.onWordbookReady(() => { + this.#logger.log('Received wordbook ready notification'); + if (!this.#isWordbookReady) { + this.#isWordbookReady = true; + this.#forceRerenderAfterWordbookReady(); + } + }); + } + + // Если словарь уже готов при подписке, запускаем перерендер + if (wordbookService && wordbookService.isWordbookReady() && !this.#isWordbookReady) { + this.#logger.log('Wordbook already ready at subscription time'); + this.#isWordbookReady = true; + this.#forceRerenderAfterWordbookReady(); + } + } catch (error) { + this.#logger.log(`Error subscribing to wordbook ready: ${error.message}`); + } + }, 1000); // Небольшая задержка для инициализации + } + + #forceRerenderAfterWordbookReady = () => { + this.#logger.log('Forcing rerender after wordbook ready...'); + + // Принудительно запускаем парсинг страницы заново + setTimeout(() => { + try { + const body = window.document.querySelector('body'); + if (body) { + this.#logger.log('Re-parsing page after wordbook ready'); + this.#parser.putInQueue(body); + } + } catch (error) { + this.#logger.log(`Error re-parsing page: ${error.message}`); + } + }, 100); + + // Принудительно запускаем рендеринг + setTimeout(() => { + try { + this.#logger.log('Forcing page rebuild after wordbook ready'); + this.#builder.rebuildPage(); + } catch (error) { + this.#logger.log(`Error forcing rebuild: ${error.message}`); + } + }, 200); } runInfinityParsing = () => { - this.#startPageParsing(); - this.#startTextsParsing(); + // Ждём готовности словаря перед началом парсинга + const startParsing = () => { + if (this.#isWordbookReady) { + this.#logger.log('Starting infinite parsing - wordbook is ready'); + this.#startPageParsing(); + this.#startTextsParsing(); + } else { + setTimeout(startParsing, 100); + } + }; + startParsing(); } #startPageParsing = () => { let body = window.document.querySelector('body'); + this.#detectPageLanguage(body); this.#parser.putInQueue(body); setInterval(() => { if (this.#isParsingQueueReady() && this.#isParsingPageQueueReady()) { @@ -34,6 +142,24 @@ export class QueueProcessor { }, 100); } + #detectPageLanguage = (bodyElement) => { + try { + // Получаем весь текст со страницы для анализа + const pageText = bodyElement.textContent || bodyElement.innerText || ''; + + // Ограничиваем текст для более быстрого анализа (первые 1000 символов) + const sampleText = pageText.substring(0, 1000); + + // Определяем язык с помощью franc + const detectedLanguage = franc(sampleText); + + this.#logger.log(`Detected page language: ${detectedLanguage}`); + + } catch (error) { + this.#logger.log(`Error detecting page language: ${error.message}`); + } + } + #startTextsParsing = () => { setInterval(() => { if (this.#isParsingQueueReady() && this.#isParsingTextsQueueReady()) { @@ -41,13 +167,22 @@ export class QueueProcessor { } }, 100); } -А + runInfinityRender = () => { - setInterval(() => { - if (this.#isRenderQueueReady()) { - this.#builder.rebuildPage(); + // Ждём готовности словаря перед началом рендеринга + const startRender = () => { + if (this.#isWordbookReady) { + this.#logger.log('Starting infinite render - wordbook is ready'); + setInterval(() => { + if (this.#isRenderQueueReady()) { + this.#builder.rebuildPage(); + } + }, 100); + } else { + setTimeout(startRender, 100); } - }, 100); + }; + startRender(); } #isRenderQueueReady = () => { diff --git a/src/page/render/PageWord.js b/src/page/render/PageWord.js index 383daca..20a3a07 100644 --- a/src/page/render/PageWord.js +++ b/src/page/render/PageWord.js @@ -38,12 +38,25 @@ export class PageWord { return ref; } - #saveWord = () => { - const level = Levels.BEGINNER.name; - const word = this.#word.getClear(); - this.#wordbookService.set([{word, level}]); - this.#renderer.renderAll(word, level); - this.#renderer.onHoverAll(this.#word.getClear()); + #saveWord = async () => { + try { + const level = Levels.BEGINNER.name; + const word = this.#word.getClear(); + + console.log('Attempting to save word:', word, 'with level:', level); + console.log('WordbookService:', this.#wordbookService); + + await this.#wordbookService.set([{word, level}]); + + console.log('Word saved successfully'); + + this.#renderer.renderAll(word, level); + this.#renderer.onHoverAll(this.#word.getClear()); + } catch (error) { + console.error('Failed to save word:', error); + // Показываем пользователю ошибку + alert(`Failed to save word: ${error.message}`); + } } #pullInContext = () => (this.#word.getClear() !== " ") && Context.get("notSavedWords").add(this.#word.getClear()); diff --git a/src/popup.js b/src/popup.js index cf1b288..420bd24 100644 --- a/src/popup.js +++ b/src/popup.js @@ -3,4 +3,8 @@ import {PopupService} from "./popup/PopupService"; const service = new PopupService(); const app = new App(service); -app.start(); \ No newline at end of file + +// Запускаем приложение асинхронно +app.start().catch(error => { + console.error('Failed to start popup app:', error); +}); \ No newline at end of file diff --git a/src/popup/deprecated/wordbook.js b/src/popup/deprecated/wordbook.js deleted file mode 100644 index 0691507..0000000 --- a/src/popup/deprecated/wordbook.js +++ /dev/null @@ -1,55 +0,0 @@ -import {enumForEach} from '../../core/enum.js'; - -const displayWordbook = () => { - chrome.storage.local.get(['wordbook3'], function(app) { - getWordbook(app.wordbook3); - const wordbook = getWordbook(); - wordbook.forEach((level, word) => displayWord(word, level)); - }); -}; - -const displayWord = (word, level) => { - const wordbook = getWordbookElement(); - const row = buildRow(word, level); - wordbook.appendChild(row); -} - -const getWordbookElement = () => window.document.getElementsByClassName('words')[0]; - -const buildRow = (word, level) => { - const row = create('div'); - row.textContent = word; - configureSelect(row, level); - return row; -}; - -const configureSelect = (row, level) => { - const select = create('select'); - addClass(select,'level'); - row.appendChild(select); - addOptions(select, level); - return select; -}; - -const addOptions = (select, selected) => { - enumForEach(Levels, (level) => { - configureOption(select, level.name, selected); - }); -} - -const configureOption = (select, value, selected) => { - const option = document.createElement('option'); - option.value = value; - option.innerText = value; - option.selected = selected === value; - select.appendChild(option) -}; - -const configureColumn = (content, className, parent) => { - const span = create('span'); - span.innerText = content; - span.classList.add(className); - parent.appendChild(span); -}; - -displayWordbook(); \ No newline at end of file diff --git a/src/popup/scroll/Filter.js b/src/popup/scroll/Filter.js index 0d018b9..6948116 100644 --- a/src/popup/scroll/Filter.js +++ b/src/popup/scroll/Filter.js @@ -11,11 +11,17 @@ export class Filter { this.#setupInput(); } + setBuildPageButtons = (buildPageButtons) => { + this.#buildPageButtons = buildPageButtons; + } + #setupInput = () => { const filterInput = window.document.getElementById("filter-terms"); filterInput.addEventListener("change", (e) => { this.#filter = e.target.value; - this.#buildPageButtons(0); + if (this.#buildPageButtons) { + this.#buildPageButtons(0); + } this.#fillScroll(0); }); } diff --git a/src/popup/scroll/WordbookScroll.js b/src/popup/scroll/WordbookScroll.js index 62a6a54..f5de1d7 100644 --- a/src/popup/scroll/WordbookScroll.js +++ b/src/popup/scroll/WordbookScroll.js @@ -13,8 +13,9 @@ export class WordbookScroll { constructor() { this.#wordbookService = Context.getWordbookService(); this.#wordsAppender = new WordsAppender(); - this.#pageButtons = new ChangePageButtons(this.fillScroll); - this.#filter = new Filter(this.#pageButtons.buildPageButtons, this.fillScroll); + this.#filter = new Filter(null, this.fillScroll); + this.#pageButtons = new ChangePageButtons(this.fillScroll, this.#filter); + this.#filter.setBuildPageButtons(this.#pageButtons.buildPageButtons); Context.add("filter", this.#filter); } @@ -47,20 +48,20 @@ export class WordbookScroll { select.addEventListener("change", (event) => this.#changeLevel(event, word)); } - #changeWord = (event, word) => { + #changeWord = async (event, word) => { const level = this.#wordbookService.getWordbookCache().get(word); const edited = event.target.value; - this.#wordbookService.remove(word); - this.#updateWord(edited, level); + await this.#wordbookService.remove(word); + await this.#updateWord(edited, level); this.fillScroll(0); } - #changeLevel = (event, word) => { + #changeLevel = async (event, word) => { const level = event.target.value; - this.#updateWord(word, level); + await this.#updateWord(word, level); } - #updateWord = (word, level) => { - this.#wordbookService.set([{word, level}]); + #updateWord = async (word, level) => { + await this.#wordbookService.set([{word, level}]); } } \ No newline at end of file diff --git a/src/popup/settings/SettingsService.js b/src/popup/settings/SettingsService.js index d8aa246..5f29dc6 100644 --- a/src/popup/settings/SettingsService.js +++ b/src/popup/settings/SettingsService.js @@ -1,47 +1,66 @@ import {SettingsBuilder} from "./SettingsBuilder"; +import {ApiSettingsService} from "../../core/api/ApiSettingsService.js"; export class SettingsService { #builder; - #settings; + #settingsService; constructor() { this.#builder = new SettingsBuilder(); - this.#settings = {enable: true, russian: true, english: true, china: true, korean: true} + this.#settingsService = new ApiSettingsService(); } - fillSettings = () => { - chrome.storage.local.get(['enable', 'russian', "english", "china", "korean"], (settings) => this.#setupSettings(settings)); + fillSettings = async () => { + try { + // Инициализируем API сервис настроек + await this.#settingsService.initialize(); + + const settings = this.#settingsService.getSettings(); + this.#setupSettings(settings); + } catch (error) { + console.error('Failed to load settings:', error); + // Fallback на дефолтные настройки + const defaultSettings = { + enable: true, + russian: true, + english: true, + china: false, + korean: true + }; + this.#setupSettings(defaultSettings); + } } #setupSettings = (settings) => { - this.#settings = settings; this.#builder.loadLevers(); - this.#setupEnableAppLever(); - this.#setupLangLevers(); + this.#setupEnableAppLever(settings.enable); + this.#setupLangLevers(settings); } - #setupEnableAppLever = () => { + #setupEnableAppLever = (enable) => { this.#builder.setupAppEnableLever(this.#changeEnable); - this.#builder.renderAppEnableLever(this.#settings.enable); + this.#builder.renderAppEnableLever(enable); } - #setupLangLevers = () => { + #setupLangLevers = (settings) => { //TODO:: Clean code (Remove duplicate) this.#builder.setupRussianEnableLever(this.#changeEnable); - this.#builder.renderRussianEnableLever(this.#settings.russian); + this.#builder.renderRussianEnableLever(settings.russian); this.#builder.setupKoreanEnableLever(this.#changeEnable); - this.#builder.renderKoreanEnableLever(this.#settings.korean); + this.#builder.renderKoreanEnableLever(settings.korean); this.#builder.setupEnglishEnableLever(this.#changeEnable); - this.#builder.renderEnglishEnableLever(this.#settings.english); + this.#builder.renderEnglishEnableLever(settings.english); this.#builder.setupChinaEnableLever(this.#changeEnable); - this.#builder.renderChinaEnableLever(this.#settings.china); + this.#builder.renderChinaEnableLever(settings.china); } - #changeEnable = (lever, name) => { - this.#settings[name] = !this.#settings[name]; - chrome.storage.local.set(this.#settings, () => { - this.#builder.renderLever(lever, this.#settings[name]); - }); + #changeEnable = async (lever, name) => { + try { + await this.#settingsService.updateSetting(name, !this.#settingsService.getSetting(name)); + this.#builder.renderLever(lever, this.#settingsService.getSetting(name)); + } catch (error) { + console.error('Failed to update setting:', error); + } }; } \ No newline at end of file