diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
new file mode 100644
index 0000000..1d16081
--- /dev/null
+++ b/.github/workflows/deploy-docs.yml
@@ -0,0 +1,49 @@
+name: Deploy Docs
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - 'docs/**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+ - name: Install dependencies
+ run: npm ci
+ - name: Build with VitePress
+ run: npm run docs:build
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: docs/.vitepress/dist
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/versioning.yaml b/.github/workflows/versioning.yaml
index e0a61a8..1e18da2 100644
--- a/.github/workflows/versioning.yaml
+++ b/.github/workflows/versioning.yaml
@@ -1,4 +1,4 @@
-name: 버전 관리
+name: 버전 관리 및 릴리즈
on:
pull_request:
@@ -11,169 +11,115 @@ permissions:
pull-requests: read
jobs:
- bump-version:
+ release:
runs-on: ubuntu-latest
- if: github.event.pull_request.merged == true # PR이 병합된 경우에만 실행
+ if: github.event.pull_request.merged == true
steps:
- # 1단계: 리포지토리 체크아웃
- name: 리포지토리 체크아웃
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
- fetch-depth: 0 # 모든 히스토리와 태그를 가져옴
+ fetch-depth: 0
- # 2단계: Node.js 설정
- name: Node.js 설정
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: '18' # Node.js 18 이상으로 설정
+ node-version: '20'
- # 3단계: 의존성 설치
- name: 의존성 설치
run: npm install
- # 4단계: PR 번호 가져오기
- - name: PR 번호 가져오기
- id: get_pr_number
- uses: actions/github-script@v6
+ - name: PR 라벨에서 버전 범프 결정
+ id: version
+ uses: actions/github-script@v7
with:
script: |
- const pr = context.payload.pull_request;
- if (!pr) {
- throw new Error('해당 커밋과 연관된 PR을 찾을 수 없습니다.');
+ const labels = context.payload.pull_request.labels.map(l => l.name);
+ let bump = 'patch';
+ if (labels.includes('🔖 major')) bump = 'major';
+ else if (labels.includes('🔖 minor')) bump = 'minor';
+
+ // 최신 태그에서 현재 버전 추출
+ const { execSync } = require('child_process');
+ let currentVersion;
+ try {
+ const latestTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf-8' }).trim();
+ currentVersion = latestTag.replace(/^v/, '');
+ } catch {
+ currentVersion = '0.0.0';
}
- return pr.number;
- # 5단계: PR 라벨 가져오기 (콤마로 구분된 문자열로 반환)
- - name: PR 라벨 가져오기
- id: get_labels
- uses: actions/github-script@v6
- with:
- script: |
- const prNumber = ${{ steps.get_pr_number.outputs.result }};
- const pr = await github.rest.pulls.get({
- owner: context.repo.owner,
- repo: context.repo.repo,
- pull_number: prNumber,
- });
- const labels = pr.data.labels.map(label => label.name).join(',');
- return labels;
-
- # 6단계: PR 라벨 출력하기
- - name: PR 라벨 출력하기
- run: |
- echo "PR 라벨: ${{ steps.get_labels.outputs.result }}"
+ const [major, minor, patch] = currentVersion.split('.').map(Number);
+ let newVersion;
+ if (bump === 'major') newVersion = `${major + 1}.0.0`;
+ else if (bump === 'minor') newVersion = `${major}.${minor + 1}.0`;
+ else newVersion = `${major}.${minor}.${patch + 1}`;
- # 7단계: 파일 존재 확인 및 목록 출력
- - name: List files to verify manifest.config.ts
- run: |
- echo "현재 작업 디렉토리: $(pwd)"
- ls -al
- find . -name "manifest.config.ts"
+ core.setOutput('new_version', newVersion);
+ core.setOutput('bump', bump);
- # 8단계: 다음 버전 결정
- - name: 다음 버전 결정
- id: version
- run: |
- set -euo pipefail
- IFS=$'\n\t'
-
- # 최신 태그 가져오기
- LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "v0.0.0")
- echo "최신 태그: $LATEST_TAG"
-
- # 현재 버전 추출
- CURRENT_VERSION=${LATEST_TAG#v}
- echo "현재 버전: $CURRENT_VERSION"
-
- # 버전 분할
- IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
-
- # PR 라벨 가져오기 (콤마로 구분된 문자열)
- LABELS=$(echo "${{ steps.get_labels.outputs.result }}" | tr -d '"')
- echo "PR 라벨: $LABELS"
-
- # 콤마를 기준으로 배열로 변환
- IFS=',' read -r -a LABEL_ARRAY <<< "$LABELS"
-
- # 라벨에 따라 버전 증가 (우선순위: major > minor > patch)
- NEW_VERSION=""
- for label in "${LABEL_ARRAY[@]}"; do
- label=$(echo "$label" | xargs) # 공백 제거
- if [[ "$label" == "🔖 major" ]]; then
- NEW_MAJOR=$((MAJOR + 1))
- NEW_VERSION="$NEW_MAJOR.0.0"
- break
- elif [[ "$label" == "🔖 minor" ]]; then
- NEW_MINOR=$((MINOR + 1))
- NEW_VERSION="$MAJOR.$NEW_MINOR.0"
- break
- elif [[ "$label" == "🔖 patch" ]]; then
- NEW_PATCH=$((PATCH + 1))
- NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
- break
- fi
- done
-
- # 라벨이 없거나 인식할 수 없는 경우 패치 버전 증가
- if [[ -z "$NEW_VERSION" ]]; then
- echo "라벨이 지정되지 않았거나 인식할 수 없습니다. 기본적으로 패치 버전을 증가시킵니다."
- NEW_PATCH=$((PATCH + 1))
- NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
- fi
-
- echo "새 버전: $NEW_VERSION"
-
- # 출력 설정
- echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
-
- # 9단계: package.json 및 manifest.config.ts 버전 업데이트
- - name: package.json 및 manifest.config.ts 버전 업데이트
- run: |
- NEW_VERSION=${{ steps.version.outputs.new_version }}
- echo "새 버전: $NEW_VERSION"
-
- # package.json 업데이트
- echo "package.json을 버전 $NEW_VERSION으로 업데이트합니다."
- npm version $NEW_VERSION --no-git-tag-version
-
- # manifest.config.ts 업데이트
- FILE_PATH="manifest.config.ts"
- if [ -f "$FILE_PATH" ]; then
- echo "manifest.config.ts를 버전 $NEW_VERSION으로 업데이트합니다."
- sed -i "s/\(version: '\)[0-9]\+\.[0-9]\+\.[0-9]\+'/\1$NEW_VERSION'/" "$FILE_PATH"
- echo "🔄 업데이트된 manifest.config.ts 내용:"
- cat "$FILE_PATH"
- else
- echo "❌ manifest.config.ts 파일을 찾을 수 없습니다!"
- exit 1
- fi
-
- # 10단계: 변경 사항 커밋 및 푸시
- - name: 변경 사항 커밋 및 푸시
+ - name: package.json 버전 업데이트
+ run: npm version ${{ steps.version.outputs.new_version }} --no-git-tag-version
+
+ - name: 프로덕션 빌드
+ run: npm run build
+
+ - name: dist 압축
+ run: cd dist && zip -r ../dotbugi-v${{ steps.version.outputs.new_version }}.zip .
+
+ - name: README 배지 버전 업데이트
+ run: sed -i "s/Chrome_Web_Store-v[0-9]\+\.[0-9]\+\.[0-9]\+/Chrome_Web_Store-v${{ steps.version.outputs.new_version }}/" README.md
+
+ - name: 변경사항 커밋 및 태그
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git add package.json manifest.config.ts
- git commit -m "🔖 chore: bump version to v${{ steps.version.outputs.new_version }}"
- git push origin main
+ git add package.json package-lock.json README.md
+ git commit -m "chore: bump version to v${{ steps.version.outputs.new_version }}"
+ git tag v${{ steps.version.outputs.new_version }}
+ git push origin main --follow-tags
+
+ - name: 릴리즈 노트 생성
+ id: changelog
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const { execSync } = require('child_process');
+
+ // 이전 태그 찾기
+ let prevTag;
+ try {
+ prevTag = execSync('git describe --tags --abbrev=0 HEAD~1', { encoding: 'utf-8' }).trim();
+ } catch {
+ prevTag = execSync('git rev-list --max-parents=0 HEAD', { encoding: 'utf-8' }).trim();
+ }
- # 11단계: 새 태그 생성 및 푸시
- - name: 새 태그 생성 및 푸시
- run: |
- NEW_VERSION=${{ steps.version.outputs.new_version }}
- git tag -a v$NEW_VERSION -m "Release v$NEW_VERSION"
- git push origin v$NEW_VERSION
-
- # 12단계: 릴리스 생성
- - name: 릴리스 생성
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ const newVersion = '${{ steps.version.outputs.new_version }}';
+
+ // PR에 포함된 커밋 가져오기
+ const commits = execSync(
+ `git log ${prevTag}..HEAD --pretty=format:"- %s (%h)" --no-merges`,
+ { encoding: 'utf-8' }
+ ).trim();
+
+ const pr = context.payload.pull_request;
+ const body = [
+ `## What's Changed`,
+ ``,
+ `**PR:** #${pr.number} ${pr.title}`,
+ ``,
+ `### Commits`,
+ commits,
+ ``,
+ `**Full Changelog:** https://github.com/${context.repo.owner}/${context.repo.repo}/compare/${prevTag}...v${newVersion}`,
+ ].join('\n');
+
+ core.setOutput('body', body);
+
+ - name: GitHub 릴리즈 생성
+ uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.new_version }}
- release_name: Release v${{ steps.version.outputs.new_version }}
- body: 'Release v${{ steps.version.outputs.new_version }}'
- draft: false
- prerelease: false
- generate_release_notes: true
+ name: v${{ steps.version.outputs.new_version }}
+ body: ${{ steps.changelog.outputs.body }}
+ files: dotbugi-v${{ steps.version.outputs.new_version }}.zip
diff --git a/.gitignore b/.gitignore
index 3d53d11..1b3be4b 100755
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ dist/
# 환경 설정 파일 (환경에 따라 사용)
.env
+.env.*
.env.local
.env.development.local
.env.test.local
@@ -46,4 +47,9 @@ temp/
dist
*.md
-!README.md
\ No newline at end of file
+!README.md
+!docs/**/*.md
+
+# VitePress
+docs/.vitepress/dist/
+docs/.vitepress/cache/
\ No newline at end of file
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
new file mode 100644
index 0000000..96aa955
--- /dev/null
+++ b/docs/.vitepress/config.ts
@@ -0,0 +1,100 @@
+import { defineConfig, type DefaultTheme } from 'vitepress';
+
+function guideSidebar(basic: string, advanced: string, calendar: string, faq: string, label: string, prefix = ''): DefaultTheme.SidebarItem[] {
+ return [{ text: label, items: [
+ { text: basic, link: `${prefix}/guide/basic` },
+ { text: advanced, link: `${prefix}/guide/advanced` },
+ { text: calendar, link: `${prefix}/guide/calendar` },
+ { text: faq, link: `${prefix}/guide/faq` },
+ ]}];
+}
+
+function updatesSidebar(label: string, changelog: string, prefix = ''): DefaultTheme.SidebarItem[] {
+ return [{ text: label, items: [{ text: changelog, link: `${prefix}/updates/changelog` }] }];
+}
+
+export default defineConfig({
+ title: '돋부기',
+ base: '/dotbugi/',
+ description: 'dotbugi 사용 가이드 및 업데이트 내역',
+
+ head: [
+ ['link', { rel: 'icon', href: '/dotbugi/favicon.png', type: 'image/png' }],
+ ['link', { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard-jp.min.css' }],
+ ],
+
+ locales: {
+ root: {
+ label: '한국어',
+ lang: 'ko-KR',
+ themeConfig: {
+ nav: [
+ { text: '가이드', link: '/guide/basic' },
+ { text: '업데이트', link: '/updates/changelog' },
+ { text: '설치', link: 'https://chromewebstore.google.com/detail/hsu-%EB%8F%8B%EB%B6%80%EA%B8%B0-%F0%9F%94%8E/fbhdnbombekihdhjcfiimiibfmikghch' },
+ ],
+ sidebar: {
+ '/guide/': guideSidebar('간단 사용 설명서', '고급 사용 설명서', 'Google 캘린더 연동', 'FAQ', '가이드'),
+ '/updates/': updatesSidebar('업데이트', '업데이트 로그'),
+ },
+ outline: { label: '목차' },
+ docFooter: { prev: '이전', next: '다음' },
+ },
+ },
+ en: {
+ label: 'English',
+ lang: 'en-US',
+ themeConfig: {
+ nav: [
+ { text: 'Guide', link: '/en/guide/basic' },
+ { text: 'Updates', link: '/en/updates/changelog' },
+ { text: 'Install', link: 'https://chromewebstore.google.com/detail/hsu-%EB%8F%8B%EB%B6%80%EA%B8%B0-%F0%9F%94%8E/fbhdnbombekihdhjcfiimiibfmikghch' },
+ ],
+ sidebar: {
+ '/en/guide/': guideSidebar('Quick Start', 'Advanced Guide', 'Google Calendar', 'FAQ', 'Guide', '/en'),
+ '/en/updates/': updatesSidebar('Updates', 'Changelog', '/en'),
+ },
+ outline: { label: 'On this page' },
+ docFooter: { prev: 'Previous', next: 'Next' },
+ },
+ },
+ ja: {
+ label: '日本語',
+ lang: 'ja-JP',
+ themeConfig: {
+ nav: [
+ { text: 'ガイド', link: '/ja/guide/basic' },
+ { text: '更新履歴', link: '/ja/updates/changelog' },
+ { text: 'インストール', link: 'https://chromewebstore.google.com/detail/hsu-%EB%8F%8B%EB%B6%80%EA%B8%B0-%F0%9F%94%8E/fbhdnbombekihdhjcfiimiibfmikghch' },
+ ],
+ sidebar: {
+ '/ja/guide/': guideSidebar('簡単ガイド', '詳細ガイド', 'Google カレンダー連携', 'よくある質問', 'ガイド', '/ja'),
+ '/ja/updates/': updatesSidebar('更新履歴', '更新ログ', '/ja'),
+ },
+ outline: { label: '目次' },
+ docFooter: { prev: '前へ', next: '次へ' },
+ },
+ },
+ zh: {
+ label: '中文',
+ lang: 'zh-CN',
+ themeConfig: {
+ nav: [
+ { text: '指南', link: '/zh/guide/basic' },
+ { text: '更新日志', link: '/zh/updates/changelog' },
+ { text: '安装', link: 'https://chromewebstore.google.com/detail/hsu-%EB%8F%8B%EB%B6%80%EA%B8%B0-%F0%9F%94%8E/fbhdnbombekihdhjcfiimiibfmikghch' },
+ ],
+ sidebar: {
+ '/zh/guide/': guideSidebar('快速入门', '高级指南', 'Google 日历', '常见问题', '指南', '/zh'),
+ '/zh/updates/': updatesSidebar('更新日志', '更新记录', '/zh'),
+ },
+ outline: { label: '目录' },
+ docFooter: { prev: '上一页', next: '下一页' },
+ },
+ },
+ },
+
+ themeConfig: {
+ socialLinks: [{ icon: 'github', link: 'https://github.com/hs-shell/dotbugi' }],
+ },
+});
diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css
new file mode 100644
index 0000000..029899f
--- /dev/null
+++ b/docs/.vitepress/theme/custom.css
@@ -0,0 +1,4 @@
+:root {
+ --vp-font-family-base: 'Pretendard JP', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
+ --vp-font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;
+}
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
new file mode 100644
index 0000000..149273e
--- /dev/null
+++ b/docs/.vitepress/theme/index.ts
@@ -0,0 +1,4 @@
+import DefaultTheme from 'vitepress/theme';
+import './custom.css';
+
+export default DefaultTheme;
diff --git a/docs/en/guide/advanced.md b/docs/en/guide/advanced.md
new file mode 100644
index 0000000..20d2440
--- /dev/null
+++ b/docs/en/guide/advanced.md
@@ -0,0 +1,135 @@
+# Advanced User Guide
+
+## Course Tracking Settings
+
+A tracking toggle button is displayed on each course card on the LMS main page.
+
+- **Blue check**: Tracking enabled — tasks will appear on the dashboard.
+- **Transparent**: Tracking disabled — hidden from the dashboard.
+
+By default, all regular courses are tracked, and extracurricular courses are excluded.
+
+| Before Tracking | After Adding Tracking |
+| :------------------------------------------: | :---------------------------------------------: |
+|  |  |
+
+## Search and Filter
+
+You can search by course name or title using the search bar at the top.
+
+Filter options:
+
+- **By course**: Show only specific courses
+- **By status**: Filter completed/incomplete items
+
+When a filter is applied, a "Filter applied" indicator appears.
+
+
+
+## Hiding Tasks
+
+You can hide items that you have already checked or no longer need.
+
+- **Right-click** a card → Select "Hide this task"
+- Right-click a course group header to hide all items for that course at once.
+
+Hidden items can be viewed and restored in the "Hidden Tasks" section of the Settings tab.
+
+| Hide | Unhide |
+| :-----------------------: | :---------------------------: |
+|  |  |
+
+## Course Page Status Badges
+
+Status badges are displayed next to activities (lectures/assignments/quizzes) on each course page.
+
+### Lectures (VOD)
+
+
+ Badge Status Meaning
+
+ Attended Attended Watched for the required attendance time or more
+ Watching · In 11 days Watching Partially watched (progress visualized)
+ Watching · In 23 hours Watching (urgent) Partially watched + less than 24 hours until deadline
+ Not Watched · In 6 days Not Watched Not yet watched
+ Not Watched · In 23 hours Not Watched (urgent) Less than 24 hours until deadline
+ Absent Absent Past due
+ Hidden Hidden Item has been hidden
+
+
+
+::: info Watching Badge
+The watching badge calculates and displays progress based on the study time data from the progress page.
+:::
+
+### Assignments / Quizzes
+
+
+
+
+**Assignments**
+
+
+ Badge Meaning
+
+ Submitted Assignment submitted
+ Not Submitted · In 6 days More than 1 day until deadline
+ Not Submitted · In 23 hours Less than 24 hours until deadline
+ Not Submitted (Closed) Past due
+ Hidden Item has been hidden
+
+
+
+
+
+
+**Quizzes**
+
+
+ Badge Meaning
+
+ Taken Quiz completed
+ Not Taken · In 6 days More than 1 day until deadline
+ Not Taken · In 23 hours Less than 24 hours until deadline
+ Not Taken (Closed) Past due
+ Hidden Item has been hidden
+
+
+
+
+
+
+
+
+## Auto-Play Player
+
+
+
+::: info Smart Playback
+- Lectures already marked as attended are automatically skipped.
+- If there is a previous watch position, playback resumes from that point.
+:::
+
+### Estimated End Time
+
+During playback, the estimated end time for the remaining lectures is displayed on the button.
+
+### Changing Playback Order
+
+You can change the playback order by dragging and dropping lectures in the sidebar list.
+
+### Page Leave Warning
+
+A warning is displayed when you try to leave the page during auto-playback.
+
+## Refresh
+
+
+
+You can refresh the data using the refresh button in the dropdown menu. A red dot appears when 5 minutes have passed since the last refresh. Data is automatically refreshed after 1 hour.
+
+## Contact
+
+
+
+You can contact us via email or KakaoTalk open chat at the bottom of the Settings tab.
diff --git a/docs/en/guide/basic.md b/docs/en/guide/basic.md
new file mode 100644
index 0000000..57c4cd6
--- /dev/null
+++ b/docs/en/guide/basic.md
@@ -0,0 +1,78 @@
+# Basic User Guide
+
+## Installation
+
+1. Search for **HSU Dotbugi** on the Chrome Web Store.
+2. Click the **Add to Chrome** button.
+3. After installation, navigate to the university LMS (learn.hansung.ac.kr).
+
+
+
+## First Launch
+
+Once installed, a Dotbugi button will appear at the bottom-right of the LMS page. Click it to open the dashboard.
+
+
+
+## Dashboard
+
+The dashboard consists of 3 tabs: **Online Lectures**, **Assignments**, and **Quizzes**.
+
+Each card displays the course name, title, and deadline, with colors that change based on status.
+
+
+
+
+**Online Lectures**
+
+
+ Badge Meaning
+
+ Attended Watching completed
+ Watching · In 11 days Partially watched
+ Not Watched · In 6 days Not watched
+ Absent Past due
+
+
+
+
+
+
+**Assignments / Quizzes**
+
+
+ Badge Meaning
+
+ Submitted Submitted/taken
+ Not Submitted · In 6 days More than 1 day until deadline
+ Not Taken · In 23 hours Less than 24 hours remaining
+ Not Submitted (Closed) Past due
+
+
+
+
+
+
+Clicking a card navigates directly to the corresponding lecture/assignment/quiz page.
+
+Data is automatically refreshed **every hour**, and also refreshes immediately when you return to the tab after more than an hour away. To refresh manually, click **⋮ Menu → Refresh** in the top-right corner. A red dot on the ⋮ button indicates the data is stale.
+
+
+
+## Auto-Play Player
+
+The sidebar that appears on the left side of the course page lets you automatically play unwatched lectures in sequence.
+
+1. Click the Dotbugi trigger on the left side of the course page.
+2. Press the **Start Playing** button to begin auto-playing from the first unwatched lecture.
+3. When all lectures are finished, it will be marked as "Completed".
+
+
+
+## Change Language
+
+You can choose from Korean, English, Chinese, or Japanese in the Settings tab.
+
+| | |
+| -------------------------------- | --------------------------------- |
+|  |  |
diff --git a/docs/en/guide/calendar.md b/docs/en/guide/calendar.md
new file mode 100644
index 0000000..ec48c33
--- /dev/null
+++ b/docs/en/guide/calendar.md
@@ -0,0 +1,39 @@
+# Google Calendar Integration
+
+## Connect
+
+1. Open the **Settings** tab in the dashboard.
+2. Click the **Login** button under "Google Calendar Integration".
+3. Log in with your Google account to complete the integration.
+
+
+
+## Sync
+
+After connecting, press the **Sync Tasks** button in the dropdown menu to add tasks to Google Calendar.
+
+
+
+Sync result notifications:
+
+- "N events have been added" — New items were added
+- "Already up to date" — No items to add
+- "Failed to add N events" — Some items failed
+
+## Calendar Color Coding
+
+| Color | Item |
+| ------------ | ---------------- |
+| Lavender | Online Lectures |
+| Blue | Assignments |
+| Orange | Quizzes |
+
+## Login Expiration
+
+A notification is displayed when your Google login expires. Click the "Log in again" button to reconnect.
+
+
+
+## Disconnect
+
+Click the **Logout** button in the Settings tab to disconnect the integration.
diff --git a/docs/en/guide/faq.md b/docs/en/guide/faq.md
new file mode 100644
index 0000000..42d38bd
--- /dev/null
+++ b/docs/en/guide/faq.md
@@ -0,0 +1,55 @@
+# Frequently Asked Questions
+
+## Installation & Setup
+
+::: details The Dotbugi button doesn't appear after installation
+Make sure you are logged in to the LMS (learn.hansung.ac.kr). Dotbugi only works on LMS pages. You may need to refresh the page after installation.
+:::
+
+::: details Can I use it on other browsers?
+Currently supported on Chrome and Chromium-based browsers (Edge, Brave, Arc, etc.). Firefox, Safari, and others are not supported.
+:::
+
+## Dashboard
+
+::: details Data isn't updating in real time
+Data is automatically refreshed every hour. To refresh immediately, click **⋮ Menu → Refresh**.
+:::
+
+::: details A specific course isn't showing on the dashboard
+The tracking toggle for that course may be turned off. On the LMS main page, click the button at the top-right of the course card to enable tracking.
+:::
+
+::: details Can I track community (non-credit) courses?
+Yes. Enable the tracking toggle for community courses on the LMS main page. They are disabled by default.
+:::
+
+## Auto Player
+
+::: details Auto playback won't start
+The player only appears on course pages (course/view.php). Click the Dotbugi icon in the left sidebar, then press "Start Watching".
+:::
+
+::: details Does it replay already attended lectures?
+No. Already attended lectures are automatically skipped. Partially watched lectures resume from where you left off.
+:::
+
+## Google Calendar
+
+::: details Why does it request Google permissions?
+Write access to Google Calendar API is needed to add events. Dotbugi does not access any other data.
+:::
+
+::: details Events don't appear in my calendar after syncing
+Check the "Other calendars" list in Google Calendar. The Dotbugi calendar may be hidden.
+:::
+
+## Other
+
+::: details Is my personal data sent to external servers?
+No. Dotbugi stores all data in the browser's local storage and does not transmit data to external servers. Communication with Google API only occurs during calendar sync.
+:::
+
+::: details Badge colors don't match the actual attendance status
+Try **⋮ Menu → Refresh** to fetch the latest data. There may be a delay in LMS data reflection.
+:::
diff --git a/docs/en/index.md b/docs/en/index.md
new file mode 100644
index 0000000..deed178
--- /dev/null
+++ b/docs/en/index.md
@@ -0,0 +1,147 @@
+---
+layout: home
+
+hero:
+ name: Dotbugi
+ tagline: Make your LMS more convenient
+ image:
+ src: /favicon.png
+ alt: Dotbugi
+ actions:
+ - theme: brand
+ text: User Guide
+ link: /en/guide/basic
+ - theme: alt
+ text: FAQ
+ link: /en/guide/faq
+ - theme: alt
+ text: Update Log
+ link: /en/updates/changelog
+---
+
+
+
+
+
+
Task Dashboard
+
View online lectures, assignments, and quizzes in one screen with color-coded deadline statuses.
+
+
+
Auto Continuous Playback
+
Automatically play unwatched lectures in sequence to complete your coursework quickly.
+
+
+
Google Calendar Integration
+
Automatically register assignment and quiz deadlines to Google Calendar so you never miss a due date.
+
+
+
Course Page Badges
+
Instantly check the attendance/submission status of each activity with badges on the course page.
+
+
+
Multi-language Support
+
Supports 4 languages: Korean, English, Chinese, and Japanese.
+
+
+
+
diff --git a/docs/en/updates/changelog.md b/docs/en/updates/changelog.md
new file mode 100644
index 0000000..f134fc6
--- /dev/null
+++ b/docs/en/updates/changelog.md
@@ -0,0 +1,9 @@
+# Update Log
+
+## v4.0.5
+
+- Course page status badges and current week highlight feature
+- Quiz submission tracking and task hiding feature
+- Per-course tracking selection and UX improvements
+- Popover settings tab and calendar integration improvements
+- Multi-language (i18n) support and LMS parsing localization
diff --git a/docs/guide/advanced.md b/docs/guide/advanced.md
new file mode 100644
index 0000000..639f6c1
--- /dev/null
+++ b/docs/guide/advanced.md
@@ -0,0 +1,135 @@
+# 고급 사용 설명서
+
+## 과목 트래킹 설정
+
+LMS 메인 페이지의 각 과목 카드에 트래킹 토글 버튼이 표시됩니다.
+
+- **파란색 체크**: 트래킹 중 — 대시보드에 할 일이 표시됩니다.
+- **반투명**: 트래킹 해제 — 대시보드에서 숨겨집니다.
+
+기본적으로 모든 정규 과목은 트래킹되며, 비교과 과목은 제외됩니다.
+
+| 트래킹 전 | 트래킹 추가 |
+| :------------------------------------: | :-----------------------------------: |
+|  |  |
+
+## 검색 및 필터
+
+상단 검색창에서 과목명이나 제목으로 검색할 수 있습니다.
+
+필터 옵션:
+
+- **과목별**: 특정 과목만 표시
+- **상태별**: 완료/미완료 항목 필터링
+
+필터가 적용되면 "필터 설정됨" 표시가 나타납니다.
+
+
+
+## 태스크 숨기기
+
+이미 확인했거나 불필요한 항목은 숨길 수 있습니다.
+
+- 카드를 **우클릭** → "이 태스크 숨기기" 선택
+- 과목 그룹 헤더를 우클릭하면 해당 과목의 모든 항목을 한번에 숨길 수 있습니다.
+
+숨긴 항목은 설정 탭의 "숨긴 태스크"에서 확인하고 복원할 수 있습니다.
+
+| 숨기기 | 숨기기 해제 |
+| :-------------------------: | :--------------------------------: |
+|  |  |
+
+## 강의 페이지 상태 배지
+
+각 강의의 코스 페이지에서 활동(강의/과제/퀴즈) 옆에 상태 배지가 표시됩니다.
+
+### 강의 (VOD)
+
+
+ 배지 상태 의미
+
+ 출석 출석 출석인정 요구시간 이상 시청 완료
+ 시청중 · 11일 후 시청중 일부 시청함 (진행률 시각화)
+ 시청중 · 23시간 후 시청중 (임박) 일부 시청 + 마감까지 24시간 이내
+ 미시청 · 6일 후 미시청 아직 시청하지 않음
+ 미시청 · 23시간 후 미시청 (임박) 마감까지 24시간 이내
+ 결석 결석 기한이 지남
+ 숨김 숨김 숨김 처리된 항목
+
+
+
+::: info 시청중 배지
+시청중 배지는 진도 페이지의 학습시간 데이터를 기반으로 진행률을 계산하여 표시합니다.
+:::
+
+### 과제 / 퀴즈
+
+
+
+
+**과제**
+
+
+ 배지 의미
+
+ 제출완료 과제 제출 완료
+ 미제출 · 6일 후 마감까지 1일 이상
+ 미제출 · 23시간 후 마감까지 24시간 이내
+ 미제출 (마감) 기한이 지남
+ 숨김 숨김 처리된 항목
+
+
+
+
+
+
+**퀴즈**
+
+
+ 배지 의미
+
+ 응시완료 퀴즈 응시 완료
+ 미응시 · 6일 후 마감까지 1일 이상
+ 미응시 · 23시간 후 마감까지 24시간 이내
+ 미응시 (마감) 기한이 지남
+ 숨김 숨김 처리된 항목
+
+
+
+
+
+
+
+
+## 자동 재생 플레이어
+
+
+
+::: info 스마트 재생
+- 이미 출석 처리된 강의는 자동으로 건너뜁니다.
+- 이전에 시청한 위치가 있으면 해당 지점부터 이어서 재생합니다.
+:::
+
+### 예상 종료 시간
+
+재생 중 버튼에 남은 강의의 예상 종료 시간이 표시됩니다.
+
+### 재생 순서 변경
+
+사이드바의 강의 목록에서 드래그 앤 드롭으로 재생 순서를 바꿀 수 있습니다.
+
+### 페이지 이탈 경고
+
+자동 재생 중 페이지를 떠나려 하면 경고가 표시됩니다.
+
+## 새로고침
+
+
+
+드롭다운 메뉴의 새로고침 버튼으로 데이터를 갱신할 수 있습니다. 마지막 갱신 후 5분이 지나면 빨간 점이 표시됩니다. 1시간이 지나면 자동으로 데이터가 갱신됩니다.
+
+## 문의
+
+
+
+설정 탭 하단에서 이메일 또는 카카오톡 오픈채팅으로 문의할 수 있습니다.
diff --git a/docs/guide/basic.md b/docs/guide/basic.md
new file mode 100644
index 0000000..948e288
--- /dev/null
+++ b/docs/guide/basic.md
@@ -0,0 +1,78 @@
+# 간단 사용 설명서
+
+## 설치
+
+1. Chrome 웹스토어에서 **HSU 돋부기**를 검색합니다.
+2. **Chrome에 추가** 버튼을 클릭합니다.
+3. 설치 완료 후, 학교 LMS(learn.hansung.ac.kr)에 접속합니다.
+
+
+
+## 첫 실행
+
+설치가 완료되면 LMS 페이지 우측 하단에 돋부기 버튼이 나타납니다. 클릭하면 대시보드가 열립니다.
+
+
+
+## 대시보드
+
+대시보드는 **온라인 강의**, **과제**, **퀴즈** 3개 탭으로 구성됩니다.
+
+각 카드에는 과목명, 제목, 마감일이 표시되며 상태에 따라 색상이 달라집니다.
+
+
+
+
+**온라인 강의**
+
+
+ 배지 의미
+
+ 출석 시청 완료
+ 시청중 · 11일 후 일부 시청함
+ 미시청 · 6일 후 미시청
+ 결석 기한 지남
+
+
+
+
+
+
+**과제 / 퀴즈**
+
+
+ 배지 의미
+
+ 제출완료 제출/응시 완료
+ 미제출 · 6일 후 마감 1일 이상
+ 미응시 · 23시간 후 24시간 이내
+ 미제출 (마감) 기한 지남
+
+
+
+
+
+
+카드를 클릭하면 해당 강의/과제/퀴즈 페이지로 바로 이동합니다.
+
+데이터는 **1시간마다** 자동으로 갱신되며, 브라우저 탭을 벗어났다가 돌아왔을 때도 1시간이 지났으면 즉시 갱신됩니다. 바로 갱신하고 싶다면 우측 상단 **⋮ 메뉴 → 새로고침**을 클릭하세요. ⋮ 버튼에 빨간 점이 보이면 데이터가 오래되었다는 뜻입니다.
+
+
+
+## 자동 재생 플레이어
+
+강의 페이지 좌측에 나타나는 사이드바에서 미수강 강의를 자동으로 연속 재생할 수 있습니다.
+
+1. 강의 페이지에서 좌측의 돋부기 트리거를 클릭합니다.
+2. **수강 시작** 버튼을 누르면 첫 번째 미수강 강의부터 자동 재생됩니다.
+3. 모든 강의가 끝나면 "수강 완료"로 표시됩니다.
+
+
+
+## 언어 변경
+
+설정 탭에서 한국어, English, 中文, 日本語 중 선택할 수 있습니다.
+
+| | |
+| -------------------------------- | --------------------------------- |
+|  |  |
diff --git a/docs/guide/calendar.md b/docs/guide/calendar.md
new file mode 100644
index 0000000..780a059
--- /dev/null
+++ b/docs/guide/calendar.md
@@ -0,0 +1,39 @@
+# Google 캘린더 연동
+
+## 연결하기
+
+1. 대시보드의 **설정** 탭을 엽니다.
+2. "Google Calendar 연동"의 **로그인** 버튼을 클릭합니다.
+3. Google 계정으로 로그인하면 연동이 완료됩니다.
+
+
+
+## 동기화
+
+연동 후 드롭다운 메뉴의 **태스크 동기화** 버튼을 누르면 할 일이 Google 캘린더에 추가됩니다.
+
+
+
+동기화 결과 알림:
+
+- "N개 일정이 추가되었어요" — 새 항목이 추가됨
+- "이미 최신 상태예요" — 추가할 항목 없음
+- "N개 일정 추가에 실패했어요" — 일부 실패
+
+## 캘린더 색상 구분
+
+| 색상 | 항목 |
+| --------- | ----------- |
+| 💜 라벤더 | 온라인 강의 |
+| 💙 파랑 | 과제 |
+| 🧡 주황 | 퀴즈 |
+
+## 로그인 만료
+
+Google 로그인이 만료되면 알림이 표시됩니다. "다시 로그인" 버튼을 눌러 재연결하세요.
+
+
+
+## 연동 해제
+
+설정 탭에서 **로그아웃** 버튼을 클릭하면 연동이 해제됩니다.
diff --git a/docs/guide/faq.md b/docs/guide/faq.md
new file mode 100644
index 0000000..38d7f5f
--- /dev/null
+++ b/docs/guide/faq.md
@@ -0,0 +1,55 @@
+# 자주 묻는 질문
+
+## 설치 및 실행
+
+::: details 설치 후 돋부기 버튼이 안 보여요
+LMS(learn.hansung.ac.kr)에 로그인한 상태인지 확인하세요. 돋부기는 LMS 페이지에서만 동작합니다. 설치 직후에는 페이지를 새로고침해야 할 수 있습니다.
+:::
+
+::: details 다른 브라우저에서도 사용할 수 있나요?
+현재 Chrome 및 Chrome 기반 브라우저(Edge, Brave, Arc 등)에서 사용할 수 있습니다. Firefox, Safari 등은 지원하지 않습니다.
+:::
+
+## 대시보드
+
+::: details 데이터가 실시간으로 반영되지 않아요
+데이터는 1시간마다 자동 갱신됩니다. 바로 반영하고 싶다면 **⋮ 메뉴 → 새로고침**을 클릭하세요.
+:::
+
+::: details 특정 과목이 대시보드에 안 나와요
+LMS 메인 페이지에서 해당 과목의 트래킹 토글이 꺼져 있을 수 있습니다. 과목 카드의 우측 상단 버튼을 클릭하여 트래킹을 활성화하세요.
+:::
+
+::: details 비교과(커뮤니티) 과목도 트래킹할 수 있나요?
+네, LMS 메인 페이지에서 비교과 과목의 트래킹 토글을 켜면 대시보드에 표시됩니다. 기본값은 꺼져 있습니다.
+:::
+
+## 자동 재생 플레이어
+
+::: details 자동 재생이 시작되지 않아요
+강의 페이지(course/view.php)에서만 플레이어가 나타납니다. 좌측 사이드바의 돋부기 아이콘을 클릭한 후 "수강 시작" 버튼을 눌러주세요.
+:::
+
+::: details 이미 출석한 강의도 재생되나요?
+아니요, 이미 출석 처리된 강의는 자동으로 건너뜁니다. 이전에 일부 시청한 강의는 시청한 지점부터 이어서 재생됩니다.
+:::
+
+## Google 캘린더
+
+::: details Google 캘린더 연동 시 권한을 왜 요청하나요?
+캘린더에 일정을 추가하기 위해 Google Calendar API의 이벤트 쓰기 권한이 필요합니다. 돋부기는 일정 추가 외에 다른 데이터에 접근하지 않습니다.
+:::
+
+::: details 동기화했는데 캘린더에 일정이 안 보여요
+Google 캘린더 앱이나 웹에서 "다른 캘린더" 목록을 확인하세요. 돋부기 캘린더가 숨김 처리되어 있을 수 있습니다.
+:::
+
+## 기타
+
+::: details 개인정보가 외부로 전송되나요?
+아니요. 돋부기는 모든 데이터를 브라우저 로컬 스토리지에 저장하며, 외부 서버로 전송하지 않습니다. Google 캘린더 연동 시에만 Google API와 통신합니다.
+:::
+
+::: details 배지 색상이 실제 출석 상태와 다르게 표시돼요
+**⋮ 메뉴 → 새로고침**으로 최신 데이터를 가져와 보세요. LMS의 데이터 반영에 시간차가 있을 수 있습니다.
+:::
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..9756068
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,162 @@
+---
+layout: home
+
+head:
+ - - script
+ - {}
+ - |
+ if (!sessionStorage.getItem('lang-redirected')) {
+ const lang = navigator.language?.toLowerCase() || '';
+ const base = '/dotbugi/';
+ const map = { en: 'en/', ja: 'ja/', zh: 'zh/' };
+ const match = Object.entries(map).find(([k]) => lang.startsWith(k));
+ if (match) {
+ sessionStorage.setItem('lang-redirected', '1');
+ window.location.replace(base + match[1]);
+ }
+ }
+
+hero:
+ name: 돋부기
+ tagline: 학교 LMS를 더 편리하게
+ image:
+ src: /favicon.png
+ alt: 돋부기
+ actions:
+ - theme: brand
+ text: 사용 설명서
+ link: /guide/basic
+ - theme: alt
+ text: FAQ
+ link: /guide/faq
+ - theme: alt
+ text: 업데이트 로그
+ link: /updates/changelog
+---
+
+
+
+
+
+
할 일 대시보드
+
온라인 강의, 과제, 퀴즈를 한 화면에 모아 마감 상태를 색상으로 구분합니다.
+
+
+
자동 연속 재생
+
미수강 강의를 자동으로 연속 재생하여 수강을 빠르게 완료할 수 있습니다.
+
+
+
Google 캘린더 연동
+
과제·퀴즈 마감일을 Google 캘린더에 자동 등록하여 일정을 놓치지 않습니다.
+
+
+
강의 페이지 배지
+
강의 페이지에서 각 활동의 출석/제출 상태를 배지로 한눈에 확인할 수 있습니다.
+
+
+
다국어 지원
+
한국어, English, 中文, 日本語 4개 언어를 지원합니다.
+
+
+
+
diff --git a/docs/ja/guide/advanced.md b/docs/ja/guide/advanced.md
new file mode 100644
index 0000000..6a1c157
--- /dev/null
+++ b/docs/ja/guide/advanced.md
@@ -0,0 +1,135 @@
+# 上級ガイド
+
+## 科目トラッキング設定
+
+LMSメインページの各科目カードにトラッキングトグルボタンが表示されます。
+
+- **青いチェック**: トラッキング中 — ダッシュボードにタスクが表示されます。
+- **半透明**: トラッキング解除 — ダッシュボードから非表示になります。
+
+デフォルトではすべての正規科目がトラッキングされ、課外科目は除外されます。
+
+| トラッキング前 | トラッキング追加 |
+| :------------------------------------: | :-----------------------------------: |
+|  |  |
+
+## 検索とフィルター
+
+上部の検索バーで科目名やタイトルで検索できます。
+
+フィルターオプション:
+
+- **科目別**: 特定の科目のみ表示
+- **状態別**: 完了/未完了の項目をフィルタリング
+
+フィルターが適用されると「フィルター設定済み」の表示が出ます。
+
+
+
+## タスクの非表示
+
+確認済みまたは不要な項目は非表示にできます。
+
+- カードを**右クリック** → 「このタスクを非表示」を選択
+- 科目グループヘッダーを右クリックすると、その科目のすべての項目を一括で非表示にできます。
+
+非表示にした項目は設定タブの「非表示のタスク」で確認・復元できます。
+
+| 非表示 | 非表示解除 |
+| :-------------------------: | :--------------------------------: |
+|  |  |
+
+## 講義ページのステータスバッジ
+
+各講義のコースページでアクティビティ(講義/課題/クイズ)の横にステータスバッジが表示されます。
+
+### 講義 (VOD)
+
+
+ バッジ 状態 意味
+
+ 出席 出席 出席認定の必要時間以上を視聴完了
+ 視聴中 · 11日後 視聴中 一部視聴済み(進捗率を可視化)
+ 視聴中 · 23時間後 視聴中(間近) 一部視聴 + 締切まで24時間以内
+ 未視聴 · 6日後 未視聴 まだ視聴していない
+ 未視聴 · 23時間後 未視聴(間近) 締切まで24時間以内
+ 欠席 欠席 期限切れ
+ 非表示 非表示 非表示にされた項目
+
+
+
+::: info 視聴中バッジ
+視聴中バッジは進捗ページの学習時間データに基づいて進捗率を計算・表示します。
+:::
+
+### 課題 / クイズ
+
+
+
+
+**課題**
+
+
+ バッジ 意味
+
+ 提出済み 課題提出完了
+ 未提出 · 6日後 締切まで1日以上
+ 未提出 · 23時間後 締切まで24時間以内
+ 未提出 (締切) 期限切れ
+ 非表示 非表示にされた項目
+
+
+
+
+
+
+**クイズ**
+
+
+ バッジ 意味
+
+ 受験済み クイズ受験完了
+ 未受験 · 6日後 締切まで1日以上
+ 未受験 · 23時間後 締切まで24時間以内
+ 未受験 (締切) 期限切れ
+ 非表示 非表示にされた項目
+
+
+
+
+
+
+
+
+## 自動再生プレーヤー
+
+
+
+::: info スマート再生
+- すでに出席処理された講義は自動的にスキップされます。
+- 以前視聴した位置がある場合、その地点から再生を再開します。
+:::
+
+### 予想終了時間
+
+再生中にボタンに残りの講義の予想終了時間が表示されます。
+
+### 再生順序の変更
+
+サイドバーの講義リストでドラッグ&ドロップにより再生順序を変更できます。
+
+### ページ離脱の警告
+
+自動再生中にページを離れようとすると警告が表示されます。
+
+## 更新
+
+
+
+ドロップダウンメニューの更新ボタンでデータを更新できます。最後の更新から5分が経過すると赤い点が表示されます。1時間が経過すると自動的にデータが更新されます。
+
+## お問い合わせ
+
+
+
+設定タブの下部からメールまたはKakaoTalkオープンチャットでお問い合わせいただけます。
diff --git a/docs/ja/guide/basic.md b/docs/ja/guide/basic.md
new file mode 100644
index 0000000..9251c4a
--- /dev/null
+++ b/docs/ja/guide/basic.md
@@ -0,0 +1,78 @@
+# 基本ガイド
+
+## インストール
+
+1. Chromeウェブストアで**HSU ドットブギ**を検索します。
+2. **Chromeに追加**ボタンをクリックします。
+3. インストール完了後、大学LMS(learn.hansung.ac.kr)にアクセスします。
+
+
+
+## 初回起動
+
+インストールが完了すると、LMSページの右下にドットブギボタンが表示されます。クリックするとダッシュボードが開きます。
+
+
+
+## ダッシュボード
+
+ダッシュボードは**オンライン講義**、**課題**、**クイズ**の3つのタブで構成されています。
+
+各カードには科目名、タイトル、締切日が表示され、状態に応じて色が変わります。
+
+
+
+
+**オンライン講義**
+
+
+ バッジ 意味
+
+ 出席 視聴完了
+ 視聴中 · 11日後 一部視聴済み
+ 未視聴 · 6日後 未視聴
+ 欠席 期限切れ
+
+
+
+
+
+
+**課題 / クイズ**
+
+
+ バッジ 意味
+
+ 提出済み 提出/受験完了
+ 未提出 · 6日後 締切まで1日以上
+ 未受験 · 23時間後 24時間以内
+ 未提出 (締切) 期限切れ
+
+
+
+
+
+
+カードをクリックすると、該当の講義/課題/クイズページに直接移動します。
+
+データは**1時間ごと**に自動更新され、ブラウザタブを離れて戻った際も1時間が経過していれば即座に更新されます。すぐに更新したい場合は、右上の **⋮ メニュー → 更新** をクリックしてください。⋮ ボタンに赤い点が表示されている場合、データが古くなっていることを示しています。
+
+
+
+## 自動再生プレーヤー
+
+講義ページの左側に表示されるサイドバーから、未視聴の講義を自動で連続再生できます。
+
+1. 講義ページで左側のドットブギトリガーをクリックします。
+2. **受講開始**ボタンを押すと、最初の未視聴講義から自動再生されます。
+3. すべての講義が終わると「受講完了」と表示されます。
+
+
+
+## 言語変更
+
+設定タブで한국어、English、中文、日本語から選択できます。
+
+| | |
+| -------------------------------- | --------------------------------- |
+|  |  |
diff --git a/docs/ja/guide/calendar.md b/docs/ja/guide/calendar.md
new file mode 100644
index 0000000..3082e4f
--- /dev/null
+++ b/docs/ja/guide/calendar.md
@@ -0,0 +1,39 @@
+# Googleカレンダー連携
+
+## 連携する
+
+1. ダッシュボードの**設定**タブを開きます。
+2. 「Google Calendar連携」の**ログイン**ボタンをクリックします。
+3. Googleアカウントでログインすると連携が完了します。
+
+
+
+## 同期
+
+連携後、ドロップダウンメニューの**タスク同期**ボタンを押すと、タスクがGoogleカレンダーに追加されます。
+
+
+
+同期結果の通知:
+
+- 「N件の予定が追加されました」 — 新しい項目が追加された
+- 「すでに最新の状態です」 — 追加する項目なし
+- 「N件の予定の追加に失敗しました」 — 一部失敗
+
+## カレンダーの色分け
+
+| 色 | 項目 |
+| --------- | ------------- |
+| 💜 ラベンダー | オンライン講義 |
+| 💙 ブルー | 課題 |
+| 🧡 オレンジ | クイズ |
+
+## ログインの有効期限切れ
+
+Googleログインの有効期限が切れると通知が表示されます。「再ログイン」ボタンを押して再接続してください。
+
+
+
+## 連携解除
+
+設定タブで**ログアウト**ボタンをクリックすると連携が解除されます。
diff --git a/docs/ja/guide/faq.md b/docs/ja/guide/faq.md
new file mode 100644
index 0000000..a2d1679
--- /dev/null
+++ b/docs/ja/guide/faq.md
@@ -0,0 +1,55 @@
+# よくある質問
+
+## インストールと起動
+
+::: details インストール後にドットブギボタンが表示されません
+LMS(learn.hansung.ac.kr)にログインしているか確認してください。ドットブギはLMSページでのみ動作します。インストール直後はページの更新が必要な場合があります。
+:::
+
+::: details 他のブラウザでも使えますか?
+現在、ChromeおよびChromiumベースのブラウザ(Edge、Brave、Arcなど)に対応しています。Firefox、Safariなどには対応していません。
+:::
+
+## ダッシュボード
+
+::: details データがリアルタイムで反映されません
+データは1時間ごとに自動更新されます。すぐに反映したい場合は **⋮ メニュー → 更新** をクリックしてください。
+:::
+
+::: details 特定の科目がダッシュボードに表示されません
+LMSメインページでその科目のトラッキングがオフになっている可能性があります。科目カードの右上のボタンをクリックしてトラッキングを有効にしてください。
+:::
+
+::: details コミュニティ(非教科)科目もトラッキングできますか?
+はい、LMSメインページでコミュニティ科目のトラッキングをオンにするとダッシュボードに表示されます。デフォルトではオフになっています。
+:::
+
+## 自動再生プレイヤー
+
+::: details 自動再生が始まりません
+プレイヤーは講義ページ(course/view.php)でのみ表示されます。左サイドバーのドットブギアイコンをクリックし、「受講開始」ボタンを押してください。
+:::
+
+::: details すでに出席した講義も再生されますか?
+いいえ、すでに出席処理された講義は自動的にスキップされます。一部視聴した講義は、視聴した位置から再開されます。
+:::
+
+## Googleカレンダー
+
+::: details Google権限を要求する理由は?
+カレンダーにイベントを追加するためにGoogle Calendar APIの書き込み権限が必要です。ドットブギは予定の追加以外のデータにはアクセスしません。
+:::
+
+::: details 同期してもカレンダーに予定が表示されません
+Googleカレンダーの「他のカレンダー」リストを確認してください。ドットブギのカレンダーが非表示になっている可能性があります。
+:::
+
+## その他
+
+::: details 個人情報は外部に送信されますか?
+いいえ。ドットブギはすべてのデータをブラウザのローカルストレージに保存し、外部サーバーには送信しません。Googleカレンダー連携時のみGoogle APIと通信します。
+:::
+
+::: details バッジの色が実際の出席状況と異なります
+**⋮ メニュー → 更新** で最新データを取得してみてください。LMSのデータ反映にタイムラグがある場合があります。
+:::
diff --git a/docs/ja/index.md b/docs/ja/index.md
new file mode 100644
index 0000000..e2a17ad
--- /dev/null
+++ b/docs/ja/index.md
@@ -0,0 +1,147 @@
+---
+layout: home
+
+hero:
+ name: ドットブギ
+ tagline: 大学LMSをもっと便利に
+ image:
+ src: /favicon.png
+ alt: ドットブギ
+ actions:
+ - theme: brand
+ text: 使い方ガイド
+ link: /ja/guide/basic
+ - theme: alt
+ text: よくある質問
+ link: /ja/guide/faq
+ - theme: alt
+ text: アップデートログ
+ link: /ja/updates/changelog
+---
+
+
+
+
+
+
タスクダッシュボード
+
オンライン講義、課題、クイズを一画面にまとめ、締切状況を色で区別します。
+
+
+
自動連続再生
+
未視聴の講義を自動で連続再生し、受講を素早く完了できます。
+
+
+
Googleカレンダー連携
+
課題・クイズの締切をGoogleカレンダーに自動登録し、予定を見逃しません。
+
+
+
講義ページバッジ
+
講義ページで各アクティビティの出席・提出状況をバッジで一目で確認できます。
+
+
+
多言語対応
+
한국어、English、中文、日本語の4言語に対応しています。
+
+
+
+
diff --git a/docs/ja/updates/changelog.md b/docs/ja/updates/changelog.md
new file mode 100644
index 0000000..6796e38
--- /dev/null
+++ b/docs/ja/updates/changelog.md
@@ -0,0 +1,9 @@
+# アップデートログ
+
+## v4.0.5
+
+- 講義ページのステータスバッジおよび現在の週ハイライト機能
+- クイズ提出トラッキングおよびタスク非表示機能
+- 科目別トラッキング選択およびUX改善
+- ポップオーバー設定タブおよびカレンダー連携の改善
+- 多言語(i18n)対応およびLMSパーシングの多言語対応
diff --git a/docs/public/favicon.png b/docs/public/favicon.png
new file mode 100644
index 0000000..5ce5084
Binary files /dev/null and b/docs/public/favicon.png differ
diff --git a/docs/public/images/dashboard.png b/docs/public/images/dashboard.png
new file mode 100644
index 0000000..5bc8caa
Binary files /dev/null and b/docs/public/images/dashboard.png differ
diff --git a/docs/public/images/expired.png b/docs/public/images/expired.png
new file mode 100644
index 0000000..c6e3cdb
Binary files /dev/null and b/docs/public/images/expired.png differ
diff --git a/docs/public/images/filter.png b/docs/public/images/filter.png
new file mode 100644
index 0000000..94b0ec4
Binary files /dev/null and b/docs/public/images/filter.png differ
diff --git a/docs/public/images/hide.png b/docs/public/images/hide.png
new file mode 100644
index 0000000..9c448dc
Binary files /dev/null and b/docs/public/images/hide.png differ
diff --git a/docs/public/images/language.png b/docs/public/images/language.png
new file mode 100644
index 0000000..f0f6552
Binary files /dev/null and b/docs/public/images/language.png differ
diff --git a/docs/public/images/onboard.png b/docs/public/images/onboard.png
new file mode 100644
index 0000000..848bedd
Binary files /dev/null and b/docs/public/images/onboard.png differ
diff --git a/docs/public/images/page.png b/docs/public/images/page.png
new file mode 100644
index 0000000..2b04d67
Binary files /dev/null and b/docs/public/images/page.png differ
diff --git a/docs/public/images/player.png b/docs/public/images/player.png
new file mode 100644
index 0000000..e40cb7d
Binary files /dev/null and b/docs/public/images/player.png differ
diff --git a/docs/public/images/refresh.png b/docs/public/images/refresh.png
new file mode 100644
index 0000000..319c532
Binary files /dev/null and b/docs/public/images/refresh.png differ
diff --git a/docs/public/images/setting-tab.png b/docs/public/images/setting-tab.png
new file mode 100644
index 0000000..85e0828
Binary files /dev/null and b/docs/public/images/setting-tab.png differ
diff --git a/docs/public/images/setting.png b/docs/public/images/setting.png
new file mode 100644
index 0000000..a14cff7
Binary files /dev/null and b/docs/public/images/setting.png differ
diff --git a/docs/public/images/sync.png b/docs/public/images/sync.png
new file mode 100644
index 0000000..298a97d
Binary files /dev/null and b/docs/public/images/sync.png differ
diff --git a/docs/public/images/tasks.png b/docs/public/images/tasks.png
new file mode 100644
index 0000000..908d869
Binary files /dev/null and b/docs/public/images/tasks.png differ
diff --git a/docs/public/images/track-after.png b/docs/public/images/track-after.png
new file mode 100644
index 0000000..7b75b79
Binary files /dev/null and b/docs/public/images/track-after.png differ
diff --git a/docs/public/images/track-before.png b/docs/public/images/track-before.png
new file mode 100644
index 0000000..38e5ef0
Binary files /dev/null and b/docs/public/images/track-before.png differ
diff --git a/docs/public/images/unhide.png b/docs/public/images/unhide.png
new file mode 100644
index 0000000..c8223fb
Binary files /dev/null and b/docs/public/images/unhide.png differ
diff --git a/docs/public/images/web-store.png b/docs/public/images/web-store.png
new file mode 100644
index 0000000..3558b96
Binary files /dev/null and b/docs/public/images/web-store.png differ
diff --git a/docs/updates/changelog.md b/docs/updates/changelog.md
new file mode 100644
index 0000000..4866184
--- /dev/null
+++ b/docs/updates/changelog.md
@@ -0,0 +1,9 @@
+# 업데이트 로그
+
+## v4.0.5
+
+- 강의 페이지 상태 배지 및 현재 주차 하이라이트 기능
+- 퀴즈 제출 트래킹 및 태스크 숨김 기능
+- 과목별 트래킹 선택 및 UX 개선
+- 팝오버 설정 탭 및 캘린더 연동 개선
+- 다국어(i18n) 지원 및 LMS 파싱 다국어 대응
diff --git a/docs/zh/guide/advanced.md b/docs/zh/guide/advanced.md
new file mode 100644
index 0000000..0999452
--- /dev/null
+++ b/docs/zh/guide/advanced.md
@@ -0,0 +1,135 @@
+# 高级使用说明
+
+## 科目跟踪设置
+
+LMS 主页面的每个科目卡片上会显示跟踪切换按钮。
+
+- **蓝色勾选**: 跟踪中 — 待办事项将显示在仪表盘中。
+- **半透明**: 取消跟踪 — 从仪表盘中隐藏。
+
+默认情况下,所有正规科目都会被跟踪,非学分科目除外。
+
+| 跟踪前 | 添加跟踪 |
+| :------------------------------------: | :-----------------------------------: |
+|  |  |
+
+## 搜索和筛选
+
+可以在顶部搜索栏中按科目名称或标题进行搜索。
+
+筛选选项:
+
+- **按科目**: 仅显示特定科目
+- **按状态**: 筛选已完成/未完成项目
+
+应用筛选后会显示"已设置筛选"提示。
+
+
+
+## 隐藏任务
+
+已确认或不需要的项目可以隐藏。
+
+- **右键点击** 卡片 → 选择"隐藏此任务"
+- 右键点击科目分组标题可以一次性隐藏该科目的所有项目。
+
+隐藏的项目可以在设置标签页的"已隐藏任务"中查看和恢复。
+
+| 隐藏 | 取消隐藏 |
+| :-------------------------: | :--------------------------------: |
+|  |  |
+
+## 课程页面状态徽章
+
+在每门课程的页面中,活动(课程/作业/测验)旁边会显示状态徽章。
+
+### 课程 (VOD)
+
+
+ 徽章 状态 含义
+
+ 出勤 出勤 已观看达到出勤要求时长
+ 观看中 · 11天后 观看中 部分观看(进度可视化)
+ 观看中 · 23小时后 观看中(临近) 部分观看 + 距截止不到24小时
+ 未观看 · 6天后 未观看 尚未观看
+ 未观看 · 23小时后 未观看(临近) 距截止不到24小时
+ 缺勤 缺勤 已过期
+ 已隐藏 已隐藏 已隐藏的项目
+
+
+
+::: info 观看中徽章
+观看中徽章基于进度页面的学习时间数据计算并显示进度。
+:::
+
+### 作业 / 测验
+
+
+
+
+**作业**
+
+
+ 徽章 含义
+
+ 已提交 作业提交完成
+ 未提交 · 6天后 距截止1天以上
+ 未提交 · 23小时后 距截止不到24小时
+ 未提交 (截止) 已过期
+ 已隐藏 已隐藏的项目
+
+
+
+
+
+
+**测验**
+
+
+ 徽章 含义
+
+ 已参加 测验参加完成
+ 未参加 · 6天后 距截止1天以上
+ 未参加 · 23小时后 距截止不到24小时
+ 未参加 (截止) 已过期
+ 已隐藏 已隐藏的项目
+
+
+
+
+
+
+
+
+## 自动播放器
+
+
+
+::: info 智能播放
+- 已出勤的课程会自动跳过。
+- 如果有之前观看的位置,将从该位置继续播放。
+:::
+
+### 预计结束时间
+
+播放中按钮上会显示剩余课程的预计结束时间。
+
+### 更改播放顺序
+
+可以在侧边栏的课程列表中通过拖放来更改播放顺序。
+
+### 离开页面警告
+
+自动播放中尝试离开页面时会显示警告。
+
+## 刷新
+
+
+
+可以通过下拉菜单中的刷新按钮更新数据。最后更新超过5分钟后会显示红点。超过1小时后数据将自动更新。
+
+## 联系我们
+
+
+
+可以在设置标签页底部通过电子邮件或 KakaoTalk 开放聊天进行咨询。
diff --git a/docs/zh/guide/basic.md b/docs/zh/guide/basic.md
new file mode 100644
index 0000000..c4ff37e
--- /dev/null
+++ b/docs/zh/guide/basic.md
@@ -0,0 +1,78 @@
+# 基础使用说明
+
+## 安装
+
+1. 在 Chrome 网上应用店搜索 **HSU 돋부기**。
+2. 点击 **添加至 Chrome** 按钮。
+3. 安装完成后,访问学校 LMS (learn.hansung.ac.kr)。
+
+
+
+## 首次运行
+
+安装完成后,LMS 页面右下角会出现돋부기按钮。点击即可打开仪表盘。
+
+
+
+## 仪表盘
+
+仪表盘由 **在线课程**、**作业**、**测验** 3个标签页组成。
+
+每张卡片显示科目名称、标题和截止日期,并根据状态以不同颜色区分。
+
+
+
+
+**在线课程**
+
+
+ 徽章 含义
+
+ 出勤 已观看完成
+ 观看中 · 11天后 部分观看
+ 未观看 · 6天后 未观看
+ 缺勤 已过期
+
+
+
+
+
+
+**作业 / 测验**
+
+
+ 徽章 含义
+
+ 已提交 提交/参加完成
+ 未提交 · 6天后 距截止1天以上
+ 未参加 · 23小时后 24小时以内
+ 未提交 (截止) 已过期
+
+
+
+
+
+
+点击卡片即可直接跳转到对应的课程/作业/测验页面。
+
+数据每 **1小时** 自动更新,离开浏览器标签页后返回时如果超过1小时也会立即更新。如需立即刷新,请点击右上角 **⋮ 菜单 → 刷新**。如果 ⋮ 按钮上出现红点,说明数据已过期。
+
+
+
+## 自动播放器
+
+在课程页面左侧出现的侧边栏中,可以自动连续播放未观看的课程。
+
+1. 在课程页面点击左侧的돋부기触发按钮。
+2. 点击 **开始学习** 按钮,将从第一个未观看的课程开始自动播放。
+3. 所有课程播放完毕后,将显示"学习完成"。
+
+
+
+## 语言切换
+
+在设置标签页中可以选择한국어、English、中文、日本語。
+
+| | |
+| -------------------------------- | --------------------------------- |
+|  |  |
diff --git a/docs/zh/guide/calendar.md b/docs/zh/guide/calendar.md
new file mode 100644
index 0000000..7d46c7e
--- /dev/null
+++ b/docs/zh/guide/calendar.md
@@ -0,0 +1,39 @@
+# Google 日历同步
+
+## 连接
+
+1. 打开仪表盘的 **设置** 标签页。
+2. 点击"Google Calendar 同步"的 **登录** 按钮。
+3. 使用 Google 账号登录后即完成同步连接。
+
+
+
+## 同步
+
+连接后,点击下拉菜单中的 **同步任务** 按钮,待办事项将被添加到 Google 日历中。
+
+
+
+同步结果通知:
+
+- "已添加 N 个日程" — 添加了新项目
+- "已是最新状态" — 没有需要添加的项目
+- "N 个日程添加失败" — 部分失败
+
+## 日历颜色区分
+
+| 颜色 | 项目 |
+| --------- | ----------- |
+| 💜 薰衣草 | 在线课程 |
+| 💙 蓝色 | 作业 |
+| 🧡 橙色 | 测验 |
+
+## 登录过期
+
+Google 登录过期时会显示通知。请点击"重新登录"按钮进行重新连接。
+
+
+
+## 取消同步
+
+在设置标签页中点击 **登出** 按钮即可取消同步连接。
diff --git a/docs/zh/guide/faq.md b/docs/zh/guide/faq.md
new file mode 100644
index 0000000..16d3f75
--- /dev/null
+++ b/docs/zh/guide/faq.md
@@ -0,0 +1,55 @@
+# 常见问题
+
+## 安装与启动
+
+::: details 安装后看不到돋부기按钮
+请确认是否已登录LMS(learn.hansung.ac.kr)。돋부기仅在LMS页面上运行。安装后可能需要刷新页面。
+:::
+
+::: details 可以在其他浏览器上使用吗?
+目前支持Chrome及基于Chromium的浏览器(Edge、Brave、Arc等)。不支持Firefox、Safari等。
+:::
+
+## 仪表盘
+
+::: details 数据没有实时更新
+数据每小时自动刷新一次。如需立即刷新,请点击 **⋮ 菜单 → 刷新**。
+:::
+
+::: details 特定课程没有显示在仪表盘上
+该课程的追踪开关可能已关闭。请在LMS主页的课程卡片右上角点击按钮启用追踪。
+:::
+
+::: details 可以追踪社区(非学分)课程吗?
+可以。在LMS主页开启社区课程的追踪开关即可在仪表盘中显示。默认为关闭状态。
+:::
+
+## 自动播放器
+
+::: details 自动播放无法开始
+播放器仅在课程页面(course/view.php)上显示。请点击左侧栏的돋부기图标,然后点击"开始学习"按钮。
+:::
+
+::: details 已出勤的课程也会播放吗?
+不会。已标记出勤的课程会自动跳过。之前部分观看的课程会从上次观看的位置继续播放。
+:::
+
+## Google日历
+
+::: details 为什么需要Google权限?
+需要Google Calendar API的写入权限来添加日程。돋부기不会访问其他任何数据。
+:::
+
+::: details 同步后日历中看不到日程
+请检查Google日历中的"其他日历"列表。돋부기的日历可能被隐藏了。
+:::
+
+## 其他
+
+::: details 个人信息会发送到外部服务器吗?
+不会。돋부기将所有数据存储在浏览器本地存储中,不会向外部服务器传输数据。仅在Google日历同步时与Google API通信。
+:::
+
+::: details 徽章颜色与实际出勤状态不一致
+请尝试 **⋮ 菜单 → 刷新** 获取最新数据。LMS数据可能存在延迟。
+:::
diff --git a/docs/zh/index.md b/docs/zh/index.md
new file mode 100644
index 0000000..e79792b
--- /dev/null
+++ b/docs/zh/index.md
@@ -0,0 +1,147 @@
+---
+layout: home
+
+hero:
+ name: 돋부기
+ tagline: 让大学LMS更便捷
+ image:
+ src: /favicon.png
+ alt: 돋부기
+ actions:
+ - theme: brand
+ text: 使用说明
+ link: /zh/guide/basic
+ - theme: alt
+ text: 常见问题
+ link: /zh/guide/faq
+ - theme: alt
+ text: 更新日志
+ link: /zh/updates/changelog
+---
+
+
+
+
+
+
待办仪表盘
+
将在线课程、作业、测验汇集在一个界面,通过颜色区分截止状态。
+
+
+
自动连续播放
+
自动连续播放未观看的课程,快速完成学习。
+
+
+
Google 日历同步
+
将作业和测验截止日期自动添加到 Google 日历,不再错过任何日程。
+
+
+
课程页面徽章
+
在课程页面上以徽章形式一目了然地查看各活动的出勤/提交状态。
+
+
+
多语言支持
+
支持한국어、English、中文、日本語四种语言。
+
+
+
+
diff --git a/docs/zh/updates/changelog.md b/docs/zh/updates/changelog.md
new file mode 100644
index 0000000..6ce1192
--- /dev/null
+++ b/docs/zh/updates/changelog.md
@@ -0,0 +1,9 @@
+# 更新日志
+
+## v4.0.5
+
+- 课程页面状态徽章及当前周次高亮功能
+- 测验提交跟踪及任务隐藏功能
+- 按科目跟踪选择及用户体验改进
+- 弹出窗口设置标签页及日历同步改进
+- 多语言 (i18n) 支持及 LMS 解析多语言适配
diff --git a/manifest.config.ts b/manifest.config.ts
index 9346380..32a52e5 100644
--- a/manifest.config.ts
+++ b/manifest.config.ts
@@ -1,10 +1,18 @@
import { ManifestV3Export } from '@crxjs/vite-plugin';
+import packageJson from './package.json' with { type: 'json' };
-const manifest = {
+const PROD_CLIENT_ID = '804067218183-3pev3tppten6i94lrfvmk729hmbdejqb.apps.googleusercontent.com';
+const DEV_CLIENT_ID = '981860765955-t69elhj8osi7vdp84m5bf8b9hdib44kr.apps.googleusercontent.com';
+
+export function createManifest(mode?: string): ManifestV3Export {
+ const clientId = mode === 'mock' ? DEV_CLIENT_ID : PROD_CLIENT_ID;
+
+ return {
manifest_version: 3,
- name: 'HSU 돋부기 🔎',
- version: '4.0.5',
- description: '한성대학교 LMS 강의, 과제, 퀴즈를 한 눈에!',
+ name: '__MSG_extName__',
+ version: packageJson.version,
+ description: '__MSG_extDescription__',
+ default_locale: 'ko',
action: {},
icons: {
'16': 'images/icon/icon-16.png',
@@ -18,7 +26,7 @@ const manifest = {
content_scripts: [
{
matches: ['https://learn.hansung.ac.kr/**'],
- js: ['src/content/index.tsx'],
+ js: ['src/popover/index.tsx'],
},
],
web_accessible_resources: [
@@ -27,13 +35,11 @@ const manifest = {
matches: ['*://*/*'],
},
],
- options_page: 'option.html',
- permissions: ['storage', 'notifications', 'alarms', 'identity'],
+ permissions: ['storage', 'identity'],
host_permissions: ['https://*/*', 'http://*/*'],
oauth2: {
- client_id: '804067218183-3pev3tppten6i94lrfvmk729hmbdejqb.apps.googleusercontent.com',
+ client_id: clientId,
scopes: ['https://www.googleapis.com/auth/calendar.events'],
},
-} as ManifestV3Export;
-
-export default manifest;
+ };
+}
diff --git a/option.html b/option.html
deleted file mode 100644
index 6b73a4b..0000000
--- a/option.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
- 돋부기
-
-
-
-
-
-
-
diff --git a/package-lock.json b/package-lock.json
index 34d37af..138423f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "dotbugi",
- "version": "3.1.19",
+ "version": "4.0.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dotbugi",
- "version": "3.1.19",
+ "version": "4.0.5",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
@@ -16,7 +16,9 @@
"@heroui/theme": "^2.4.6",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
@@ -27,20 +29,19 @@
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.7",
- "@types/gapi.client.calendar": "^3.0.12",
- "axios": "^1.12.0",
+ "@types/gapi.client.calendar-v3": "^0.0.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.0.6",
- "glob": "^11.0.1",
"googleapis": "^160.0.0",
+ "i18next": "^25.8.18",
"lucide-react": "^0.471.2",
"motion": "^12.23.24",
- "node-fetch": "^3.3.2",
"react": "^18.3.1",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
+ "react-i18next": "^16.5.8",
"react-router-dom": "^7.9.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
@@ -49,6 +50,7 @@
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.30",
"@eslint/js": "^9.17.0",
+ "@testing-library/jest-dom": "^6.9.1",
"@types/chrome": "^0.0.296",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
@@ -58,6 +60,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
+ "jsdom": "^25.0.1",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.17",
@@ -65,7 +68,274 @@
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.3.6",
- "vite-plugin-pages": "^0.32.4"
+ "vite-plugin-pages": "^0.32.4",
+ "vitepress": "^1.6.4",
+ "vitest": "^4.1.0"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
+ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@algolia/abtesting": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.15.2.tgz",
+ "integrity": "sha512-rF7vRVE61E0QORw8e2NNdnttcl3jmFMWS9B4hhdga12COe+lMa26bQLfcBn/Nbp9/AF/8gXdaRCPsVns3CnjsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/autocomplete-core": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz",
+ "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/autocomplete-plugin-algolia-insights": "1.17.7",
+ "@algolia/autocomplete-shared": "1.17.7"
+ }
+ },
+ "node_modules/@algolia/autocomplete-plugin-algolia-insights": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz",
+ "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/autocomplete-shared": "1.17.7"
+ },
+ "peerDependencies": {
+ "search-insights": ">= 1 < 3"
+ }
+ },
+ "node_modules/@algolia/autocomplete-preset-algolia": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz",
+ "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/autocomplete-shared": "1.17.7"
+ },
+ "peerDependencies": {
+ "@algolia/client-search": ">= 4.9.1 < 6",
+ "algoliasearch": ">= 4.9.1 < 6"
+ }
+ },
+ "node_modules/@algolia/autocomplete-shared": {
+ "version": "1.17.7",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz",
+ "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@algolia/client-search": ">= 4.9.1 < 6",
+ "algoliasearch": ">= 4.9.1 < 6"
+ }
+ },
+ "node_modules/@algolia/client-abtesting": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.49.2.tgz",
+ "integrity": "sha512-XyvKCm0RRmovMI/ChaAVjTwpZhXdbgt3iZofK914HeEHLqD1MUFFVLz7M0+Ou7F56UkHXwRbpHwb9xBDNopprQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-analytics": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.49.2.tgz",
+ "integrity": "sha512-jq/3qvtmj3NijZlhq7A1B0Cl41GfaBpjJxcwukGsYds6aMSCWrEAJ9pUqw/C9B3hAmILYKl7Ljz3N9SFvekD3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-common": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.49.2.tgz",
+ "integrity": "sha512-bn0biLequn3epobCfjUqCxlIlurLr4RHu7RaE4trgN+RDcUq6HCVC3/yqq1hwbNYpVtulnTOJzcaxYlSr1fnuw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-insights": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.49.2.tgz",
+ "integrity": "sha512-z14wfFs1T3eeYbCArC8pvntAWsPo9f6hnUGoj8IoRUJTwgJiiySECkm8bmmV47/x0oGHfsVn3kBdjMX0yq0sNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-personalization": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.49.2.tgz",
+ "integrity": "sha512-GpRf7yuuAX93+Qt0JGEJZwgtL0MFdjFO9n7dn8s2pA9mTjzl0Sc5+uTk1VPbIAuf7xhCP9Mve+URGb6J+EYxgA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-query-suggestions": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.49.2.tgz",
+ "integrity": "sha512-HZwApmNkp0DiAjZcLYdQLddcG4Agb88OkojiAHGgcm5DVXobT5uSZ9lmyrbw/tmQBJwgu2CNw4zTyXoIB7YbPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/client-search": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.2.tgz",
+ "integrity": "sha512-y1IOpG6OSmTpGg/CT0YBb/EAhR2nsC18QWp9Jy8HO9iGySpcwaTvs5kHa17daP3BMTwWyaX9/1tDTDQshZzXdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/ingestion": {
+ "version": "1.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.49.2.tgz",
+ "integrity": "sha512-YYJRjaZ2bqk923HxE4um7j/Cm3/xoSkF2HC2ZweOF8cXL3sqnlndSUYmCaxHFjNPWLaSHk2IfssX6J/tdKTULw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/monitoring": {
+ "version": "1.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.49.2.tgz",
+ "integrity": "sha512-9WgH+Dha39EQQyGKCHlGYnxW/7W19DIrEbCEbnzwAMpGAv1yTWCHMPXHxYa+LcL3eCp2V/5idD1zHNlIKmHRHg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/recommend": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.49.2.tgz",
+ "integrity": "sha512-K7Gp5u+JtVYgaVpBxF5rGiM+Ia8SsMdcAJMTDV93rwh00DKNllC19o1g+PwrDjDvyXNrnTEbofzbTs2GLfFyKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-browser-xhr": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.49.2.tgz",
+ "integrity": "sha512-3UhYCcWX6fbtN8ABcxZlhaQEwXFh3CsFtARyyadQShHMPe3mJV9Wel4FpJTa+seugRkbezFz0tt6aPTZSYTBuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-fetch": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.49.2.tgz",
+ "integrity": "sha512-G94VKSGbsr+WjsDDOBe5QDQ82QYgxvpxRGJfCHZBnYKYsy/jv9qGIDb93biza+LJWizQBUtDj7bZzp3QZyzhPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@algolia/requester-node-http": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.49.2.tgz",
+ "integrity": "sha512-UuihBGHafG/ENsrcTGAn5rsOffrCIRuHMOsD85fZGLEY92ate+BMTUqxz60dv5zerh8ZumN4bRm8eW2z9L11jA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/client-common": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
}
},
"node_modules/@alloc/quick-lru": {
@@ -94,16 +364,37 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/@babel/code-frame": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
- "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@@ -227,9 +518,9 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -237,9 +528,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -257,27 +548,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
- "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.25.9",
- "@babel/types": "^7.26.0"
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz",
- "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.26.5"
+ "@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -318,16 +609,25 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
- "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.25.9",
- "@babel/parser": "^7.25.9",
- "@babel/types": "^7.25.9"
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -363,23 +663,23 @@
}
},
"node_modules/@babel/types": {
- "version": "7.26.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz",
- "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@crxjs/vite-plugin": {
- "version": "2.0.0-beta.30",
- "resolved": "https://registry.npmjs.org/@crxjs/vite-plugin/-/vite-plugin-2.0.0-beta.30.tgz",
- "integrity": "sha512-skRcaJAbDrcfKPRqgBtyeBAk19KrRqtA8lO3ZiwJgnpRPX8EICbv0iR6Jb8E3V+knXCrYTc4O5Im+r+n43f14A==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@crxjs/vite-plugin/-/vite-plugin-2.3.0.tgz",
+ "integrity": "sha512-+0CNVGS4bB30OoaF1vUsHVwWU1Lm7MxI0XWY9Fd/Ob+ZVTZgEFNqJ1ZC69IVwQsoYhY0sMQLvpLWiFIuDz8htg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -394,7 +694,8 @@
"fs-extra": "^10.0.1",
"jsesc": "^3.0.2",
"magic-string": "^0.30.12",
- "picocolors": "^1.0.0",
+ "pathe": "^2.0.1",
+ "picocolors": "^1.1.1",
"react-refresh": "^0.13.0",
"rollup": "2.79.2",
"rxjs": "7.5.7"
@@ -425,9 +726,9 @@
}
},
"node_modules/@crxjs/vite-plugin/node_modules/rollup": {
- "version": "2.79.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
- "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
+ "version": "2.80.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
+ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -440,6 +741,121 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@dnd-kit/accessibility": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
@@ -507,31 +923,80 @@
"react": ">=16.8.0"
}
},
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
- "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/@docsearch/css": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz",
+ "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@docsearch/js": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz",
+ "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
+ "dependencies": {
+ "@docsearch/react": "3.8.2",
+ "preact": "^10.0.0"
}
},
- "node_modules/@esbuild/android-arm": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
- "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
- "cpu": [
+ "node_modules/@docsearch/react": {
+ "version": "3.8.2",
+ "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz",
+ "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@algolia/autocomplete-core": "1.17.7",
+ "@algolia/autocomplete-preset-algolia": "1.17.7",
+ "@docsearch/css": "3.8.2",
+ "algoliasearch": "^5.14.2"
+ },
+ "peerDependencies": {
+ "@types/react": ">= 16.8.0 < 19.0.0",
+ "react": ">= 16.8.0 < 19.0.0",
+ "react-dom": ">= 16.8.0 < 19.0.0",
+ "search-insights": ">= 1 < 3"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ },
+ "search-insights": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
+ "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
+ "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
+ "cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -548,7 +1013,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -565,7 +1029,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -582,7 +1045,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -599,7 +1061,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -616,7 +1077,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -633,7 +1093,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -650,7 +1109,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -667,7 +1125,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -684,7 +1141,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -701,7 +1157,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -718,7 +1173,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -735,7 +1189,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -752,7 +1205,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -769,7 +1221,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -786,7 +1237,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -803,7 +1253,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -820,7 +1269,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -837,7 +1285,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -854,7 +1301,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -871,7 +1317,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -888,7 +1333,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -905,7 +1349,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -922,7 +1365,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -933,9 +1375,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
- "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -975,24 +1417,61 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
- "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.5",
+ "@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
- "minimatch": "^3.1.2"
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
- "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1003,20 +1482,20 @@
}
},
"node_modules/@eslint/eslintrc": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
- "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ajv": "^6.12.4",
+ "ajv": "^6.14.0",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
"strip-json-comments": "^3.1.1"
},
"engines": {
@@ -1026,6 +1505,17 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -1039,20 +1529,36 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/@eslint/js": {
- "version": "9.18.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
- "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==",
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
- "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -1060,13 +1566,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
- "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.10.0",
+ "@eslint/core": "^0.17.0",
"levn": "^0.4.1"
},
"engines": {
@@ -1348,9 +1854,9 @@
}
},
"node_modules/@humanwhocodes/retry": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
- "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -1361,6 +1867,23 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@iconify-json/simple-icons": {
+ "version": "1.2.74",
+ "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.74.tgz",
+ "integrity": "sha512-yqaohfY6jnYjTVpuTkaBQHrWbdUrQyWXhau0r/0EZiNWYXPX/P8WWwl1DoLH5CbvDjjcWQw5J0zADhgCUklOqA==",
+ "dev": true,
+ "license": "CC0-1.0",
+ "dependencies": {
+ "@iconify/types": "*"
+ }
+ },
+ "node_modules/@iconify/types": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
+ "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@internationalized/date": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz",
@@ -1451,7 +1974,7 @@
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -1460,9 +1983,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
@@ -1687,6 +2210,160 @@
}
}
},
+ "node_modules/@radix-ui/react-context-menu": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz",
+ "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context-menu/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-dialog": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz",
@@ -1882,54 +2559,61 @@
}
}
},
- "node_modules/@radix-ui/react-focus-guards": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
- "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
"license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
"peerDependencies": {
"@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
}
}
},
- "node_modules/@radix-ui/react-focus-scope": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
- "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0"
- },
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-id": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
- "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -1940,50 +2624,31 @@
}
}
},
- "node_modules/@radix-ui/react-label": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz",
- "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.0.1"
+ "@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-popover": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz",
- "integrity": "sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.3",
- "@radix-ui/react-focus-guards": "1.1.1",
- "@radix-ui/react-focus-scope": "1.1.1",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.1",
- "@radix-ui/react-portal": "1.1.3",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-slot": "1.1.1",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "aria-hidden": "^1.1.1",
- "react-remove-scroll": "^2.6.1"
+ "@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -2000,117 +2665,82 @@
}
}
},
- "node_modules/@radix-ui/react-popper": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
- "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
"dependencies": {
- "@floating-ui/react-dom": "^2.0.0",
- "@radix-ui/react-arrow": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-use-rect": "1.1.0",
- "@radix-ui/react-use-size": "1.1.0",
- "@radix-ui/rect": "1.1.0"
+ "@radix-ui/react-compose-refs": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-portal": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
- "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-layout-effect": "1.1.0"
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-presence": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
- "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-primitive": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
- "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+ "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-slot": "1.1.1"
- },
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-progress": {
+ "node_modules/@radix-ui/react-focus-scope": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.1.tgz",
- "integrity": "sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA==",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
+ "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.1"
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
@@ -2127,52 +2757,31 @@
}
}
},
- "node_modules/@radix-ui/react-roving-focus": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
- "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0"
+ "@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/react-scroll-area": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz",
- "integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==",
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz",
+ "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==",
"license": "MIT",
"dependencies": {
- "@radix-ui/number": "1.1.0",
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0"
+ "@radix-ui/react-primitive": "2.0.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2189,33 +2798,30 @@
}
}
},
- "node_modules/@radix-ui/react-select": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.5.tgz",
- "integrity": "sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/number": "1.1.0",
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-dismissable-layer": "1.1.4",
- "@radix-ui/react-focus-guards": "1.1.1",
- "@radix-ui/react-focus-scope": "1.1.1",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.1",
- "@radix-ui/react-portal": "1.1.3",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-slot": "1.1.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-visually-hidden": "1.1.1",
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
"aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.2"
+ "react-remove-scroll": "^2.6.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -2232,17 +2838,19 @@
}
}
},
- "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz",
- "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-escape-keydown": "1.1.0"
+ "@radix-ui/react-primitive": "2.1.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -2259,13 +2867,16 @@
}
}
},
- "node_modules/@radix-ui/react-separator": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz",
- "integrity": "sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.0.1"
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
@@ -2282,14 +2893,11 @@
}
}
},
- "node_modules/@radix-ui/react-slot": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
- "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1"
- },
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -2300,24 +2908,51 @@
}
}
},
- "node_modules/@radix-ui/react-tabs": {
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": {
"version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz",
- "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-roving-focus": "1.1.1",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
@@ -2330,24 +2965,30 @@
}
}
},
- "node_modules/@radix-ui/react-toast": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.4.tgz",
- "integrity": "sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.3",
- "@radix-ui/react-portal": "1.1.3",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-visually-hidden": "1.1.1"
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2364,24 +3005,40 @@
}
}
},
- "node_modules/@radix-ui/react-tooltip": {
- "version": "1.1.7",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.7.tgz",
- "integrity": "sha512-ss0s80BC0+g0+Zc53MvilcnTYSOi4mSuFWBPYPuTOFGjx+pUU+ZrmamMNwS56t8MTFlniA5ocjd4jYm/CdhbOg==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.4",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.1",
- "@radix-ui/react-portal": "1.1.3",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-slot": "1.1.1",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-visually-hidden": "1.1.1"
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2398,17 +3055,14 @@
}
}
},
- "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz",
- "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
"license": "MIT",
"dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-primitive": "2.0.1",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-escape-keydown": "1.1.0"
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2425,62 +3079,92 @@
}
}
},
- "node_modules/@radix-ui/react-use-callback-ref": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
- "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
"peerDependencies": {
"@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
}
}
},
- "node_modules/@radix-ui/react-use-controllable-state": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
- "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.0"
+ "@radix-ui/react-slot": "1.2.3"
},
"peerDependencies": {
"@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
}
}
},
- "node_modules/@radix-ui/react-use-escape-keydown": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
- "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.0"
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
},
"peerDependencies": {
"@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
}
}
},
- "node_modules/@radix-ui/react-use-layout-effect": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
- "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
"license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -2491,10 +3175,10 @@
}
}
},
- "node_modules/@radix-ui/react-use-previous": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
- "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
@@ -2506,13 +3190,14 @@
}
}
},
- "node_modules/@radix-ui/react-use-rect": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
- "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
"license": "MIT",
"dependencies": {
- "@radix-ui/rect": "1.1.0"
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2524,13 +3209,13 @@
}
}
},
- "node_modules/@radix-ui/react-use-size": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
- "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.0"
+ "@radix-ui/react-use-callback-ref": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
@@ -2542,1696 +3227,4101 @@
}
}
},
- "node_modules/@radix-ui/react-visually-hidden": {
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
- "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.1"
- },
"peerDependencies": {
"@types/react": "*",
- "@types/react-dom": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
- },
- "@types/react-dom": {
- "optional": true
}
}
},
- "node_modules/@radix-ui/rect": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
- "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
- "license": "MIT"
- },
- "node_modules/@react-aria/focus": {
- "version": "3.19.0",
- "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.0.tgz",
- "integrity": "sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/interactions": "^3.22.5",
- "@react-aria/utils": "^3.26.0",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0",
- "clsx": "^2.0.0"
+ "@radix-ui/rect": "1.1.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/i18n": {
- "version": "3.12.4",
- "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.4.tgz",
- "integrity": "sha512-j9+UL3q0Ls8MhXV9gtnKlyozq4aM95YywXqnmJtzT1rYeBx7w28hooqrWkCYLfqr4OIryv1KUnPiCSLwC2OC7w==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
"dependencies": {
- "@internationalized/date": "^3.6.0",
- "@internationalized/message": "^3.1.6",
- "@internationalized/number": "^3.6.0",
- "@internationalized/string": "^3.2.5",
- "@react-aria/ssr": "^3.9.7",
- "@react-aria/utils": "^3.26.0",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/interactions": {
- "version": "3.22.5",
- "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.22.5.tgz",
- "integrity": "sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.4.tgz",
+ "integrity": "sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/ssr": "^3.9.7",
- "@react-aria/utils": "^3.26.0",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "^2.6.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
- }
- },
- "node_modules/@react-aria/overlays": {
- "version": "3.24.0",
- "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.24.0.tgz",
- "integrity": "sha512-0kAXBsMNTc/a3M07tK9Cdt/ea8CxTAEJ223g8YgqImlmoBBYAL7dl5G01IOj67TM64uWPTmZrOklBchHWgEm3A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@react-aria/focus": "^3.19.0",
- "@react-aria/i18n": "^3.12.4",
- "@react-aria/interactions": "^3.22.5",
- "@react-aria/ssr": "^3.9.7",
- "@react-aria/utils": "^3.26.0",
- "@react-aria/visually-hidden": "^3.8.18",
- "@react-stately/overlays": "^3.6.12",
- "@react-types/button": "^3.10.1",
- "@react-types/overlays": "^3.8.11",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/ssr": {
- "version": "3.9.7",
- "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz",
- "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
+ "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
+ "license": "MIT",
"dependencies": {
- "@swc/helpers": "^0.5.0"
- },
- "engines": {
- "node": ">= 12"
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-rect": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0",
+ "@radix-ui/rect": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/switch": {
- "version": "3.6.10",
- "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.10.tgz",
- "integrity": "sha512-FtaI9WaEP1tAmra1sYlAkYXg9x75P5UtgY8pSbe9+1WRyWbuE1QZT+RNCTi3IU4fZ7iJQmXH6+VaMyzPlSUagw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
+ "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/toggle": "^3.10.10",
- "@react-stately/toggle": "^3.8.0",
- "@react-types/shared": "^3.26.0",
- "@react-types/switch": "^3.5.7",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/switch/node_modules/@react-aria/toggle": {
- "version": "3.10.11",
- "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.11.tgz",
- "integrity": "sha512-J3jO3KJiUbaYVDEpeXSBwqcyKxpi9OreiHRGiaxb6VwB+FWCj7Gb2WKajByXNyfs8jc6kX9VUFaXa7jze60oEQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
+ "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/focus": "^3.19.1",
- "@react-aria/interactions": "^3.23.0",
- "@react-aria/utils": "^3.27.0",
- "@react-stately/toggle": "^3.8.1",
- "@react-types/checkbox": "^3.9.1",
- "@react-types/shared": "^3.27.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/switch/node_modules/@react-aria/toggle/node_modules/@react-aria/focus": {
- "version": "3.19.1",
- "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.1.tgz",
- "integrity": "sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
+ "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/interactions": "^3.23.0",
- "@react-aria/utils": "^3.27.0",
- "@react-types/shared": "^3.27.0",
- "@swc/helpers": "^0.5.0",
- "clsx": "^2.0.0"
+ "@radix-ui/react-slot": "1.1.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/switch/node_modules/@react-aria/toggle/node_modules/@react-aria/interactions": {
- "version": "3.23.0",
- "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.23.0.tgz",
- "integrity": "sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.1.tgz",
+ "integrity": "sha512-6diOawA84f/eMxFHcWut0aE1C2kyE9dOyCTQOMRR2C/qPiXz/X0SaiA/RLbapQaXUCmy0/hLMf9meSccD1N0pA==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/ssr": "^3.9.7",
- "@react-aria/utils": "^3.27.0",
- "@react-types/shared": "^3.27.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/switch/node_modules/@react-aria/toggle/node_modules/@react-aria/utils": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz",
- "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
+ "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/ssr": "^3.9.7",
- "@react-stately/utils": "^3.10.5",
- "@react-types/shared": "^3.27.0",
- "@swc/helpers": "^0.5.0",
- "clsx": "^2.0.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
- "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/switch/node_modules/@react-stately/toggle": {
- "version": "3.8.1",
- "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.1.tgz",
- "integrity": "sha512-MVpe79ghVQiwLmVzIPhF/O/UJAUc9B+ZSylVTyJiEPi0cwhbkKGQv9thOF0ebkkRkace5lojASqUAYtSTZHQJA==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz",
+ "integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==",
+ "license": "MIT",
"dependencies": {
- "@react-stately/utils": "^3.10.5",
- "@react-types/checkbox": "^3.9.1",
- "@react-types/shared": "^3.27.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
- }
- },
- "node_modules/@react-aria/switch/node_modules/@react-types/shared": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
- "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/utils": {
- "version": "3.26.0",
- "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.26.0.tgz",
- "integrity": "sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.5.tgz",
+ "integrity": "sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/ssr": "^3.9.7",
- "@react-stately/utils": "^3.10.5",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0",
- "clsx": "^2.0.0"
+ "@radix-ui/number": "1.1.0",
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.4",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-previous": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.2"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-aria/visually-hidden": {
- "version": "3.8.18",
- "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.18.tgz",
- "integrity": "sha512-l/0igp+uub/salP35SsNWq5mGmg3G5F5QMS1gDZ8p28n7CgjvzyiGhJbbca7Oxvaw1HRFzVl9ev+89I7moNnFQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz",
+ "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==",
+ "license": "MIT",
"dependencies": {
- "@react-aria/interactions": "^3.22.5",
- "@react-aria/utils": "^3.26.0",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-stately/overlays": {
- "version": "3.6.13",
- "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.13.tgz",
- "integrity": "sha512-WsU85Gf/b+HbWsnnYw7P/Ila3wD+C37Uk/WbU4/fHgJ26IEOWsPE6wlul8j54NZ1PnLNhV9Fn+Kffi+PaJMQXQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.1.tgz",
+ "integrity": "sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==",
+ "license": "MIT",
"dependencies": {
- "@react-stately/utils": "^3.10.5",
- "@react-types/overlays": "^3.8.12",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/react-primitive": "2.0.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-stately/toggle": {
- "version": "3.8.0",
- "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.0.tgz",
- "integrity": "sha512-pyt/k/J8BwE/2g6LL6Z6sMSWRx9HEJB83Sm/MtovXnI66sxJ2EfQ1OaXB7Su5PEL9OMdoQF6Mb+N1RcW3zAoPw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
+ "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+ "license": "MIT",
"dependencies": {
- "@react-stately/utils": "^3.10.5",
- "@react-types/checkbox": "^3.9.0",
- "@react-types/shared": "^3.26.0",
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-stately/utils": {
- "version": "3.10.5",
- "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz",
- "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz",
+ "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==",
+ "license": "MIT",
"dependencies": {
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-roving-focus": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/button": {
- "version": "3.10.2",
- "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.10.2.tgz",
- "integrity": "sha512-h8SB/BLoCgoBulCpyzaoZ+miKXrolK9XC48+n1dKJXT8g4gImrficurDW6+PRTQWaRai0Q0A6bu8UibZOU4syg==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.4.tgz",
+ "integrity": "sha512-Sch9idFJHJTMH9YNpxxESqABcAFweJG4tKv+0zo0m5XBvUSL8FM5xKcJLFLXononpePs8IclyX1KieL5SDUNgA==",
+ "license": "MIT",
"dependencies": {
- "@react-types/shared": "^3.27.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-collection": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
- }
- },
- "node_modules/@react-types/button/node_modules/@react-types/shared": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
- "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/calendar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.6.0.tgz",
- "integrity": "sha512-BtFh4BFwvsYlsaSqUOVxlqXZSlJ6u4aozgO3PwHykhpemwidlzNwm9qDZhcMWPioNF/w2cU/6EqhvEKUHDnFZg==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.7.tgz",
+ "integrity": "sha512-ss0s80BC0+g0+Zc53MvilcnTYSOi4mSuFWBPYPuTOFGjx+pUU+ZrmamMNwS56t8MTFlniA5ocjd4jYm/CdhbOg==",
+ "license": "MIT",
"dependencies": {
- "@internationalized/date": "^3.7.0",
- "@react-types/shared": "^3.27.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.4",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.1",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.1"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
- }
- },
- "node_modules/@react-types/calendar/node_modules/@internationalized/date": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz",
- "integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==",
- "license": "Apache-2.0",
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.4.tgz",
+ "integrity": "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==",
+ "license": "MIT",
"dependencies": {
- "@swc/helpers": "^0.5.0"
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/calendar/node_modules/@react-types/shared": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
- "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
+ "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
+ "license": "MIT",
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/checkbox": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.1.tgz",
- "integrity": "sha512-0x/KQcipfNM9Nvy6UMwYG25roRLvsiqf0J3woTYylNNWzF+72XT0iI5FdJkE3w2wfa0obmSoeq4WcbFREQrH/A==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
+ "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
+ "license": "MIT",
"dependencies": {
- "@react-types/shared": "^3.27.0"
+ "@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/checkbox/node_modules/@react-types/shared": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
- "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/datepicker": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.9.0.tgz",
- "integrity": "sha512-dbKL5Qsm2MQwOTtVQdOcKrrphcXAqDD80WLlSQrBLg+waDuuQ7H+TrvOT0thLKloNBlFUGnZZfXGRHINpih/0g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@internationalized/date": "^3.6.0",
- "@react-types/calendar": "^3.5.0",
- "@react-types/overlays": "^3.8.11",
- "@react-types/shared": "^3.26.0"
- },
+ "node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/overlays": {
- "version": "3.8.12",
- "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.12.tgz",
- "integrity": "sha512-ZvR1t0YV7/6j+6OD8VozKYjvsXT92+C/2LOIKozy7YUNS5KI4MkXbRZzJvkuRECVZOmx8JXKTUzhghWJM/3QuQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
+ "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+ "license": "MIT",
"dependencies": {
- "@react-types/shared": "^3.27.0"
+ "@radix-ui/react-use-callback-ref": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/overlays/node_modules/@react-types/shared": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
- "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
+ "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
+ "license": "MIT",
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/shared": {
- "version": "3.26.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.26.0.tgz",
- "integrity": "sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
+ "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
+ "license": "MIT",
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/switch": {
- "version": "3.5.8",
- "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.8.tgz",
- "integrity": "sha512-sL7jmh8llF8BxzY4HXkSU4bwU8YU6gx45P85D0AdYXgRHxU9Cp7BQPOMF4pJoQ8TTej05MymY5q7xvJVmxUTAQ==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
+ "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
+ "license": "MIT",
"dependencies": {
- "@react-types/shared": "^3.27.0"
+ "@radix-ui/rect": "1.1.0"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@react-types/switch/node_modules/@react-types/shared": {
- "version": "3.27.0",
- "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
- "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
- "license": "Apache-2.0",
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
+ "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
"peerDependencies": {
- "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/@rollup/pluginutils": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
- "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
- "dev": true,
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.1.tgz",
+ "integrity": "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==",
"license": "MIT",
"dependencies": {
- "estree-walker": "^2.0.1",
- "picomatch": "^2.2.2"
+ "@radix-ui/react-primitive": "2.0.1"
},
- "engines": {
- "node": ">= 8.0.0"
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz",
- "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
+ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz",
- "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
+ "node_modules/@react-aria/focus": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.0.tgz",
+ "integrity": "sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/interactions": "^3.22.5",
+ "@react-aria/utils": "^3.26.0",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/i18n": {
+ "version": "3.12.4",
+ "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.4.tgz",
+ "integrity": "sha512-j9+UL3q0Ls8MhXV9gtnKlyozq4aM95YywXqnmJtzT1rYeBx7w28hooqrWkCYLfqr4OIryv1KUnPiCSLwC2OC7w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@internationalized/date": "^3.6.0",
+ "@internationalized/message": "^3.1.6",
+ "@internationalized/number": "^3.6.0",
+ "@internationalized/string": "^3.2.5",
+ "@react-aria/ssr": "^3.9.7",
+ "@react-aria/utils": "^3.26.0",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/interactions": {
+ "version": "3.22.5",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.22.5.tgz",
+ "integrity": "sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.7",
+ "@react-aria/utils": "^3.26.0",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/overlays": {
+ "version": "3.24.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.24.0.tgz",
+ "integrity": "sha512-0kAXBsMNTc/a3M07tK9Cdt/ea8CxTAEJ223g8YgqImlmoBBYAL7dl5G01IOj67TM64uWPTmZrOklBchHWgEm3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/focus": "^3.19.0",
+ "@react-aria/i18n": "^3.12.4",
+ "@react-aria/interactions": "^3.22.5",
+ "@react-aria/ssr": "^3.9.7",
+ "@react-aria/utils": "^3.26.0",
+ "@react-aria/visually-hidden": "^3.8.18",
+ "@react-stately/overlays": "^3.6.12",
+ "@react-types/button": "^3.10.1",
+ "@react-types/overlays": "^3.8.11",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.7",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.7.tgz",
+ "integrity": "sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch": {
+ "version": "3.6.10",
+ "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.10.tgz",
+ "integrity": "sha512-FtaI9WaEP1tAmra1sYlAkYXg9x75P5UtgY8pSbe9+1WRyWbuE1QZT+RNCTi3IU4fZ7iJQmXH6+VaMyzPlSUagw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/toggle": "^3.10.10",
+ "@react-stately/toggle": "^3.8.0",
+ "@react-types/shared": "^3.26.0",
+ "@react-types/switch": "^3.5.7",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch/node_modules/@react-aria/toggle": {
+ "version": "3.10.11",
+ "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.11.tgz",
+ "integrity": "sha512-J3jO3KJiUbaYVDEpeXSBwqcyKxpi9OreiHRGiaxb6VwB+FWCj7Gb2WKajByXNyfs8jc6kX9VUFaXa7jze60oEQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/focus": "^3.19.1",
+ "@react-aria/interactions": "^3.23.0",
+ "@react-aria/utils": "^3.27.0",
+ "@react-stately/toggle": "^3.8.1",
+ "@react-types/checkbox": "^3.9.1",
+ "@react-types/shared": "^3.27.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch/node_modules/@react-aria/toggle/node_modules/@react-aria/focus": {
+ "version": "3.19.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.19.1.tgz",
+ "integrity": "sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/interactions": "^3.23.0",
+ "@react-aria/utils": "^3.27.0",
+ "@react-types/shared": "^3.27.0",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch/node_modules/@react-aria/toggle/node_modules/@react-aria/interactions": {
+ "version": "3.23.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.23.0.tgz",
+ "integrity": "sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.7",
+ "@react-aria/utils": "^3.27.0",
+ "@react-types/shared": "^3.27.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch/node_modules/@react-aria/toggle/node_modules/@react-aria/utils": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.27.0.tgz",
+ "integrity": "sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.7",
+ "@react-stately/utils": "^3.10.5",
+ "@react-types/shared": "^3.27.0",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1",
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch/node_modules/@react-stately/toggle": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.1.tgz",
+ "integrity": "sha512-MVpe79ghVQiwLmVzIPhF/O/UJAUc9B+ZSylVTyJiEPi0cwhbkKGQv9thOF0ebkkRkace5lojASqUAYtSTZHQJA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-stately/utils": "^3.10.5",
+ "@react-types/checkbox": "^3.9.1",
+ "@react-types/shared": "^3.27.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/switch/node_modules/@react-types/shared": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
+ "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/utils": {
+ "version": "3.26.0",
+ "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.26.0.tgz",
+ "integrity": "sha512-LkZouGSjjQ0rEqo4XJosS4L3YC/zzQkfRM3KoqK6fUOmUJ9t0jQ09WjiF+uOoG9u+p30AVg3TrZRUWmoTS+koQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.7",
+ "@react-stately/utils": "^3.10.5",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/visually-hidden": {
+ "version": "3.8.18",
+ "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.18.tgz",
+ "integrity": "sha512-l/0igp+uub/salP35SsNWq5mGmg3G5F5QMS1gDZ8p28n7CgjvzyiGhJbbca7Oxvaw1HRFzVl9ev+89I7moNnFQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/interactions": "^3.22.5",
+ "@react-aria/utils": "^3.26.0",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-stately/overlays": {
+ "version": "3.6.13",
+ "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.13.tgz",
+ "integrity": "sha512-WsU85Gf/b+HbWsnnYw7P/Ila3wD+C37Uk/WbU4/fHgJ26IEOWsPE6wlul8j54NZ1PnLNhV9Fn+Kffi+PaJMQXQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-stately/utils": "^3.10.5",
+ "@react-types/overlays": "^3.8.12",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-stately/toggle": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.8.0.tgz",
+ "integrity": "sha512-pyt/k/J8BwE/2g6LL6Z6sMSWRx9HEJB83Sm/MtovXnI66sxJ2EfQ1OaXB7Su5PEL9OMdoQF6Mb+N1RcW3zAoPw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-stately/utils": "^3.10.5",
+ "@react-types/checkbox": "^3.9.0",
+ "@react-types/shared": "^3.26.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-stately/utils": {
+ "version": "3.10.5",
+ "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.5.tgz",
+ "integrity": "sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/button": {
+ "version": "3.10.2",
+ "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.10.2.tgz",
+ "integrity": "sha512-h8SB/BLoCgoBulCpyzaoZ+miKXrolK9XC48+n1dKJXT8g4gImrficurDW6+PRTQWaRai0Q0A6bu8UibZOU4syg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-types/shared": "^3.27.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/button/node_modules/@react-types/shared": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
+ "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/calendar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.6.0.tgz",
+ "integrity": "sha512-BtFh4BFwvsYlsaSqUOVxlqXZSlJ6u4aozgO3PwHykhpemwidlzNwm9qDZhcMWPioNF/w2cU/6EqhvEKUHDnFZg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@internationalized/date": "^3.7.0",
+ "@react-types/shared": "^3.27.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/calendar/node_modules/@internationalized/date": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz",
+ "integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@react-types/calendar/node_modules/@react-types/shared": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
+ "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/checkbox": {
+ "version": "3.9.1",
+ "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.9.1.tgz",
+ "integrity": "sha512-0x/KQcipfNM9Nvy6UMwYG25roRLvsiqf0J3woTYylNNWzF+72XT0iI5FdJkE3w2wfa0obmSoeq4WcbFREQrH/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-types/shared": "^3.27.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/checkbox/node_modules/@react-types/shared": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
+ "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/datepicker": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.9.0.tgz",
+ "integrity": "sha512-dbKL5Qsm2MQwOTtVQdOcKrrphcXAqDD80WLlSQrBLg+waDuuQ7H+TrvOT0thLKloNBlFUGnZZfXGRHINpih/0g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@internationalized/date": "^3.6.0",
+ "@react-types/calendar": "^3.5.0",
+ "@react-types/overlays": "^3.8.11",
+ "@react-types/shared": "^3.26.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/overlays": {
+ "version": "3.8.12",
+ "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.12.tgz",
+ "integrity": "sha512-ZvR1t0YV7/6j+6OD8VozKYjvsXT92+C/2LOIKozy7YUNS5KI4MkXbRZzJvkuRECVZOmx8JXKTUzhghWJM/3QuQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-types/shared": "^3.27.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/overlays/node_modules/@react-types/shared": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
+ "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/shared": {
+ "version": "3.26.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.26.0.tgz",
+ "integrity": "sha512-6FuPqvhmjjlpEDLTiYx29IJCbCNWPlsyO+ZUmCUXzhUv2ttShOXfw8CmeHWHftT/b2KweAWuzqSlfeXPR76jpw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/switch": {
+ "version": "3.5.8",
+ "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.8.tgz",
+ "integrity": "sha512-sL7jmh8llF8BxzY4HXkSU4bwU8YU6gx45P85D0AdYXgRHxU9Cp7BQPOMF4pJoQ8TTej05MymY5q7xvJVmxUTAQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-types/shared": "^3.27.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/switch/node_modules/@react-types/shared": {
+ "version": "3.27.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.27.0.tgz",
+ "integrity": "sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz",
+ "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "estree-walker": "^2.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@shikijs/core": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz",
+ "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/engine-javascript": "2.5.0",
+ "@shikijs/engine-oniguruma": "2.5.0",
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4",
+ "hast-util-to-html": "^9.0.4"
+ }
+ },
+ "node_modules/@shikijs/engine-javascript": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz",
+ "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "oniguruma-to-es": "^3.1.0"
+ }
+ },
+ "node_modules/@shikijs/engine-oniguruma": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz",
+ "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2"
+ }
+ },
+ "node_modules/@shikijs/langs": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz",
+ "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "2.5.0"
+ }
+ },
+ "node_modules/@shikijs/themes": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz",
+ "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/types": "2.5.0"
+ }
+ },
+ "node_modules/@shikijs/transformers": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz",
+ "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/core": "2.5.0",
+ "@shikijs/types": "2.5.0"
+ }
+ },
+ "node_modules/@shikijs/types": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz",
+ "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
+ }
+ },
+ "node_modules/@shikijs/vscode-textmate": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz",
+ "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
+ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
+ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/chrome": {
+ "version": "0.0.296",
+ "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.296.tgz",
+ "integrity": "sha512-EWVcJlIXi2PzKfnzOXkIfySMFkdOQ6DiKkhbuHeuvTxOcVYeUBXbfiExSJ5EDS4M0gAChO/15tDHqMWP47muYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/filesystem": "*",
+ "@types/har-format": "*"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "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==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/filesystem": {
+ "version": "0.0.36",
+ "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz",
+ "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/filewriter": "*"
+ }
+ },
+ "node_modules/@types/filewriter": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
+ "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/gapi.client": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz",
+ "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==",
+ "license": "MIT"
+ },
+ "node_modules/@types/gapi.client.calendar-v3": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@types/gapi.client.calendar-v3/-/gapi.client.calendar-v3-0.0.4.tgz",
+ "integrity": "sha512-EwXzPhk98ef+esL1R951e3HFeMQSP6hSBv+YBbaAJoOLLRANV2JY5KHOImBSkk7HKt1PiarQGx5BD8+4eHeeIQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@maxim_mazurok/gapi.client.calendar-v3": "latest"
+ }
+ },
+ "node_modules/@types/gapi.client.discovery-v1": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz",
+ "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@maxim_mazurok/gapi.client.discovery-v1": "latest"
+ }
+ },
+ "node_modules/@types/har-format": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz",
+ "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "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/linkify-it": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/markdown-it": {
+ "version": "14.1.2",
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/linkify-it": "^5",
+ "@types/mdurl": "^2"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
+ "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.5",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
+ "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz",
+ "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.20.0",
+ "@typescript-eslint/type-utils": "8.20.0",
+ "@typescript-eslint/utils": "8.20.0",
+ "@typescript-eslint/visitor-keys": "8.20.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz",
+ "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.20.0",
+ "@typescript-eslint/types": "8.20.0",
+ "@typescript-eslint/typescript-estree": "8.20.0",
+ "@typescript-eslint/visitor-keys": "8.20.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz",
+ "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.20.0",
+ "@typescript-eslint/visitor-keys": "8.20.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz",
+ "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "8.20.0",
+ "@typescript-eslint/utils": "8.20.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz",
+ "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz",
+ "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.20.0",
+ "@typescript-eslint/visitor-keys": "8.20.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz",
+ "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "8.20.0",
+ "@typescript-eslint/types": "8.20.0",
+ "@typescript-eslint/typescript-estree": "8.20.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz",
+ "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.20.0",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
+ "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.26.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz",
+ "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.0",
+ "@vitest/utils": "4.1.0",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz",
+ "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.0",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/mocker/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/@vitest/mocker/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz",
+ "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz",
+ "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.0",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz",
+ "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.0",
+ "@vitest/utils": "4.1.0",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz",
+ "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz",
+ "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.0",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
+ "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@vue/shared": "3.5.30",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
+ "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.30",
+ "@vue/shared": "3.5.30"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
+ "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@vue/compiler-core": "3.5.30",
+ "@vue/compiler-dom": "3.5.30",
+ "@vue/compiler-ssr": "3.5.30",
+ "@vue/shared": "3.5.30",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.8",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
+ "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.30",
+ "@vue/shared": "3.5.30"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.9",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
+ "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.9"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.9",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
+ "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.9",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.9",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
+ "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
+ "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.30"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
+ "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.30",
+ "@vue/shared": "3.5.30"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
+ "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.30",
+ "@vue/runtime-core": "3.5.30",
+ "@vue/shared": "3.5.30",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
+ "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.30",
+ "@vue/shared": "3.5.30"
+ },
+ "peerDependencies": {
+ "vue": "3.5.30"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
+ "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vueuse/core": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz",
+ "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "12.8.2",
+ "@vueuse/shared": "12.8.2",
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/integrations": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz",
+ "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vueuse/core": "12.8.2",
+ "@vueuse/shared": "12.8.2",
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "async-validator": "^4",
+ "axios": "^1",
+ "change-case": "^5",
+ "drauu": "^0.4",
+ "focus-trap": "^7",
+ "fuse.js": "^7",
+ "idb-keyval": "^6",
+ "jwt-decode": "^4",
+ "nprogress": "^0.2",
+ "qrcode": "^1.5",
+ "sortablejs": "^1",
+ "universal-cookie": "^7"
+ },
+ "peerDependenciesMeta": {
+ "async-validator": {
+ "optional": true
+ },
+ "axios": {
+ "optional": true
+ },
+ "change-case": {
+ "optional": true
+ },
+ "drauu": {
+ "optional": true
+ },
+ "focus-trap": {
+ "optional": true
+ },
+ "fuse.js": {
+ "optional": true
+ },
+ "idb-keyval": {
+ "optional": true
+ },
+ "jwt-decode": {
+ "optional": true
+ },
+ "nprogress": {
+ "optional": true
+ },
+ "qrcode": {
+ "optional": true
+ },
+ "sortablejs": {
+ "optional": true
+ },
+ "universal-cookie": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz",
+ "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
},
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz",
- "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/@vueuse/shared": {
+ "version": "12.8.2",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz",
+ "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
+ "dependencies": {
+ "vue": "^3.5.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
},
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz",
- "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
- "cpu": [
- "x64"
- ],
+ "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",
- "optional": true,
- "os": [
- "darwin"
- ]
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+ }
},
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz",
- "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
- "cpu": [
- "arm64"
- ],
+ "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",
+ "peer": true
+ },
+ "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",
+ "peer": true
+ },
+ "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",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webcomponents/custom-elements": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz",
+ "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "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",
+ "peer": true
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true,
+ "license": "Apache-2.0",
+ "peer": true
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "devOptional": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
},
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz",
- "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
- "cpu": [
- "x64"
- ],
+ "node_modules/acorn-import-phases": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
+ "peer": true,
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "peerDependencies": {
+ "acorn": "^8.14.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz",
- "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
- "cpu": [
- "arm"
- ],
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz",
- "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
- "cpu": [
- "arm"
- ],
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz",
- "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">= 14"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz",
- "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "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/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz",
- "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
- "cpu": [
- "loong64"
- ],
+ "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",
- "optional": true,
- "os": [
- "linux"
- ]
+ "peer": true,
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz",
- "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
- "cpu": [
- "ppc64"
- ],
+ "node_modules/ajv-formats/node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "peer": true,
+ "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/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz",
- "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
- "cpu": [
- "riscv64"
- ],
+ "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",
- "optional": true,
- "os": [
- "linux"
- ]
+ "peer": true
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz",
- "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
- "cpu": [
- "riscv64"
- ],
+ "node_modules/algoliasearch": {
+ "version": "5.49.2",
+ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.2.tgz",
+ "integrity": "sha512-1K0wtDaRONwfhL4h8bbJ9qTjmY6rhGgRvvagXkMBsAOMNr+3Q2SffHECh9DIuNVrMA1JwA0zCwhyepgBZVakng==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "@algolia/abtesting": "1.15.2",
+ "@algolia/client-abtesting": "5.49.2",
+ "@algolia/client-analytics": "5.49.2",
+ "@algolia/client-common": "5.49.2",
+ "@algolia/client-insights": "5.49.2",
+ "@algolia/client-personalization": "5.49.2",
+ "@algolia/client-query-suggestions": "5.49.2",
+ "@algolia/client-search": "5.49.2",
+ "@algolia/ingestion": "1.49.2",
+ "@algolia/monitoring": "1.49.2",
+ "@algolia/recommend": "5.49.2",
+ "@algolia/requester-browser-xhr": "5.49.2",
+ "@algolia/requester-fetch": "5.49.2",
+ "@algolia/requester-node-http": "5.49.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
},
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz",
- "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
+ "node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz",
- "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
- "cpu": [
- "x64"
- ],
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
"license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz",
- "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
- "cpu": [
- "x64"
- ],
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
},
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz",
- "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz",
- "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
- "cpu": [
- "arm64"
- ],
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz",
- "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
- "cpu": [
- "ia32"
- ],
+ "node_modules/autoprefixer": {
+ "version": "10.4.20",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
+ "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
"dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "dependencies": {
+ "browserslist": "^4.23.3",
+ "caniuse-lite": "^1.0.30001646",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz",
- "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
- "cpu": [
- "x64"
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "license": "MIT"
},
- "node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.8",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz",
+ "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==",
+ "dev": true,
"license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.8.0"
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
}
},
- "node_modules/@types/babel__core": {
- "version": "7.20.5",
- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
- "dev": true,
+ "node_modules/bignumber.js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+ "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
"license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.20.7",
- "@babel/types": "^7.20.7",
- "@types/babel__generator": "*",
- "@types/babel__template": "*",
- "@types/babel__traverse": "*"
+ "engines": {
+ "node": "*"
}
},
- "node_modules/@types/babel__generator": {
- "version": "7.6.8",
- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
- "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
- "dev": true,
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"license": "MIT",
- "dependencies": {
- "@babel/types": "^7.0.0"
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@types/babel__template": {
- "version": "7.4.4",
- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "node_modules/birpc": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
+ "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@babel/parser": "^7.1.0",
- "@babel/types": "^7.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/@types/babel__traverse": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
- "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true,
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.20.7"
+ "balanced-match": "^1.0.0"
}
},
- "node_modules/@types/chrome": {
- "version": "0.0.296",
- "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.296.tgz",
- "integrity": "sha512-EWVcJlIXi2PzKfnzOXkIfySMFkdOQ6DiKkhbuHeuvTxOcVYeUBXbfiExSJ5EDS4M0gAChO/15tDHqMWP47muYw==",
- "dev": true,
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
- "@types/filesystem": "*",
- "@types/har-format": "*"
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/@types/debug": {
- "version": "4.1.12",
- "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
- "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "@types/ms": "*"
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/@types/eslint": {
- "version": "9.6.1",
- "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
- "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
- "dev": true,
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "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==",
+ "devOptional": true,
"license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/estree": "*",
- "@types/json-schema": "*"
- }
+ "peer": true
},
- "node_modules/@types/eslint-scope": {
- "version": "3.7.7",
- "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
- "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
- "dev": true,
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/eslint": "*",
- "@types/estree": "*"
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
}
},
- "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==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@types/filesystem": {
- "version": "0.0.36",
- "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz",
- "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==",
- "dev": true,
+ "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==",
"license": "MIT",
"dependencies": {
- "@types/filewriter": "*"
+ "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/@types/filewriter": {
- "version": "0.0.33",
- "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
- "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/@types/gapi.client": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz",
- "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==",
- "license": "MIT"
- },
- "node_modules/@types/gapi.client.calendar": {
- "version": "3.0.12",
- "resolved": "https://registry.npmjs.org/@types/gapi.client.calendar/-/gapi.client.calendar-3.0.12.tgz",
- "integrity": "sha512-3H4hubmAuOkYHGSoVkxYAz6zg2g5LhQ1Y0/GLDIt1AkebKZgGiK+5JNFq4Nn2qDkjFh8pMY3TLIC/J5ueMd6YA==",
- "deprecated": "use @types/gapi.client.calendar-v3 instead; see https://github.com/Maxim-Mazurok/google-api-typings-generator/issues/652 for details",
"license": "MIT",
- "dependencies": {
- "@maxim_mazurok/gapi.client.calendar-v3": "latest"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/@types/gapi.client.discovery-v1": {
- "version": "0.0.4",
- "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz",
- "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==",
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"license": "MIT",
- "dependencies": {
- "@maxim_mazurok/gapi.client.discovery-v1": "latest"
+ "engines": {
+ "node": ">= 6"
}
},
- "node_modules/@types/har-format": {
- "version": "1.2.16",
- "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz",
- "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==",
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001779",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz",
+ "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==",
"dev": true,
- "license": "MIT"
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
},
- "node_modules/@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==",
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
},
- "node_modules/@types/ms": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
- "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/@types/node": {
- "version": "22.10.7",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
- "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "undici-types": "~6.20.0"
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
}
},
- "node_modules/@types/prop-types": {
- "version": "15.7.14",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
- "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "18.3.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
- "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
- "devOptional": true,
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "@types/prop-types": "*",
- "csstype": "^3.0.2"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/@types/react-dom": {
- "version": "18.3.5",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
- "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
- "devOptional": true,
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "dev": true,
"license": "MIT",
- "peerDependencies": {
- "@types/react": "^18.0.0"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.20.0.tgz",
- "integrity": "sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==",
+ "node_modules/cheerio": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
+ "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.20.0",
- "@typescript-eslint/type-utils": "8.20.0",
- "@typescript-eslint/utils": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0",
- "graphemer": "^1.4.0",
- "ignore": "^5.3.1",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.0.0"
+ "cheerio-select": "^2.1.0",
+ "dom-serializer": "^2.0.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.1.0",
+ "encoding-sniffer": "^0.2.0",
+ "htmlparser2": "^9.1.0",
+ "parse5": "^7.1.2",
+ "parse5-htmlparser2-tree-adapter": "^7.0.0",
+ "parse5-parser-stream": "^7.1.2",
+ "undici": "^6.19.5",
+ "whatwg-mimetype": "^4.0.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18.17"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
- "node_modules/@typescript-eslint/parser": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.20.0.tgz",
- "integrity": "sha512-gKXG7A5HMyjDIedBi6bUrDcun8GIjnI8qOwVLiY3rx6T/sHP/19XLJOnIq/FgQvWLHja5JN/LSE7eklNBr612g==",
+ "node_modules/cheerio-select": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-select": "^5.1.0",
+ "css-what": "^6.1.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.20.0",
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/typescript-estree": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0",
- "debug": "^4.3.4"
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">= 8.10.0"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "url": "https://paulmillr.com/funding/"
},
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
}
},
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz",
- "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==",
- "dev": true,
- "license": "MIT",
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
"dependencies": {
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0"
+ "is-glob": "^4.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": ">= 6"
}
},
- "node_modules/@typescript-eslint/type-utils": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.20.0.tgz",
- "integrity": "sha512-bPC+j71GGvA7rVNAHAtOjbVXbLN5PkwqMvy1cwGeaxUoRQXVuKCebRoLzm+IPW/NtFFpstn1ummSIasD5t60GA==",
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@typescript-eslint/typescript-estree": "8.20.0",
- "@typescript-eslint/utils": "8.20.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.0.0"
- },
+ "peer": true,
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
+ "url": "https://polar.sh/cva"
}
},
- "node_modules/@typescript-eslint/types": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz",
- "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==",
- "dev": true,
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": ">=6"
}
},
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz",
- "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==",
- "dev": true,
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/visitor-keys": "8.20.0",
- "debug": "^4.3.4",
- "fast-glob": "^3.3.2",
- "is-glob": "^4.0.3",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "ts-api-utils": "^2.0.0"
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
},
- "peerDependencies": {
- "typescript": ">=4.8.4 <5.8.0"
+ "engines": {
+ "node": ">=7.0.0"
}
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "dev": true,
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
}
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "node_modules/color2k": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz",
+ "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==",
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "delayed-stream": "~1.0.0"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">= 0.8"
}
},
- "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
"dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">= 6"
}
},
- "node_modules/@typescript-eslint/utils": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz",
- "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==",
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.4.0",
- "@typescript-eslint/scope-manager": "8.20.0",
- "@typescript-eslint/types": "8.20.0",
- "@typescript-eslint/typescript-estree": "8.20.0"
- },
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
},
"funding": {
"type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz",
- "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==",
+ "node_modules/copy-anything": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz",
+ "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.20.0",
- "eslint-visitor-keys": "^4.2.0"
+ "is-what": "^5.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
+ "url": "https://github.com/sponsors/mesqueeb"
}
},
- "node_modules/@vitejs/plugin-react": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
- "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
- "dev": true,
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
- "@babel/core": "^7.26.0",
- "@babel/plugin-transform-react-jsx-self": "^7.25.9",
- "@babel/plugin-transform-react-jsx-source": "^7.25.9",
- "@types/babel__core": "^7.20.5",
- "react-refresh": "^0.14.2"
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
},
"engines": {
- "node": "^14.18.0 || >=16.0.0"
- },
- "peerDependencies": {
- "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+ "node": ">= 8"
}
},
- "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==",
+ "node_modules/css-select": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+ "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"dev": true,
- "license": "MIT",
- "peer": true,
+ "license": "BSD-2-Clause",
"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",
- "peer": true
- },
- "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",
- "peer": true
- },
- "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",
- "peer": true
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
},
- "node_modules/@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==",
+ "node_modules/css-what": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/floating-point-hex-parser": "1.13.2",
- "@webassemblyjs/helper-api-error": "1.13.2",
- "@xtuc/long": "4.2.2"
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
}
},
- "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==",
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"license": "MIT",
- "peer": true
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
},
- "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==",
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
"dev": true,
"license": "MIT",
- "peer": true,
"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"
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
}
},
- "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==",
+ "node_modules/cssstyle/node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
- "peer": true,
- "dependencies": {
- "@xtuc/ieee754": "^1.2.0"
+ "engines": {
+ "node": ">= 12"
}
},
- "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==",
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
"dev": true,
- "license": "Apache-2.0",
- "peer": true,
+ "license": "MIT",
"dependencies": {
- "@xtuc/long": "4.2.2"
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
}
},
- "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,
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
- "peer": true
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
},
- "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,
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
- "peer": true,
"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"
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
}
},
- "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==",
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
- "peer": true,
- "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"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "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==",
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
- "peer": true,
- "dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@webassemblyjs/helper-buffer": "1.14.1",
- "@webassemblyjs/wasm-gen": "1.14.1",
- "@webassemblyjs/wasm-parser": "1.14.1"
+ "engines": {
+ "node": ">=0.4.0"
}
},
- "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==",
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"license": "MIT",
- "peer": true,
- "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"
+ "engines": {
+ "node": ">=6"
}
},
- "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==",
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "@webassemblyjs/ast": "1.14.1",
- "@xtuc/long": "4.2.2"
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/@webcomponents/custom-elements": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz",
- "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==",
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "license": "MIT"
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "MIT"
},
- "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==",
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"dev": true,
- "license": "BSD-3-Clause",
- "peer": true
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
},
- "node_modules/@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==",
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"dev": true,
- "license": "Apache-2.0",
- "peer": true
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
},
- "node_modules/acorn": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
- "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
},
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"dev": true,
- "license": "MIT",
- "peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
}
},
- "node_modules/acorn-walk": {
- "version": "8.3.4",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
- "dev": true,
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.11.0"
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
},
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 0.4"
}
},
- "node_modules/agent-base": {
- "version": "7.1.4",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
- "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 14"
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "license": "MIT"
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
}
},
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.313",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz",
+ "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "license": "MIT"
+ },
+ "node_modules/emoji-regex-xs": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz",
+ "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/encoding-sniffer": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
+ "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
+ "iconv-lite": "^0.6.3",
+ "whatwg-encoding": "^3.1.1"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
- "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==",
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "ajv": "^8.0.0"
- },
- "peerDependencies": {
- "ajv": "^8.0.0"
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
},
- "peerDependenciesMeta": {
- "ajv": {
- "optional": true
- }
+ "engines": {
+ "node": ">=10.13.0"
}
},
- "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==",
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/ajv-formats/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
- "peer": true
+ "engines": {
+ "node": ">= 0.4"
+ }
},
- "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,
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
- "peer": true,
- "peerDependencies": {
- "ajv": "^6.9.1"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "node_modules/es-module-lexer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+ "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "es-errors": "^1.3.0"
},
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "color-convert": "^2.0.1"
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
},
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "node": ">= 0.4"
}
},
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "license": "MIT"
- },
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
+ "node_modules/esbuild": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
+ "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
},
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.0",
+ "@esbuild/android-arm": "0.25.0",
+ "@esbuild/android-arm64": "0.25.0",
+ "@esbuild/android-x64": "0.25.0",
+ "@esbuild/darwin-arm64": "0.25.0",
+ "@esbuild/darwin-x64": "0.25.0",
+ "@esbuild/freebsd-arm64": "0.25.0",
+ "@esbuild/freebsd-x64": "0.25.0",
+ "@esbuild/linux-arm": "0.25.0",
+ "@esbuild/linux-arm64": "0.25.0",
+ "@esbuild/linux-ia32": "0.25.0",
+ "@esbuild/linux-loong64": "0.25.0",
+ "@esbuild/linux-mips64el": "0.25.0",
+ "@esbuild/linux-ppc64": "0.25.0",
+ "@esbuild/linux-riscv64": "0.25.0",
+ "@esbuild/linux-s390x": "0.25.0",
+ "@esbuild/linux-x64": "0.25.0",
+ "@esbuild/netbsd-arm64": "0.25.0",
+ "@esbuild/netbsd-x64": "0.25.0",
+ "@esbuild/openbsd-arm64": "0.25.0",
+ "@esbuild/openbsd-x64": "0.25.0",
+ "@esbuild/sunos-x64": "0.25.0",
+ "@esbuild/win32-arm64": "0.25.0",
+ "@esbuild/win32-ia32": "0.25.0",
+ "@esbuild/win32-x64": "0.25.0"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">=6"
}
},
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
- "license": "MIT"
- },
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
- "license": "Python-2.0"
- },
- "node_modules/aria-hidden": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
- "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
"license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
"engines": {
"node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/autoprefixer": {
- "version": "10.4.20",
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
- "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
"license": "MIT",
"dependencies": {
- "browserslist": "^4.23.3",
- "caniuse-lite": "^1.0.30001646",
- "fraction.js": "^4.3.7",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.1",
- "postcss-value-parser": "^4.2.0"
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
},
"bin": {
- "autoprefixer": "bin/autoprefixer"
+ "eslint": "bin/eslint.js"
},
"engines": {
- "node": "^10 || ^12 || >=14"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
},
"peerDependencies": {
- "postcss": "^8.1.0"
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
}
},
- "node_modules/axios": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
- "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
+ "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "license": "MIT"
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/bignumber.js": {
- "version": "9.3.1",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
- "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
+ "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": "*"
+ "peerDependencies": {
+ "eslint": ">=8.40"
}
},
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
- "license": "MIT",
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
"engines": {
- "node": ">=8"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://opencollective.com/eslint"
}
},
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
- "license": "ISC"
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
},
- "node_modules/brace-expansion": {
+ "node_modules/eslint/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
@@ -4242,220 +7332,182 @@
"concat-map": "0.0.1"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "license": "MIT",
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
- "fill-range": "^7.1.1"
+ "brace-expansion": "^1.1.7"
},
"engines": {
- "node": ">=8"
+ "node": "*"
}
},
- "node_modules/browserslist": {
- "version": "4.24.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
- "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/browserslist"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
+ "license": "BSD-2-Clause",
"dependencies": {
- "caniuse-lite": "^1.0.30001688",
- "electron-to-chromium": "^1.5.73",
- "node-releases": "^2.0.19",
- "update-browserslist-db": "^1.1.1"
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
},
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"bin": {
- "browserslist": "cli.js"
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
},
"engines": {
- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ "node": ">=4"
}
},
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "node_modules/esprima-extract-comments": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz",
+ "integrity": "sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw==",
"dev": true,
"license": "MIT",
- "peer": true
+ "dependencies": {
+ "esprima": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
},
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
"dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
+ "estraverse": "^5.1.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=0.10"
}
},
- "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==",
- "license": "MIT",
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
+ "estraverse": "^5.2.0"
},
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=4.0"
}
},
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-2-Clause",
"engines": {
- "node": ">=6"
+ "node": ">=4.0"
}
},
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
- "license": "MIT",
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
"engines": {
- "node": ">= 6"
+ "node": ">=0.10.0"
}
},
- "node_modules/caniuse-lite": {
- "version": "1.0.30001743",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
- "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
+ "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,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/browserslist"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "CC-BY-4.0"
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.8.x"
+ }
},
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
+ "license": "Apache-2.0",
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "node": ">=12.0.0"
}
},
- "node_modules/cheerio": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
- "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/extract-comments": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/extract-comments/-/extract-comments-1.1.0.tgz",
+ "integrity": "sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cheerio-select": "^2.1.0",
- "dom-serializer": "^2.0.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.1.0",
- "encoding-sniffer": "^0.2.0",
- "htmlparser2": "^9.1.0",
- "parse5": "^7.1.2",
- "parse5-htmlparser2-tree-adapter": "^7.0.0",
- "parse5-parser-stream": "^7.1.2",
- "undici": "^6.19.5",
- "whatwg-mimetype": "^4.0.0"
+ "esprima-extract-comments": "^1.1.0",
+ "parse-code-context": "^1.0.0"
},
"engines": {
- "node": ">=18.17"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
+ "node": ">=6"
}
},
- "node_modules/cheerio-select": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
- "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
+ "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": "BSD-2-Clause",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-select": "^5.1.0",
- "css-what": "^6.1.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
+ "license": "MIT"
},
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"license": "MIT",
"dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
},
"engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
+ "node": ">=8.6.0"
}
},
- "node_modules/chokidar/node_modules/glob-parent": {
+ "node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
@@ -4467,1544 +7519,1651 @@
"node": ">= 6"
}
},
- "node_modules/chrome-trace-event": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
- "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
+ "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",
- "peer": true,
- "engines": {
- "node": ">=6.0"
- }
+ "license": "MIT"
},
- "node_modules/class-variance-authority": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
- "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
- "license": "Apache-2.0",
- "dependencies": {
- "clsx": "^2.1.1"
- },
- "funding": {
- "url": "https://polar.sh/cva"
- }
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/fastq": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
+ "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
}
},
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
},
"engines": {
- "node": ">=12.5.0"
+ "node": "^12.20 || >= 14.13"
}
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "color-name": "~1.1.4"
+ "flat-cache": "^4.0.0"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=16.0.0"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/color2k": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz",
- "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==",
- "license": "MIT"
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "delayed-stream": "~1.0.0"
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
},
"engines": {
- "node": ">= 0.8"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "license": "MIT",
- "engines": {
- "node": ">= 6"
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "flat": "cli.js"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
},
- "node_modules/confbox": {
- "version": "0.1.8",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
- "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
- "license": "MIT"
+ "license": "ISC"
},
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "node_modules/focus-trap": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz",
+ "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/cookie": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
- "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
+ "dependencies": {
+ "tabbable": "^6.4.0"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
"engines": {
- "node": ">=18"
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
},
"engines": {
- "node": ">= 8"
+ "node": ">= 6"
}
},
- "node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
"dependencies": {
- "boolbase": "^1.0.0",
- "css-what": "^6.1.0",
- "domhandler": "^5.0.2",
- "domutils": "^3.0.1",
- "nth-check": "^2.0.1"
+ "fetch-blob": "^3.1.2"
},
- "funding": {
- "url": "https://github.com/sponsors/fb55"
+ "engines": {
+ "node": ">=12.20.0"
}
},
- "node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"engines": {
- "node": ">= 6"
+ "node": "*"
},
"funding": {
- "url": "https://github.com/sponsors/fb55"
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
}
},
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "node_modules/framer-motion": {
+ "version": "12.23.24",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
+ "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
- "bin": {
- "cssesc": "bin/cssesc"
+ "dependencies": {
+ "motion-dom": "^12.23.23",
+ "motion-utils": "^12.23.6",
+ "tslib": "^2.4.0"
},
- "engines": {
- "node": ">=4"
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true,
- "license": "MIT"
+ "node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">= 12"
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/date-fns": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
- "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/kossnocorp"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
- "license": "MIT",
+ "node_modules/gaxios": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.1.tgz",
+ "integrity": "sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==",
+ "license": "Apache-2.0",
"dependencies": {
- "ms": "^2.1.3"
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "node-fetch": "^3.3.2"
},
"engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
+ "node": ">=18"
}
},
- "node_modules/decimal.js": {
- "version": "10.5.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
- "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
- "license": "MIT"
+ "node_modules/gcp-metadata": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz",
+ "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "gaxios": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
},
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
- "license": "MIT"
- },
- "node_modules/deepmerge": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=6.9.0"
}
},
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
"engines": {
- "node": ">=0.4.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
- "dev": true,
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
- "node_modules/detect-node-es": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
- "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
- "license": "MIT"
- },
- "node_modules/didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
- "license": "Apache-2.0"
- },
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "license": "MIT"
- },
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dev": true,
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
},
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "license": "BSD-2-Clause"
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "license": "ISC",
"dependencies": {
- "domelementtype": "^2.3.0"
+ "is-glob": "^4.0.3"
},
"engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
+ "node": ">=10.13.0"
}
},
- "node_modules/domutils": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
- "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "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",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
+ "peer": true
+ },
+ "node_modules/globals": {
+ "version": "15.14.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
+ "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
+ "node_modules/globrex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
+ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
+ "license": "MIT"
+ },
+ "node_modules/google-auth-library": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.3.0.tgz",
+ "integrity": "sha512-ylSE3RlCRZfZB56PFJSfUCuiuPq83Fx8hqu1KPWGK8FVdSaxlp/qkeMMX/DT/18xkwXIHvXEXkZsljRwfrdEfQ==",
+ "license": "Apache-2.0",
"dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^7.0.0",
+ "gcp-metadata": "^7.0.0",
+ "google-logging-utils": "^1.0.0",
+ "gtoken": "^8.0.0",
+ "jws": "^4.0.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=18"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "license": "MIT"
+ "node_modules/google-logging-utils": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.1.tgz",
+ "integrity": "sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=14"
+ }
},
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "node_modules/googleapis": {
+ "version": "160.0.0",
+ "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-160.0.0.tgz",
+ "integrity": "sha512-lAGFQVSeYvWKCxeCQKo20xWFFDgnWuJYbLn92IgLrT4UTYVOGdrZ9XTqgWJf316isE9KdfuDY5X8Tu4ZrXSFig==",
"license": "Apache-2.0",
"dependencies": {
- "safe-buffer": "^5.0.1"
+ "google-auth-library": "^10.2.0",
+ "googleapis-common": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/electron-to-chromium": {
- "version": "1.5.82",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.82.tgz",
- "integrity": "sha512-Zq16uk1hfQhyGx5GpwPAYDwddJuSGhtRhgOA2mCxANYaDT79nAeGnaXogMGng4KqLaJUVnOnuL0+TDop9nLOiA==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "license": "MIT"
+ "node_modules/googleapis-common": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.0.tgz",
+ "integrity": "sha512-66if47It7y+Sab3HMkwEXx1kCq9qUC9px8ZXoj1CMrmLmUw81GpbnsNlXnlyZyGbGPGcj+tDD9XsZ23m7GLaJQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "extend": "^3.0.2",
+ "gaxios": "^7.0.0-rc.4",
+ "google-auth-library": "^10.1.0",
+ "qs": "^6.7.0",
+ "url-template": "^2.0.8"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
},
- "node_modules/encoding-sniffer": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
- "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
- "dev": true,
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
- "dependencies": {
- "iconv-lite": "^0.6.3",
- "whatwg-encoding": "^3.1.1"
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/enhanced-resolve": {
- "version": "5.18.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
- "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==",
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/gtoken": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz",
+ "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==",
"license": "MIT",
"dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
+ "gaxios": "^7.0.0",
+ "jws": "^4.0.0"
},
"engines": {
- "node": ">=10.13.0"
+ "node": ">=18"
}
},
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">=8"
}
},
- "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==",
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/es-module-lexer": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
- "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
+ "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",
- "peer": 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==",
- "license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0"
+ "has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
+ "function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
- "node_modules/esbuild": {
- "version": "0.25.0",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
- "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
- "devOptional": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.0",
- "@esbuild/android-arm": "0.25.0",
- "@esbuild/android-arm64": "0.25.0",
- "@esbuild/android-x64": "0.25.0",
- "@esbuild/darwin-arm64": "0.25.0",
- "@esbuild/darwin-x64": "0.25.0",
- "@esbuild/freebsd-arm64": "0.25.0",
- "@esbuild/freebsd-x64": "0.25.0",
- "@esbuild/linux-arm": "0.25.0",
- "@esbuild/linux-arm64": "0.25.0",
- "@esbuild/linux-ia32": "0.25.0",
- "@esbuild/linux-loong64": "0.25.0",
- "@esbuild/linux-mips64el": "0.25.0",
- "@esbuild/linux-ppc64": "0.25.0",
- "@esbuild/linux-riscv64": "0.25.0",
- "@esbuild/linux-s390x": "0.25.0",
- "@esbuild/linux-x64": "0.25.0",
- "@esbuild/netbsd-arm64": "0.25.0",
- "@esbuild/netbsd-x64": "0.25.0",
- "@esbuild/openbsd-arm64": "0.25.0",
- "@esbuild/openbsd-x64": "0.25.0",
- "@esbuild/sunos-x64": "0.25.0",
- "@esbuild/win32-arm64": "0.25.0",
- "@esbuild/win32-ia32": "0.25.0",
- "@esbuild/win32-x64": "0.25.0"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
+ "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=10"
+ "dependencies": {
+ "@types/hast": "^3.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/eslint": {
- "version": "9.18.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
- "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==",
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.19.0",
- "@eslint/core": "^0.10.0",
- "@eslint/eslintrc": "^3.2.0",
- "@eslint/js": "9.18.0",
- "@eslint/plugin-kit": "^0.2.5",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.1",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.2.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
+ "whatwg-encoding": "^3.1.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
- },
- "peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
+ "node": ">=18"
}
},
- "node_modules/eslint-plugin-react-hooks": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
- "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
- "dev": true,
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ "dependencies": {
+ "void-elements": "3.1.0"
}
},
- "node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.18",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
- "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
"dev": true,
"license": "MIT",
- "peerDependencies": {
- "eslint": ">=8.40"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/eslint-scope": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
- "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+ "node_modules/htmlparser2": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
+ "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
"dev": true,
- "license": "BSD-2-Clause",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
"dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.1.0",
+ "entities": "^4.5.0"
}
},
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
},
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "engines": {
+ "node": ">= 14"
}
},
- "node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
"dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
+ "agent-base": "^7.1.2",
+ "debug": "4"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">= 14"
}
},
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
+ "node_modules/i18next": {
+ "version": "25.8.18",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.18.tgz",
+ "integrity": "sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6"
},
- "engines": {
- "node": ">=4"
+ "peerDependencies": {
+ "typescript": "^5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
- "node_modules/esprima-extract-comments": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz",
- "integrity": "sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw==",
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "esprima": "^4.0.0"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
- "node": ">=4"
+ "node": ">=0.10.0"
}
},
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "estraverse": "^5.1.0"
- },
+ "license": "MIT",
"engines": {
- "node": ">=0.10"
+ "node": ">= 4"
}
},
- "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==",
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"dependencies": {
- "estraverse": "^5.2.0"
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
},
"engines": {
- "node": ">=4.0"
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"engines": {
- "node": ">=4.0"
+ "node": ">=0.8.19"
}
},
- "node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/events": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
- "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=0.8.x"
+ "node_modules/intl-messageformat": {
+ "version": "10.7.14",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.14.tgz",
+ "integrity": "sha512-mMGnE4E1otdEutV5vLUdCxRJygHB5ozUBxsPB5qhitewssrS/qGruq9bmvIRkkGsNeK5ZWLfYRld18UHGTIifQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.3.2",
+ "@formatjs/fast-memoize": "2.2.6",
+ "@formatjs/icu-messageformat-parser": "2.11.0",
+ "tslib": "2"
}
},
- "node_modules/extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"license": "MIT"
},
- "node_modules/extract-comments": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/extract-comments/-/extract-comments-1.1.0.tgz",
- "integrity": "sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q==",
- "dev": true,
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"license": "MIT",
"dependencies": {
- "esprima-extract-comments": "^1.1.0",
- "parse-code-context": "^1.0.0"
+ "binary-extensions": "^2.0.0"
},
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
- "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-glob": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
- "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
- "@nodelib/fs.stat": "^2.0.2",
- "@nodelib/fs.walk": "^1.2.3",
- "glob-parent": "^5.1.2",
- "merge2": "^1.3.0",
- "micromatch": "^4.0.8"
+ "hasown": "^2.0.2"
},
"engines": {
- "node": ">=8.6.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/fast-glob/node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
"engines": {
- "node": ">= 6"
+ "node": ">=0.10.0"
}
},
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-uri": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz",
- "integrity": "sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fastify"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/fastify"
- }
- ],
- "license": "BSD-3-Clause",
- "peer": true
- },
- "node_modules/fastq": {
- "version": "1.18.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
- "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/fetch-blob": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"license": "MIT",
"dependencies": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
+ "is-extglob": "^2.1.1"
},
"engines": {
- "node": "^12.20 || >= 14.13"
+ "node": ">=0.10.0"
}
},
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-what": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz",
+ "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
"engines": {
- "node": ">=16.0.0"
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
}
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "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",
+ "peer": true,
"dependencies": {
- "to-regex-range": "^5.0.1"
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 10.13.0"
}
},
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
+ "has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "license": "BSD-3-Clause",
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "license": "MIT",
"bin": {
- "flat": "cli.js"
+ "jiti": "bin/jiti.js"
}
},
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
+ "argparse": "^2.0.1"
},
- "engines": {
- "node": ">=16"
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
}
},
- "node_modules/flatted": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
- "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "node_modules/jsdom": {
+ "version": "25.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
+ "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
"dev": true,
- "license": "ISC"
- },
- "node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
"license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.1.0",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.4.3",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.5",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.12",
+ "parse5": "^7.1.2",
+ "rrweb-cssom": "^0.7.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
"engines": {
- "node": ">=4.0"
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^2.11.2"
},
"peerDependenciesMeta": {
- "debug": {
+ "canvas": {
"optional": true
}
}
},
- "node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
},
"engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=6"
}
},
- "node_modules/form-data": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
- "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "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,
+ "license": "MIT",
+ "peer": 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/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
},
"engines": {
- "node": ">= 6"
+ "node": ">=6"
}
},
- "node_modules/formdata-polyfill": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "fetch-blob": "^3.1.2"
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
},
"engines": {
- "node": ">=12.20.0"
+ "node": ">= 0.8.0"
}
},
- "node_modules/fraction.js": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
- "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
- "dev": true,
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"license": "MIT",
"engines": {
- "node": "*"
+ "node": ">=14"
},
"funding": {
- "type": "patreon",
- "url": "https://github.com/sponsors/rawify"
+ "url": "https://github.com/sponsors/antonk52"
}
},
- "node_modules/framer-motion": {
- "version": "12.23.24",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
- "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/loader-runner": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz",
+ "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "motion-dom": "^12.23.23",
- "motion-utils": "^12.23.6",
- "tslib": "^2.4.0"
- },
- "peerDependencies": {
- "@emotion/is-prop-valid": "*",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
+ "peer": true,
+ "engines": {
+ "node": ">=6.11.5"
},
- "peerDependenciesMeta": {
- "@emotion/is-prop-valid": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/fs-extra": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
- "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
},
"engines": {
- "node": ">=12"
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
}
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
"engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "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==",
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
}
},
- "node_modules/gaxios": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.1.tgz",
- "integrity": "sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==",
- "license": "Apache-2.0",
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
- "extend": "^3.0.2",
- "https-proxy-agent": "^7.0.1",
- "node-fetch": "^3.3.2"
- },
- "engines": {
- "node": ">=18"
+ "yallist": "^3.0.2"
}
},
- "node_modules/gcp-metadata": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-7.0.1.tgz",
- "integrity": "sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==",
- "license": "Apache-2.0",
+ "node_modules/lucide-react": {
+ "version": "0.471.2",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.471.2.tgz",
+ "integrity": "sha512-A8fDycQxGeaSOTaI7Bm4fg8LBXO7Qr9ORAX47bDRvugCsjLIliugQO0PkKFoeAD57LIQwlWKd3NIQ3J7hYp84g==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "gaxios": "^7.0.0",
- "google-logging-utils": "^1.0.0",
- "json-bigint": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
+ "@jridgewell/sourcemap-codec": "^1.5.0"
}
},
- "node_modules/gensync": {
- "version": "1.0.0-beta.2",
- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "node_modules/mark.js": {
+ "version": "8.11.1",
+ "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz",
+ "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
- "node": ">=6.9.0"
+ "node": ">= 0.4"
}
},
- "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==",
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "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"
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/get-nonce": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
- "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "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",
- "engines": {
- "node": ">=6"
- }
+ "peer": true
},
- "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==",
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
"engines": {
- "node": ">= 0.4"
+ "node": ">= 8"
}
},
- "node_modules/glob": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz",
- "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==",
- "license": "ISC",
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^4.0.1",
- "minimatch": "^10.0.0",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^2.0.0"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
}
},
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "license": "ISC",
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
"dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
}
},
- "node_modules/glob-to-regexp": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
"dev": true,
- "license": "BSD-2-Clause",
- "peer": true
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
},
- "node_modules/glob/node_modules/brace-expansion": {
+ "node_modules/micromark-util-types": {
"version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
},
- "node_modules/glob/node_modules/minimatch": {
- "version": "10.0.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
- "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
- "license": "ISC",
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
},
"engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=8.6"
}
},
- "node_modules/globals": {
- "version": "15.14.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
- "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">= 0.6"
}
},
- "node_modules/globrex": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
- "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
- "license": "MIT"
- },
- "node_modules/google-auth-library": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.3.0.tgz",
- "integrity": "sha512-ylSE3RlCRZfZB56PFJSfUCuiuPq83Fx8hqu1KPWGK8FVdSaxlp/qkeMMX/DT/18xkwXIHvXEXkZsljRwfrdEfQ==",
- "license": "Apache-2.0",
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "base64-js": "^1.3.0",
- "ecdsa-sig-formatter": "^1.0.11",
- "gaxios": "^7.0.0",
- "gcp-metadata": "^7.0.0",
- "google-logging-utils": "^1.0.0",
- "gtoken": "^8.0.0",
- "jws": "^4.0.0"
+ "mime-db": "1.52.0"
},
"engines": {
- "node": ">=18"
+ "node": ">= 0.6"
}
},
- "node_modules/google-logging-utils": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.1.tgz",
- "integrity": "sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==",
- "license": "Apache-2.0",
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=14"
+ "node": ">=4"
}
},
- "node_modules/googleapis": {
- "version": "160.0.0",
- "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-160.0.0.tgz",
- "integrity": "sha512-lAGFQVSeYvWKCxeCQKo20xWFFDgnWuJYbLn92IgLrT4UTYVOGdrZ9XTqgWJf316isE9KdfuDY5X8Tu4ZrXSFig==",
- "license": "Apache-2.0",
+ "node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "license": "ISC",
"dependencies": {
- "google-auth-library": "^10.2.0",
- "googleapis-common": "^8.0.0"
+ "brace-expansion": "^2.0.2"
},
"engines": {
- "node": ">=18"
- }
- },
- "node_modules/googleapis-common": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.0.tgz",
- "integrity": "sha512-66if47It7y+Sab3HMkwEXx1kCq9qUC9px8ZXoj1CMrmLmUw81GpbnsNlXnlyZyGbGPGcj+tDD9XsZ23m7GLaJQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "extend": "^3.0.2",
- "gaxios": "^7.0.0-rc.4",
- "google-auth-library": "^10.1.0",
- "qs": "^6.7.0",
- "url-template": "^2.0.8"
+ "node": ">=16 || 14 >=14.17"
},
- "engines": {
- "node": ">=18.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "license": "ISC",
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=16 || 14 >=14.17"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "node_modules/minisearch": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz",
+ "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==",
"dev": true,
- "license": "ISC"
+ "license": "MIT"
},
- "node_modules/graphemer": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"dev": true,
"license": "MIT"
},
- "node_modules/gtoken": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz",
- "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==",
+ "node_modules/mlly": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
+ "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "gaxios": "^7.0.0",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=18"
+ "acorn": "^8.14.0",
+ "pathe": "^2.0.1",
+ "pkg-types": "^1.3.0",
+ "ufo": "^1.5.4"
}
},
- "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,
+ "node_modules/motion": {
+ "version": "12.23.24",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz",
+ "integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==",
"license": "MIT",
- "engines": {
- "node": ">=8"
+ "dependencies": {
+ "framer-motion": "^12.23.24",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
}
},
- "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==",
+ "node_modules/motion-dom": {
+ "version": "12.23.23",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
+ "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "dependencies": {
+ "motion-utils": "^12.23.6"
}
},
- "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==",
+ "node_modules/motion-utils": {
+ "version": "12.23.6",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
+ "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"license": "MIT",
"dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
}
},
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
},
"engines": {
- "node": ">= 0.4"
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/htmlparser2": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
- "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
- "url": "https://github.com/sponsors/fb55"
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.1.0",
- "entities": "^4.5.0"
+ "engines": {
+ "node": ">=10.5.0"
}
},
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "node_modules/node-fetch": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
},
"engines": {
- "node": ">= 14"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
}
},
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "node_modules/node-releases": {
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">= 4"
+ "node": ">=0.10.0"
}
},
- "node_modules/import-fresh": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
- "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-2-Clause",
"dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
+ "boolbase": "^1.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "node_modules/nwsapi": {
+ "version": "2.2.23",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
+ "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/intl-messageformat": {
- "version": "10.7.14",
- "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.14.tgz",
- "integrity": "sha512-mMGnE4E1otdEutV5vLUdCxRJygHB5ozUBxsPB5qhitewssrS/qGruq9bmvIRkkGsNeK5ZWLfYRld18UHGTIifQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@formatjs/ecma402-abstract": "2.3.2",
- "@formatjs/fast-memoize": "2.2.6",
- "@formatjs/icu-messageformat-parser": "2.11.0",
- "tslib": "2"
+ "node": ">=0.10.0"
}
},
- "node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
- "license": "MIT"
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 6"
}
},
- "node_modules/is-core-module": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
- "dependencies": {
- "hasown": "^2.0.2"
- },
"engines": {
"node": ">= 0.4"
},
@@ -6012,644 +9171,736 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-extglob": {
+ "node_modules/obug": {
"version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/oniguruma-to-es": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz",
+ "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "dependencies": {
+ "emoji-regex-xs": "^1.0.0",
+ "regex": "^6.0.1",
+ "regex-recursion": "^6.0.2"
}
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
"engines": {
- "node": ">=8"
+ "node": ">= 0.8.0"
}
},
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "is-extglob": "^2.1.1"
+ "yocto-queue": "^0.1.0"
},
"engines": {
- "node": ">=0.10.0"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
"engines": {
- "node": ">=0.12.0"
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "license": "ISC"
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
},
- "node_modules/jackspeak": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
- "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
- "license": "BlueOak-1.0.0",
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "@isaacs/cliui": "^8.0.2"
+ "callsites": "^3.0.0"
},
"engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-code-context": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/parse-code-context/-/parse-code-context-1.0.0.tgz",
+ "integrity": "sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
- "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==",
+ "node_modules/parse5": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "@types/node": "*",
- "merge-stream": "^2.0.0",
- "supports-color": "^8.0.0"
+ "entities": "^4.5.0"
},
- "engines": {
- "node": ">= 10.13.0"
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/jest-worker/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/parse5-htmlparser2-tree-adapter": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
+ "domhandler": "^5.0.3",
+ "parse5": "^7.0.0"
},
"funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
- }
- },
- "node_modules/jiti": {
- "version": "1.21.7",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
- "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
- "license": "MIT",
- "bin": {
- "jiti": "bin/jiti.js"
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT"
- },
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "node_modules/parse5-parser-stream": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
+ "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "argparse": "^2.0.1"
+ "parse5": "^7.0.0"
},
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/jsesc": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
- "bin": {
- "jsesc": "bin/jsesc"
- },
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
- "node_modules/json-bigint": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
- "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "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==",
"license": "MIT",
- "dependencies": {
- "bignumber.js": "^9.0.0"
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
+ "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==",
"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,
- "license": "MIT",
- "peer": 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==",
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"dev": true,
"license": "MIT"
},
- "node_modules/json5": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "json5": "lib/cli.js"
- },
- "engines": {
- "node": ">=6"
- }
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
},
- "node_modules/jsonfile": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
- "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "dev": true,
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"license": "MIT",
- "dependencies": {
- "universalify": "^2.0.0"
+ "engines": {
+ "node": ">=8.6"
},
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/jwa": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
- "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "^1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"license": "MIT",
- "dependencies": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
+ "engines": {
+ "node": ">= 6"
}
},
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "json-buffer": "3.0.1"
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
}
},
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
- "license": "MIT",
- "engines": {
- "node": ">=14"
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
},
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "license": "MIT"
- },
- "node_modules/loader-runner": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
- "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
"engines": {
- "node": ">=6.11.5"
+ "node": "^10 || ^12 || >=14"
}
},
- "node_modules/local-pkg": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
- "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
- "dev": true,
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"license": "MIT",
"dependencies": {
- "mlly": "^1.7.3",
- "pkg-types": "^1.2.1"
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
},
"engines": {
- "node": ">=14"
+ "node": ">=14.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/antfu"
+ "peerDependencies": {
+ "postcss": "^8.0.0"
}
},
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"license": "MIT",
"dependencies": {
- "p-locate": "^5.0.0"
+ "camelcase-css": "^2.0.1"
},
"engines": {
- "node": ">=10"
+ "node": "^12 || ^14 || >= 16"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
}
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
},
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "yallist": "^3.0.2"
- }
- },
- "node_modules/lucide-react": {
- "version": "0.471.2",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.471.2.tgz",
- "integrity": "sha512-A8fDycQxGeaSOTaI7Bm4fg8LBXO7Qr9ORAX47bDRvugCsjLIliugQO0PkKFoeAD57LIQwlWKd3NIQ3J7hYp84g==",
- "license": "ISC",
"peerDependencies": {
- "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
}
},
- "node_modules/magic-string": {
- "version": "0.30.17",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
- "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
- "dev": true,
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
}
},
- "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==",
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
"engines": {
- "node": ">= 0.4"
+ "node": ">=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==",
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
+ "node_modules/preact": {
+ "version": "10.29.0",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz",
+ "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==",
"dev": true,
"license": "MIT",
- "peer": true
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
},
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">= 8"
+ "node": ">= 0.8.0"
}
},
- "node_modules/micromatch": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "node_modules/prettier": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "braces": "^3.0.3",
- "picomatch": "^2.3.1"
+ "bin": {
+ "prettier": "bin/prettier.cjs"
},
"engines": {
- "node": ">=8.6"
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "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==",
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 0.6"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "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==",
+ "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",
- "dependencies": {
- "mime-db": "1.52.0"
- },
"engines": {
- "node": ">= 0.6"
+ "node": ">=6"
}
},
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
+ "node_modules/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
+ "license": "BSD-3-Clause",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "side-channel": "^1.1.0"
},
"engines": {
- "node": "*"
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "license": "ISC",
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "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/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": ">=0.10.0"
}
},
- "node_modules/mlly": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
- "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
- "dev": true,
+ "node_modules/react-colorful": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
+ "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.14.0",
- "pathe": "^2.0.1",
- "pkg-types": "^1.3.0",
- "ufo": "^1.5.4"
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
}
},
- "node_modules/motion": {
- "version": "12.23.24",
- "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz",
- "integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==",
+ "node_modules/react-i18next": {
+ "version": "16.5.8",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.8.tgz",
+ "integrity": "sha512-2ABeHHlakxVY+LSirD+OiERxFL6+zip0PaHo979bgwzeHg27Sqc82xxXWIrSFmfWX0ZkrvXMHwhsi/NGUf5VQg==",
"license": "MIT",
"dependencies": {
- "framer-motion": "^12.23.24",
- "tslib": "^2.4.0"
+ "@babel/runtime": "^7.28.4",
+ "html-parse-stringify": "^3.0.1",
+ "use-sync-external-store": "^1.6.0"
},
"peerDependencies": {
- "@emotion/is-prop-valid": "*",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
+ "i18next": ">= 25.6.2",
+ "react": ">= 16.8.0",
+ "typescript": "^5"
},
"peerDependenciesMeta": {
- "@emotion/is-prop-valid": {
+ "react-dom": {
"optional": true
},
- "react": {
+ "react-native": {
"optional": true
},
- "react-dom": {
+ "typescript": {
"optional": true
}
}
},
- "node_modules/motion-dom": {
- "version": "12.23.23",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
- "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "motion-utils": "^12.23.6"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/motion-utils": {
- "version": "12.23.6",
- "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
- "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
- "license": "MIT"
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "node_modules/react-remove-scroll": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz",
+ "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==",
"license": "MIT",
"dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
"license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
},
"engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "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/react-router": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
+ "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
"license": "MIT",
- "peer": true
- },
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
}
- ],
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz",
+ "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==",
"license": "MIT",
+ "dependencies": {
+ "react-router": "7.13.1"
+ },
"engines": {
- "node": ">=10.5.0"
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
}
},
- "node_modules/node-fetch": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
"license": "MIT",
"dependencies": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">=10"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/node-fetch"
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/node-releases": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
- "dev": true,
- "license": "MIT"
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
},
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8.10.0"
}
},
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
- "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "node_modules/regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz",
+ "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==",
"dev": true,
- "license": "BSD-2-Clause",
+ "license": "MIT",
"dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
+ "regex-utilities": "^2.3.0"
}
},
- "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==",
+ "node_modules/regex-recursion": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz",
+ "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=0.10.0"
+ "dependencies": {
+ "regex-utilities": "^2.3.0"
}
},
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "node_modules/regex-utilities": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz",
+ "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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",
+ "peer": true,
"engines": {
- "node": ">= 6"
+ "node": ">=0.10.0"
}
},
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
"engines": {
"node": ">= 0.4"
},
@@ -6657,658 +9908,715 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
"engines": {
- "node": ">= 0.8.0"
+ "node": ">=4"
}
},
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
- "yocto-queue": "^0.1.0"
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
},
"engines": {
- "node": ">=10"
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
}
},
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "node_modules/rrweb-cssom": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "queue-microtask": "^1.2.2"
}
},
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "license": "BlueOak-1.0.0"
+ "node_modules/rxjs": {
+ "version": "7.5.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz",
+ "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
},
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
- "license": "MIT",
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
"dependencies": {
- "callsites": "^3.0.0"
+ "xmlchars": "^2.2.0"
},
"engines": {
- "node": ">=6"
+ "node": ">=v12.22.7"
}
},
- "node_modules/parse-code-context": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/parse-code-context/-/parse-code-context-1.0.0.tgz",
- "integrity": "sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA==",
- "dev": true,
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "dependencies": {
+ "loose-envify": "^1.1.0"
}
},
- "node_modules/parse5": {
- "version": "7.2.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
- "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "node_modules/schema-utils": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
+ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "entities": "^4.5.0"
+ "@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": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/parse5-htmlparser2-tree-adapter": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
- "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
+ "node_modules/schema-utils/node_modules/ajv": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "domhandler": "^5.0.3",
- "parse5": "^7.0.0"
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
},
"funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/parse5-parser-stream": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
- "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
+ "node_modules/schema-utils/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",
+ "peer": true,
"dependencies": {
- "parse5": "^7.0.0"
+ "fast-deep-equal": "^3.1.3"
},
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
+ "peerDependencies": {
+ "ajv": "^8.8.2"
}
},
- "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==",
+ "node_modules/schema-utils/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",
- "engines": {
- "node": ">=8"
- }
+ "peer": true
},
- "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==",
+ "node_modules/search-insights": {
+ "version": "2.17.3",
+ "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz",
+ "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
- "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
+ "peer": true
},
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "11.0.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz",
- "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==",
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
"license": "ISC",
- "engines": {
- "node": "20 || >=22"
+ "bin": {
+ "semver": "bin/semver.js"
}
},
- "node_modules/pathe": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz",
- "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
},
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "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==",
"license": "MIT",
- "engines": {
- "node": ">=8.6"
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "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==",
"license": "MIT",
"engines": {
- "node": ">= 6"
+ "node": ">=8"
}
},
- "node_modules/pkg-types": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
- "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "node_modules/shiki": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz",
+ "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "confbox": "^0.1.8",
- "mlly": "^1.7.4",
- "pathe": "^2.0.1"
+ "@shikijs/core": "2.5.0",
+ "@shikijs/engine-javascript": "2.5.0",
+ "@shikijs/engine-oniguruma": "2.5.0",
+ "@shikijs/langs": "2.5.0",
+ "@shikijs/themes": "2.5.0",
+ "@shikijs/types": "2.5.0",
+ "@shikijs/vscode-textmate": "^10.0.2",
+ "@types/hast": "^3.0.4"
}
},
- "node_modules/postcss": {
- "version": "8.5.3",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
- "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.8",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
},
"engines": {
- "node": "^10 || ^12 || >=14"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/postcss-import": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
- "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">= 0.4"
},
- "peerDependencies": {
- "postcss": "^8.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/postcss-js": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
- "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
- "camelcase-css": "^2.0.1"
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
},
"engines": {
- "node": "^12 || ^14 || >= 16"
+ "node": ">= 0.4"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.4.21"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/postcss-load-config": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
- "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
"dependencies": {
- "lilconfig": "^3.0.0",
- "yaml": "^2.3.4"
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
},
"engines": {
- "node": ">= 14"
+ "node": ">= 0.4"
},
- "peerDependencies": {
- "postcss": ">=8.0.9",
- "ts-node": ">=9.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
},
- "peerDependenciesMeta": {
- "postcss": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/postcss-nested": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
- "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"license": "MIT",
"dependencies": {
- "postcss-selector-parser": "^6.1.1"
- },
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
"engines": {
- "node": ">=12.0"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
+ "node": ">= 8"
}
},
- "node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "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==",
+ "devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
}
},
- "node_modules/postcss-value-parser": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
- "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
- "license": "MIT"
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
},
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"dev": true,
- "license": "MIT",
+ "license": "BSD-3-Clause",
"engines": {
- "node": ">= 0.8.0"
+ "node": ">=0.10.0"
}
},
- "node_modules/prettier": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
- "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
+ "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
- "bin": {
- "prettier": "bin/prettier.cjs"
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/prettier/prettier?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
"engines": {
- "node": ">=6"
+ "node": ">=8"
}
},
- "node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
"engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=8"
}
},
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
- "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
- "peer": true,
"dependencies": {
- "safe-buffer": "^5.1.0"
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0"
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
},
- "engines": {
- "node": ">=0.10.0"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/react-colorful": {
- "version": "5.6.1",
- "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
- "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
+ "node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"license": "MIT",
- "peerDependencies": {
- "react": ">=16.8.0",
- "react-dom": ">=16.8.0"
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/react-dom": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
- "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
- "loose-envify": "^1.1.0",
- "scheduler": "^0.23.2"
+ "ansi-regex": "^5.0.1"
},
- "peerDependencies": {
- "react": "^18.3.1"
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/react-refresh": {
- "version": "0.14.2",
- "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
- "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
- "dev": true,
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/react-remove-scroll": {
- "version": "2.6.3",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz",
- "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==",
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "react-remove-scroll-bar": "^2.3.7",
- "react-style-singleton": "^2.2.3",
- "tslib": "^2.1.0",
- "use-callback-ref": "^1.3.3",
- "use-sidecar": "^1.1.3"
+ "min-indent": "^1.0.0"
},
"engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
+ "node": ">=8"
}
},
- "node_modules/react-remove-scroll-bar": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
- "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "react-style-singleton": "^2.2.2",
- "tslib": "^2.0.0"
- },
"engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ "node": ">=8"
},
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/react-router": {
- "version": "7.9.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz",
- "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==",
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"license": "MIT",
"dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
},
"engines": {
- "node": ">=20.0.0"
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
},
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
},
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/react-router-dom": {
- "version": "7.9.1",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz",
- "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==",
- "license": "MIT",
+ "node_modules/sucrase/node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "react-router": "7.9.1"
+ "@isaacs/cliui": "^8.0.2"
},
- "engines": {
- "node": ">=20.0.0"
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
},
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
}
},
- "node_modules/react-style-singleton": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
- "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
- "license": "MIT",
+ "node_modules/sucrase/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "license": "ISC"
+ },
+ "node_modules/sucrase/node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "get-nonce": "^1.0.0",
- "tslib": "^2.0.0"
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ "node": ">=16 || 14 >=14.18"
},
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/read-cache": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
- "license": "MIT",
- "dependencies": {
- "pify": "^2.3.0"
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "node_modules/superjson": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz",
+ "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "picomatch": "^2.2.1"
+ "copy-anything": "^4"
},
"engines": {
- "node": ">=8.10.0"
+ "node": ">=16"
}
},
- "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==",
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
- "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/resolve": {
- "version": "1.22.10",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "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==",
"license": "MIT",
- "dependencies": {
- "is-core-module": "^2.16.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
"engines": {
"node": ">= 0.4"
},
@@ -7316,147 +10624,148 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tabbable": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz",
+ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tailwind-merge": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
+ "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"license": "MIT",
- "engines": {
- "node": ">=4"
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
}
},
- "node_modules/reusify": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
- "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "node_modules/tailwind-variants": {
+ "version": "0.1.20",
+ "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.1.20.tgz",
+ "integrity": "sha512-AMh7x313t/V+eTySKB0Dal08RHY7ggYK0MSn/ad8wKWOrDUIzyiWNayRUm2PIJ4VRkvRnfNuyRuKbLV3EN+ewQ==",
"license": "MIT",
+ "dependencies": {
+ "tailwind-merge": "^1.14.0"
+ },
"engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
+ "node": ">=16.x",
+ "pnpm": ">=7.x"
+ },
+ "peerDependencies": {
+ "tailwindcss": "*"
}
},
- "node_modules/rollup": {
- "version": "4.50.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz",
- "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
- "devOptional": true,
+ "node_modules/tailwind-variants/node_modules/tailwind-merge": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
+ "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.8"
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.6",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
},
"bin": {
- "rollup": "dist/bin/rollup"
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
},
"engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.50.2",
- "@rollup/rollup-android-arm64": "4.50.2",
- "@rollup/rollup-darwin-arm64": "4.50.2",
- "@rollup/rollup-darwin-x64": "4.50.2",
- "@rollup/rollup-freebsd-arm64": "4.50.2",
- "@rollup/rollup-freebsd-x64": "4.50.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.50.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.50.2",
- "@rollup/rollup-linux-arm64-gnu": "4.50.2",
- "@rollup/rollup-linux-arm64-musl": "4.50.2",
- "@rollup/rollup-linux-loong64-gnu": "4.50.2",
- "@rollup/rollup-linux-ppc64-gnu": "4.50.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.50.2",
- "@rollup/rollup-linux-riscv64-musl": "4.50.2",
- "@rollup/rollup-linux-s390x-gnu": "4.50.2",
- "@rollup/rollup-linux-x64-gnu": "4.50.2",
- "@rollup/rollup-linux-x64-musl": "4.50.2",
- "@rollup/rollup-openharmony-arm64": "4.50.2",
- "@rollup/rollup-win32-arm64-msvc": "4.50.2",
- "@rollup/rollup-win32-ia32-msvc": "4.50.2",
- "@rollup/rollup-win32-x64-msvc": "4.50.2",
- "fsevents": "~2.3.2"
+ "node": ">=14.0.0"
}
},
- "node_modules/run-parallel": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
- "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "node_modules/tailwindcss-animate": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
+ "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
"license": "MIT",
- "dependencies": {
- "queue-microtask": "^1.2.2"
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders"
}
},
- "node_modules/rxjs": {
- "version": "7.5.7",
- "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz",
- "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==",
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
}
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/scheduler": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
- "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
- "license": "MIT",
+ "node_modules/terser": {
+ "version": "5.37.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
+ "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
+ "devOptional": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
- "loose-envify": "^1.1.0"
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.8.2",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
}
},
- "node_modules/schema-utils": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
- "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz",
+ "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^4.3.0",
+ "terser": "^5.31.1"
},
"engines": {
"node": ">= 10.13.0"
@@ -7464,1005 +10773,1216 @@
"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/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
+ "node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
},
- "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",
- "peer": true,
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "license": "MIT",
"dependencies": {
- "randombytes": "^2.1.0"
+ "any-promise": "^1.0.0"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
- "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
- "license": "MIT"
- },
- "node_modules/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==",
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"license": "MIT",
"dependencies": {
- "shebang-regex": "^3.0.0"
+ "thenify": ">= 3.1.0 < 4"
},
"engines": {
- "node": ">=8"
+ "node": ">=0.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==",
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
+ "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=12.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/SuperchupuDev"
}
},
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "devOptional": true,
"license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
},
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "devOptional": true,
+ "license": "MIT",
"engines": {
- "node": ">= 0.4"
+ "node": ">=12"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "engines": {
+ "node": ">=16"
}
},
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
+ "punycode": "^2.3.1"
},
"engines": {
- "node": ">= 0.4"
- },
+ "node": ">=18"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "dev": true,
+ "license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "license": "ISC",
+ "node_modules/ts-api-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
+ "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
+ "dev": true,
+ "license": "MIT",
"engines": {
- "node": ">=14"
+ "node": ">=18.12"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
}
},
- "node_modules/simple-swizzle": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/ts-loader": {
+ "version": "9.5.2",
+ "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz",
+ "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "is-arrayish": "^0.3.1"
+ "chalk": "^4.1.0",
+ "enhanced-resolve": "^5.0.0",
+ "micromatch": "^4.0.0",
+ "semver": "^7.3.4",
+ "source-map": "^0.7.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "typescript": "*",
+ "webpack": "^5.0.0"
}
},
- "node_modules/source-map": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
- "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
+ "node_modules/ts-loader/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"dev": true,
- "license": "BSD-3-Clause",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
"engines": {
- "node": ">= 8"
+ "node": ">=10"
}
},
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
+ "node_modules/tsconfck": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.4.tgz",
+ "integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==",
+ "license": "MIT",
+ "bin": {
+ "tsconfck": "bin/tsconfck.js"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": "^18 || >=20"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
- "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==",
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
}
},
- "node_modules/source-map-support/node_modules/source-map": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "peer": true,
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=14.17"
}
},
- "node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "node_modules/typescript-eslint": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.20.0.tgz",
+ "integrity": "sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
+ "@typescript-eslint/eslint-plugin": "8.20.0",
+ "@typescript-eslint/parser": "8.20.0",
+ "@typescript-eslint/utils": "8.20.0"
},
"engines": {
- "node": ">=12"
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
},
- "engines": {
- "node": ">=8"
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
}
},
- "node_modules/string-width-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "node_modules/ufo": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
+ "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici": {
+ "version": "6.24.1",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
+ "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
+ "dev": true,
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">=18.17"
}
},
- "node_modules/string-width-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true
},
- "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "node_modules/unist-util-is": {
"version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "@types/unist": "^3.0.0"
},
- "engines": {
- "node": ">=8"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
+ "@types/unist": "^3.0.0"
},
"funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "@types/unist": "^3.0.0"
},
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "node_modules/unist-util-visit": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8"
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/sucrase": {
- "version": "3.35.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
- "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "^10.3.10",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
},
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/sucrase/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
+ "engines": {
+ "node": ">= 10.0.0"
}
},
- "node_modules/sucrase/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "license": "ISC",
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
"dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
},
"bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sucrase/node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "update-browserslist-db": "cli.js"
},
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
}
},
- "node_modules/sucrase/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "license": "ISC"
+ "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/sucrase/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "license": "ISC",
+ "node_modules/url-template": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
+ "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
+ "license": "BSD"
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "tslib": "^2.0.0"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": ">=10"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/sucrase/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "license": "BlueOak-1.0.0",
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
"dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
},
"engines": {
- "node": ">=16 || 14 >=14.18"
+ "node": ">=10"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
}
},
- "node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "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==",
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">= 0.4"
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/tailwind-merge": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
- "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "dev": true,
"license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/dcastil"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/tailwind-variants": {
- "version": "0.1.20",
- "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.1.20.tgz",
- "integrity": "sha512-AMh7x313t/V+eTySKB0Dal08RHY7ggYK0MSn/ad8wKWOrDUIzyiWNayRUm2PIJ4VRkvRnfNuyRuKbLV3EN+ewQ==",
+ "node_modules/vite": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
+ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
- "tailwind-merge": "^1.14.0"
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
},
"engines": {
- "node": ">=16.x",
- "pnpm": ">=7.x"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
- "peerDependencies": {
- "tailwindcss": "*"
- }
- },
- "node_modules/tailwind-variants/node_modules/tailwind-merge": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
- "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==",
- "license": "MIT",
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/dcastil"
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
}
},
- "node_modules/tailwindcss": {
- "version": "3.4.17",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
- "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
+ "node_modules/vite-plugin-pages": {
+ "version": "0.32.4",
+ "resolved": "https://registry.npmjs.org/vite-plugin-pages/-/vite-plugin-pages-0.32.4.tgz",
+ "integrity": "sha512-OM8CNb8mAzyYR8ASRC0+2LXVB8ecR/5JHc5RpxbWtF+CmhjhmIELs0iV5y8qvU48soZbk+NsFOYlhoIcjw3+ew==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "arg": "^5.0.2",
- "chokidar": "^3.6.0",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
+ "@types/debug": "^4.1.12",
+ "debug": "^4.3.7",
+ "dequal": "^2.0.3",
+ "extract-comments": "^1.1.0",
"fast-glob": "^3.3.2",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "jiti": "^1.21.6",
- "lilconfig": "^3.1.3",
- "micromatch": "^4.0.8",
- "normalize-path": "^3.0.0",
- "object-hash": "^3.0.0",
+ "json5": "^2.2.3",
+ "local-pkg": "^0.5.1",
"picocolors": "^1.1.1",
- "postcss": "^8.4.47",
- "postcss-import": "^15.1.0",
- "postcss-js": "^4.0.1",
- "postcss-load-config": "^4.0.2",
- "postcss-nested": "^6.2.0",
- "postcss-selector-parser": "^6.1.2",
- "resolve": "^1.22.8",
- "sucrase": "^3.35.0"
+ "yaml": "^2.6.1"
},
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
+ "peerDependencies": {
+ "@vue/compiler-sfc": "^2.7.0 || ^3.0.0",
+ "vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
},
- "engines": {
- "node": ">=14.0.0"
+ "peerDependenciesMeta": {
+ "@solidjs/router": {
+ "optional": true
+ },
+ "@vue/compiler-sfc": {
+ "optional": true
+ },
+ "react-router": {
+ "optional": true
+ },
+ "vue-router": {
+ "optional": true
+ }
}
},
- "node_modules/tailwindcss-animate": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz",
- "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==",
+ "node_modules/vite-tsconfig-paths": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
+ "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
"license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "globrex": "^0.1.2",
+ "tsconfck": "^3.0.3"
+ },
"peerDependencies": {
- "tailwindcss": ">=3.0.0 || insiders"
+ "vite": "*"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
}
},
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
- "dev": true,
+ "node_modules/vite/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "devOptional": true,
"license": "MIT",
"engines": {
- "node": ">=6"
- }
- },
- "node_modules/terser": {
- "version": "5.37.0",
- "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
- "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "peer": true,
- "dependencies": {
- "@jridgewell/source-map": "^0.3.3",
- "acorn": "^8.8.2",
- "commander": "^2.20.0",
- "source-map-support": "~0.5.20"
+ "node": ">=12.0.0"
},
- "bin": {
- "terser": "bin/terser"
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
},
- "engines": {
- "node": ">=10"
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
}
},
- "node_modules/terser-webpack-plugin": {
- "version": "5.3.11",
- "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz",
- "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==",
- "dev": true,
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "devOptional": true,
"license": "MIT",
- "peer": true,
- "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"
+ "node": ">=12"
},
"funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitepress": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz",
+ "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@docsearch/css": "3.8.2",
+ "@docsearch/js": "3.8.2",
+ "@iconify-json/simple-icons": "^1.2.21",
+ "@shikijs/core": "^2.1.0",
+ "@shikijs/transformers": "^2.1.0",
+ "@shikijs/types": "^2.1.0",
+ "@types/markdown-it": "^14.1.2",
+ "@vitejs/plugin-vue": "^5.2.1",
+ "@vue/devtools-api": "^7.7.0",
+ "@vue/shared": "^3.5.13",
+ "@vueuse/core": "^12.4.0",
+ "@vueuse/integrations": "^12.4.0",
+ "focus-trap": "^7.6.4",
+ "mark.js": "8.11.1",
+ "minisearch": "^7.1.1",
+ "shiki": "^2.1.0",
+ "vite": "^5.4.14",
+ "vue": "^3.5.13"
+ },
+ "bin": {
+ "vitepress": "bin/vitepress.js"
},
"peerDependencies": {
- "webpack": "^5.1.0"
+ "markdown-it-mathjax3": "^4",
+ "postcss": "^8"
},
"peerDependenciesMeta": {
- "@swc/core": {
+ "markdown-it-mathjax3": {
"optional": true
},
- "esbuild": {
- "optional": true
- },
- "uglify-js": {
+ "postcss": {
"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==",
+ "node_modules/vitepress/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "peer": true,
- "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"
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "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==",
+ "node_modules/vitepress/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "peer": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "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",
- "peer": true
- },
- "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
- "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
+ "node_modules/vitepress/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "peer": true,
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
+ "node": ">=12"
}
},
- "node_modules/terser/node_modules/commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "node_modules/vitepress/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "peer": true
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0"
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "node_modules/vitepress/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=0.8"
+ "node": ">=12"
}
},
- "node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
- "devOptional": true,
+ "node_modules/vitepress/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
+ "node": ">=12"
}
},
- "node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "devOptional": true,
+ "node_modules/vitepress/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
+ "node": ">=12"
}
},
- "node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "devOptional": true,
+ "node_modules/vitepress/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
"node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8.0"
+ "node": ">=12"
}
},
- "node_modules/ts-api-utils": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
- "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
+ "node": ">=12"
}
},
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "license": "Apache-2.0"
- },
- "node_modules/ts-loader": {
- "version": "9.5.2",
- "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz",
- "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "chalk": "^4.1.0",
- "enhanced-resolve": "^5.0.0",
- "micromatch": "^4.0.0",
- "semver": "^7.3.4",
- "source-map": "^0.7.4"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "typescript": "*",
- "webpack": "^5.0.0"
+ "node": ">=12"
}
},
- "node_modules/ts-loader/node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=10"
+ "node": ">=12"
}
},
- "node_modules/tsconfck": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.4.tgz",
- "integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
"license": "MIT",
- "bin": {
- "tsconfck": "bin/tsconfck.js"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18 || >=20"
- },
- "peerDependencies": {
- "typescript": "^5.0.0"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
+ "node": ">=12"
}
},
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">= 0.8.0"
+ "node": ">=12"
}
},
- "node_modules/typescript": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
- "devOptional": true,
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
+ "node_modules/vitepress/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=14.17"
+ "node": ">=12"
}
},
- "node_modules/typescript-eslint": {
- "version": "8.20.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.20.0.tgz",
- "integrity": "sha512-Kxz2QRFsgbWj6Xcftlw3Dd154b3cEPFqQC+qMZrMypSijPd4UanKKvoKDrJ4o8AIfZFKAF+7sMaEIR8mTElozA==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.20.0",
- "@typescript-eslint/parser": "8.20.0",
- "@typescript-eslint/utils": "8.20.0"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <5.8.0"
+ "node": ">=12"
}
},
- "node_modules/ufo": {
- "version": "1.5.4",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
- "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
+ "node_modules/vitepress/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/undici": {
- "version": "6.21.3",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
- "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
+ "node_modules/vitepress/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=18.17"
+ "node": ">=12"
}
},
- "node_modules/undici-types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "node_modules/vitepress/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "peer": true
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
},
- "node_modules/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "node_modules/vitepress/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
"engines": {
- "node": ">= 10.0.0"
+ "node": ">=12"
}
},
- "node_modules/update-browserslist-db": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
- "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
- "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"
- }
+ "node_modules/vitepress/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "escalade": "^3.2.0",
- "picocolors": "^1.1.1"
- },
- "bin": {
- "update-browserslist-db": "cli.js"
- },
- "peerDependencies": {
- "browserslist": ">= 4.21.0"
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "node_modules/vitepress/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
}
},
- "node_modules/url-template": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
- "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
- "license": "BSD"
- },
- "node_modules/use-callback-ref": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
- "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "node_modules/vitepress/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
"license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
+ "node": ">=12"
}
},
- "node_modules/use-sidecar": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
- "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "node_modules/vitepress/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
"license": "MIT",
- "dependencies": {
- "detect-node-es": "^1.1.0",
- "tslib": "^2.0.0"
+ "bin": {
+ "esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ "node": ">=12"
},
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT"
- },
- "node_modules/vite": {
- "version": "6.3.6",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
- "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
- "devOptional": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.25.0",
- "fdir": "^6.4.4",
- "picomatch": "^4.0.2",
- "postcss": "^8.5.3",
- "rollup": "^4.34.9",
- "tinyglobby": "^0.2.13"
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vitepress/node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ "node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -8471,25 +11991,19 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "jiti": ">=1.21.0",
+ "@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
+ "terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
- "jiti": {
- "optional": true
- },
"less": {
"optional": true
},
@@ -8510,105 +12024,162 @@
},
"terser": {
"optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
}
}
},
- "node_modules/vite-plugin-pages": {
- "version": "0.32.4",
- "resolved": "https://registry.npmjs.org/vite-plugin-pages/-/vite-plugin-pages-0.32.4.tgz",
- "integrity": "sha512-OM8CNb8mAzyYR8ASRC0+2LXVB8ecR/5JHc5RpxbWtF+CmhjhmIELs0iV5y8qvU48soZbk+NsFOYlhoIcjw3+ew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/debug": "^4.1.12",
- "debug": "^4.3.7",
- "dequal": "^2.0.3",
- "extract-comments": "^1.1.0",
- "fast-glob": "^3.3.2",
- "json5": "^2.2.3",
- "local-pkg": "^0.5.1",
- "picocolors": "^1.1.1",
- "yaml": "^2.6.1"
+ "node_modules/vitest": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz",
+ "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.0",
+ "@vitest/mocker": "4.1.0",
+ "@vitest/pretty-format": "4.1.0",
+ "@vitest/runner": "4.1.0",
+ "@vitest/snapshot": "4.1.0",
+ "@vitest/spy": "4.1.0",
+ "@vitest/utils": "4.1.0",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vue/compiler-sfc": "^2.7.0 || ^3.0.0",
- "vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.0",
+ "@vitest/browser-preview": "4.1.0",
+ "@vitest/browser-webdriverio": "4.1.0",
+ "@vitest/ui": "4.1.0",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
},
"peerDependenciesMeta": {
- "@solidjs/router": {
+ "@edge-runtime/vm": {
"optional": true
},
- "@vue/compiler-sfc": {
+ "@opentelemetry/api": {
"optional": true
},
- "react-router": {
+ "@types/node": {
"optional": true
},
- "vue-router": {
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
"optional": true
+ },
+ "vite": {
+ "optional": false
}
}
},
- "node_modules/vite-tsconfig-paths": {
- "version": "5.1.4",
- "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
- "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
+ "node_modules/vitest/node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "debug": "^4.1.1",
- "globrex": "^0.1.2",
- "tsconfck": "^3.0.3"
- },
- "peerDependencies": {
- "vite": "*"
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
},
- "peerDependenciesMeta": {
- "vite": {
- "optional": true
- }
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/vite/node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "devOptional": true,
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"license": "MIT",
"engines": {
- "node": ">=12.0.0"
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
+ "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.30",
+ "@vue/compiler-sfc": "3.5.30",
+ "@vue/runtime-dom": "3.5.30",
+ "@vue/server-renderer": "3.5.30",
+ "@vue/shared": "3.5.30"
},
"peerDependencies": {
- "picomatch": "^3 || ^4"
+ "typescript": "*"
},
"peerDependenciesMeta": {
- "picomatch": {
+ "typescript": {
"optional": true
}
}
},
- "node_modules/vite/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
- "devOptional": true,
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
"license": "MIT",
- "engines": {
- "node": ">=12"
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/watchpack": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
- "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
+ "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -8629,37 +12200,49 @@
"node": ">= 8"
}
},
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/webpack": {
- "version": "5.97.1",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
- "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
+ "version": "5.105.4",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz",
+ "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
- "@types/estree": "^1.0.6",
+ "@types/estree": "^1.0.8",
+ "@types/json-schema": "^7.0.15",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
- "acorn": "^8.14.0",
- "browserslist": "^4.24.0",
+ "acorn": "^8.16.0",
+ "acorn-import-phases": "^1.0.3",
+ "browserslist": "^4.28.1",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.17.1",
- "es-module-lexer": "^1.2.1",
+ "enhanced-resolve": "^5.20.0",
+ "es-module-lexer": "^2.0.0",
"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",
+ "loader-runner": "^4.3.1",
"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"
+ "schema-utils": "^4.3.3",
+ "tapable": "^2.3.0",
+ "terser-webpack-plugin": "^5.3.17",
+ "watchpack": "^2.5.1",
+ "webpack-sources": "^3.3.4"
},
"bin": {
"webpack": "bin/webpack.js"
@@ -8678,9 +12261,9 @@
}
},
"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==",
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz",
+ "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -8737,6 +12320,20 @@
"node": ">=18"
}
},
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -8752,6 +12349,23 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -8850,6 +12464,45 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/ws": {
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
@@ -8881,6 +12534,17 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
}
}
}
diff --git a/package.json b/package.json
index ecc67e6..dcf48f4 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,13 @@
"dev": "vite",
"build": "tsc -b && vite build",
"build:mock": "tsc -b && vite build --mode mock",
+ "test": "vitest run",
+ "test:watch": "vitest",
"lint": "eslint .",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "docs:dev": "vitepress dev docs",
+ "docs:build": "vitepress build docs",
+ "docs:preview": "vitepress preview docs"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@@ -19,7 +24,9 @@
"@heroui/theme": "^2.4.6",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
@@ -30,20 +37,19 @@
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.7",
- "@types/gapi.client.calendar": "^3.0.12",
- "axios": "^1.12.0",
+ "@types/gapi.client.calendar-v3": "^0.0.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"framer-motion": "^12.0.6",
- "glob": "^11.0.1",
"googleapis": "^160.0.0",
+ "i18next": "^25.8.18",
"lucide-react": "^0.471.2",
"motion": "^12.23.24",
- "node-fetch": "^3.3.2",
"react": "^18.3.1",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
+ "react-i18next": "^16.5.8",
"react-router-dom": "^7.9.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
@@ -52,6 +58,7 @@
"devDependencies": {
"@crxjs/vite-plugin": "^2.0.0-beta.30",
"@eslint/js": "^9.17.0",
+ "@testing-library/jest-dom": "^6.9.1",
"@types/chrome": "^0.0.296",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
@@ -61,6 +68,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
+ "jsdom": "^25.0.1",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.17",
@@ -68,6 +76,13 @@
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.3.6",
- "vite-plugin-pages": "^0.32.4"
+ "vite-plugin-pages": "^0.32.4",
+ "vitepress": "^1.6.4",
+ "vitest": "^4.1.0"
+ },
+ "overrides": {
+ "@crxjs/vite-plugin": {
+ "rollup": "^2.80.0"
+ }
}
}
diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json
new file mode 100644
index 0000000..55600fd
--- /dev/null
+++ b/public/_locales/en/messages.json
@@ -0,0 +1,8 @@
+{
+ "extName": {
+ "message": "HSU Dotbugi 🔎"
+ },
+ "extDescription": {
+ "message": "View all Hansung University LMS lectures, assignments, and quizzes at a glance!"
+ }
+}
diff --git a/public/_locales/ja/messages.json b/public/_locales/ja/messages.json
new file mode 100644
index 0000000..3855e43
--- /dev/null
+++ b/public/_locales/ja/messages.json
@@ -0,0 +1,8 @@
+{
+ "extName": {
+ "message": "HSU Dotbugi 🔎"
+ },
+ "extDescription": {
+ "message": "漢城大学LMSの講義、課題、クイズを一目で!"
+ }
+}
diff --git a/public/_locales/ko/messages.json b/public/_locales/ko/messages.json
new file mode 100644
index 0000000..bf1c2c2
--- /dev/null
+++ b/public/_locales/ko/messages.json
@@ -0,0 +1,8 @@
+{
+ "extName": {
+ "message": "HSU 돋부기 🔎"
+ },
+ "extDescription": {
+ "message": "한성대학교 LMS 강의, 과제, 퀴즈를 한 눈에!"
+ }
+}
diff --git a/public/_locales/zh/messages.json b/public/_locales/zh/messages.json
new file mode 100644
index 0000000..e4673d1
--- /dev/null
+++ b/public/_locales/zh/messages.json
@@ -0,0 +1,8 @@
+{
+ "extName": {
+ "message": "HSU Dotbugi 🔎"
+ },
+ "extDescription": {
+ "message": "一目了然查看汉城大学LMS课程、作业和测验!"
+ }
+}
diff --git a/src/__tests__/attendance.test.ts b/src/__tests__/attendance.test.ts
new file mode 100644
index 0000000..dccadf3
--- /dev/null
+++ b/src/__tests__/attendance.test.ts
@@ -0,0 +1,84 @@
+import { describe, it, expect } from 'vitest';
+import { isAttended, isAbsent } from '@/lib/attendance';
+
+describe('isAttended', () => {
+ it('소문자 "o"는 출석', () => {
+ expect(isAttended('o')).toBe(true);
+ });
+
+ it('대문자 "O"는 출석', () => {
+ expect(isAttended('O')).toBe(true);
+ });
+
+ it('공백 포함 " o "는 출석', () => {
+ expect(isAttended(' o ')).toBe(true);
+ });
+
+ it('"x"는 미출석', () => {
+ expect(isAttended('x')).toBe(false);
+ });
+
+ it('"X"는 미출석', () => {
+ expect(isAttended('X')).toBe(false);
+ });
+
+ it('빈 문자열은 미출석', () => {
+ expect(isAttended('')).toBe(false);
+ });
+
+ it('"출석"같은 한글은 미출석으로 판단', () => {
+ expect(isAttended('출석')).toBe(false);
+ });
+
+ it('"O출석" 같은 혼합 문자열은 미출석', () => {
+ expect(isAttended('O출석')).toBe(false);
+ });
+
+ it('공백만 있는 문자열은 미출석 (trim 후 빈 문자열)', () => {
+ expect(isAttended(' ')).toBe(false);
+ });
+
+ it('탭 문자 포함된 "\\to\\t"는 미출석 (trim 후 "o"가 아님)', () => {
+ // '\to\t' → trim → 'o' 가 됨
+ expect(isAttended('\to\t')).toBe(true);
+ });
+
+ it('"0" (숫자 영)은 미출석 ("o"가 아님)', () => {
+ expect(isAttended('0')).toBe(false);
+ });
+});
+
+describe('isAbsent', () => {
+ it('"X"는 결석', () => {
+ expect(isAbsent('X')).toBe(true);
+ });
+
+ it('"x"는 결석', () => {
+ expect(isAbsent('x')).toBe(true);
+ });
+
+ it('"X (결석)"처럼 X로 시작하면 결석', () => {
+ expect(isAbsent('X (결석)')).toBe(true);
+ });
+
+ it('"o"는 결석 아님', () => {
+ expect(isAbsent('o')).toBe(false);
+ });
+
+ it('빈 문자열은 결석 아님', () => {
+ expect(isAbsent('')).toBe(false);
+ });
+
+ it('"결석"같은 한글은 결석 아님 (X로 시작하지 않으므로)', () => {
+ expect(isAbsent('결석')).toBe(false);
+ });
+
+ it('"xX" 소문자 x로 시작하면 결석', () => {
+ expect(isAbsent('xX')).toBe(true);
+ });
+
+ it('" X" 공백으로 시작하면 결석 아님 (toUpperCase 후 startsWith)', () => {
+ // ' X'.toUpperCase() = ' X', startsWith('X') = false
+ expect(isAbsent(' X')).toBe(false);
+ });
+});
diff --git a/src/__tests__/badge.test.ts b/src/__tests__/badge.test.ts
new file mode 100644
index 0000000..1f229cb
--- /dev/null
+++ b/src/__tests__/badge.test.ts
@@ -0,0 +1,65 @@
+import { describe, it, expect } from 'vitest';
+
+// badge.ts 내부 함수들을 테스트하기 위해 모듈을 동적으로 import
+// parseTimeToSeconds, normalizeUrl은 export되지 않으므로 같은 로직을 테스트
+
+describe('parseTimeToSeconds (logic)', () => {
+ function parseTimeToSeconds(time: string): number {
+ const parts = time.split(':').map(Number);
+ if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
+ if (parts.length === 2) return parts[0] * 60 + parts[1];
+ return 0;
+ }
+
+ it('HH:MM:SS 형식을 초로 변환', () => {
+ expect(parseTimeToSeconds('01:30:00')).toBe(5400);
+ expect(parseTimeToSeconds('00:05:30')).toBe(330);
+ expect(parseTimeToSeconds('02:00:00')).toBe(7200);
+ });
+
+ it('MM:SS 형식을 초로 변환', () => {
+ expect(parseTimeToSeconds('05:30')).toBe(330);
+ expect(parseTimeToSeconds('00:00')).toBe(0);
+ expect(parseTimeToSeconds('90:00')).toBe(5400);
+ });
+
+ it('잘못된 형식은 0 반환', () => {
+ expect(parseTimeToSeconds('')).toBe(0);
+ expect(parseTimeToSeconds('abc')).toBe(0);
+ expect(parseTimeToSeconds('30')).toBe(0);
+ });
+
+ it('경계값 처리', () => {
+ expect(parseTimeToSeconds('00:00:00')).toBe(0);
+ expect(parseTimeToSeconds('23:59:59')).toBe(86399);
+ expect(parseTimeToSeconds('00:00:01')).toBe(1);
+ });
+});
+
+describe('normalizeUrl (logic)', () => {
+ const BASE_LINK = 'https://learn.hansung.ac.kr';
+
+ function normalizeUrl(url: string): string {
+ if (url.startsWith('http')) return url;
+ return BASE_LINK + (url.startsWith('/') ? '' : '/') + url;
+ }
+
+ it('http로 시작하는 URL은 그대로 반환', () => {
+ expect(normalizeUrl('https://learn.hansung.ac.kr/mod/vod/view.php?id=1')).toBe(
+ 'https://learn.hansung.ac.kr/mod/vod/view.php?id=1',
+ );
+ expect(normalizeUrl('http://example.com')).toBe('http://example.com');
+ });
+
+ it('/로 시작하는 상대 경로에 BASE_LINK 추가', () => {
+ expect(normalizeUrl('/mod/vod/view.php?id=1')).toBe(
+ 'https://learn.hansung.ac.kr/mod/vod/view.php?id=1',
+ );
+ });
+
+ it('/가 없는 상대 경로에 BASE_LINK/ 추가', () => {
+ expect(normalizeUrl('mod/vod/view.php?id=1')).toBe(
+ 'https://learn.hansung.ac.kr/mod/vod/view.php?id=1',
+ );
+ });
+});
diff --git a/src/__tests__/cache.test.ts b/src/__tests__/cache.test.ts
new file mode 100644
index 0000000..235e06d
--- /dev/null
+++ b/src/__tests__/cache.test.ts
@@ -0,0 +1,56 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import {
+ getLastRequestTime,
+ setLastRequestTime,
+ clearLastRequestTime,
+ CACHE_TTL_MS,
+ CACHE_TTL_MINUTES,
+ REFRESH_INTERVAL_MS,
+} from '@/lib/cache';
+
+describe('cache', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ it('초기 상태에서 null 반환', () => {
+ expect(getLastRequestTime()).toBeNull();
+ });
+
+ it('setLastRequestTime 후 getLastRequestTime 반환', () => {
+ setLastRequestTime(1000);
+ expect(getLastRequestTime()).toBe(1000);
+ });
+
+ it('clearLastRequestTime 후 null 반환', () => {
+ setLastRequestTime(1000);
+ clearLastRequestTime();
+ expect(getLastRequestTime()).toBeNull();
+ });
+
+ it('큰 타임스탬프도 정확히 저장/반환', () => {
+ const ts = Date.now();
+ setLastRequestTime(ts);
+ expect(getLastRequestTime()).toBe(ts);
+ });
+
+ it('덮어쓰기가 동작', () => {
+ setLastRequestTime(100);
+ setLastRequestTime(200);
+ expect(getLastRequestTime()).toBe(200);
+ });
+});
+
+describe('cache constants', () => {
+ it('REFRESH_INTERVAL_MS = 60초', () => {
+ expect(REFRESH_INTERVAL_MS).toBe(60_000);
+ });
+
+ it('CACHE_TTL_MINUTES = 60분', () => {
+ expect(CACHE_TTL_MINUTES).toBe(60);
+ });
+
+ it('CACHE_TTL_MS = 60분 * 60초 * 1000', () => {
+ expect(CACHE_TTL_MS).toBe(CACHE_TTL_MINUTES * REFRESH_INTERVAL_MS);
+ });
+});
diff --git a/src/__tests__/calendarUtils.test.ts b/src/__tests__/calendarUtils.test.ts
new file mode 100644
index 0000000..43dcd7e
--- /dev/null
+++ b/src/__tests__/calendarUtils.test.ts
@@ -0,0 +1,93 @@
+import { describe, it, expect } from 'vitest';
+import { CalendarEvent } from '@/lib/transformCalendarEvents';
+
+type GoogleCalendarEvent = {
+ summary: string;
+ colorId?: string;
+ start: { dateTime: string; timeZone: string };
+ end: { dateTime: string; timeZone: string };
+};
+
+const EVENT_COLOR_MAP: Record = {
+ vod: '1',
+ assign: '9',
+ quiz: '6',
+};
+
+function convertCalendarEventsToGoogleEvents(events: CalendarEvent[]): GoogleCalendarEvent[] {
+ return events
+ .filter((event) => event.start !== null && event.end !== null)
+ .map((event) => ({
+ summary: `${event.title} - ${event.subject}`,
+ colorId: EVENT_COLOR_MAP[event.type],
+ start: {
+ dateTime: event.start!.toISOString(),
+ timeZone: 'Asia/Seoul',
+ },
+ end: {
+ dateTime: event.end!.toISOString(),
+ timeZone: 'Asia/Seoul',
+ },
+ }));
+}
+
+describe('convertCalendarEventsToGoogleEvents', () => {
+ it('CalendarEvent를 GoogleCalendarEvent로 변환', () => {
+ const events: CalendarEvent[] = [
+ {
+ id: 'assign-1',
+ title: '1주차 과제',
+ subject: '프로그래밍',
+ type: 'assign',
+ start: new Date('2026-03-18T00:00:00'),
+ end: new Date('2026-03-24T23:59:59'),
+ },
+ ];
+
+ const result = convertCalendarEventsToGoogleEvents(events);
+ expect(result).toHaveLength(1);
+ expect(result[0].summary).toBe('1주차 과제 - 프로그래밍');
+ expect(result[0].colorId).toBe('9');
+ expect(result[0].start.timeZone).toBe('Asia/Seoul');
+ expect(result[0].end.timeZone).toBe('Asia/Seoul');
+ });
+
+ it('type에 따른 colorId 매핑', () => {
+ const events: CalendarEvent[] = [
+ { id: '1', title: 'VOD', subject: '과목', type: 'vod', start: new Date(), end: new Date() },
+ { id: '2', title: '과제', subject: '과목', type: 'assign', start: new Date(), end: new Date() },
+ { id: '3', title: '퀴즈', subject: '과목', type: 'quiz', start: new Date(), end: new Date() },
+ ];
+
+ const result = convertCalendarEventsToGoogleEvents(events);
+ expect(result[0].colorId).toBe('1');
+ expect(result[1].colorId).toBe('9');
+ expect(result[2].colorId).toBe('6');
+ });
+
+ it('start 또는 end가 null인 이벤트 필터링', () => {
+ const events: CalendarEvent[] = [
+ { id: '1', title: 'A', subject: '과목', type: 'vod', start: null, end: new Date() },
+ { id: '2', title: 'B', subject: '과목', type: 'vod', start: new Date(), end: null },
+ { id: '3', title: 'C', subject: '과목', type: 'vod', start: null, end: null },
+ { id: '4', title: 'D', subject: '과목', type: 'vod', start: new Date(), end: new Date() },
+ ];
+
+ const result = convertCalendarEventsToGoogleEvents(events);
+ expect(result).toHaveLength(1);
+ expect(result[0].summary).toBe('D - 과목');
+ });
+
+ it('빈 배열 입력 시 빈 배열 반환', () => {
+ expect(convertCalendarEventsToGoogleEvents([])).toEqual([]);
+ });
+
+ it('summary 형식이 "title - subject"', () => {
+ const events: CalendarEvent[] = [
+ { id: 'q1', title: '중간고사', subject: '자료구조', type: 'quiz', start: new Date(), end: new Date() },
+ ];
+
+ const result = convertCalendarEventsToGoogleEvents(events);
+ expect(result[0].summary).toBe('중간고사 - 자료구조');
+ });
+});
diff --git a/src/__tests__/currentWeek.test.ts b/src/__tests__/currentWeek.test.ts
new file mode 100644
index 0000000..9223dda
--- /dev/null
+++ b/src/__tests__/currentWeek.test.ts
@@ -0,0 +1,156 @@
+import { describe, it, expect } from 'vitest';
+
+const MONTH_NAMES: Record = {
+ january: 1, february: 2, march: 3, april: 4, may: 5, june: 6,
+ july: 7, august: 8, september: 9, october: 10, november: 11, december: 12,
+};
+
+function parseLocalizedDate(str: string, year: number): Date | null {
+ let m: RegExpMatchArray | null;
+
+ m = str.match(/(\d{1,2})월\s*(\d{1,2})일/) ?? str.match(/(\d{1,2})月(\d{1,2})日/);
+ if (m) return new Date(year, parseInt(m[1]) - 1, parseInt(m[2]));
+
+ m = str.match(/^(\d{1,2})\/(\d{1,2})$/);
+ if (m) return new Date(year, parseInt(m[1]) - 1, parseInt(m[2]));
+
+ m = str.match(/(\d{1,2})\s+([A-Za-z]+)/);
+ if (m && MONTH_NAMES[m[2].toLowerCase()]) {
+ return new Date(year, MONTH_NAMES[m[2].toLowerCase()] - 1, parseInt(m[1]));
+ }
+
+ return null;
+}
+
+function parseSectionDateRange(text: string, year: number): { start: Date; end: Date } | null {
+ const match = text.match(/\[([^\]]+)\]/);
+ if (!match) return null;
+
+ const [startStr, endStr] = match[1].split(/\s*-\s*/);
+ if (!startStr || !endStr) return null;
+
+ const start = parseLocalizedDate(startStr.trim(), year);
+ const end = parseLocalizedDate(endStr.trim(), year);
+ if (!start || !end) return null;
+
+ end.setHours(23, 59, 59);
+ return { start, end };
+}
+
+describe('parseLocalizedDate', () => {
+ const year = 2026;
+
+ describe('한국어 (월일)', () => {
+ it('3월18일', () => {
+ const result = parseLocalizedDate('3월18일', year);
+ expect(result).toEqual(new Date(2026, 2, 18));
+ });
+
+ it('12월 1일 (공백 포함)', () => {
+ const result = parseLocalizedDate('12월 1일', year);
+ expect(result).toEqual(new Date(2026, 11, 1));
+ });
+ });
+
+ describe('중국어 (月日)', () => {
+ it('03月04日', () => {
+ const result = parseLocalizedDate('03月04日', year);
+ expect(result).toEqual(new Date(2026, 2, 4));
+ });
+
+ it('12月31日', () => {
+ const result = parseLocalizedDate('12月31日', year);
+ expect(result).toEqual(new Date(2026, 11, 31));
+ });
+ });
+
+ describe('일본어 (MM/DD)', () => {
+ it('03/04', () => {
+ const result = parseLocalizedDate('03/04', year);
+ expect(result).toEqual(new Date(2026, 2, 4));
+ });
+
+ it('1/1', () => {
+ const result = parseLocalizedDate('1/1', year);
+ expect(result).toEqual(new Date(2026, 0, 1));
+ });
+ });
+
+ describe('영어 (DD Month)', () => {
+ it('04 March', () => {
+ const result = parseLocalizedDate('04 March', year);
+ expect(result).toEqual(new Date(2026, 2, 4));
+ });
+
+ it('25 December', () => {
+ const result = parseLocalizedDate('25 December', year);
+ expect(result).toEqual(new Date(2026, 11, 25));
+ });
+
+ it('대소문자 무시', () => {
+ const result = parseLocalizedDate('1 january', year);
+ expect(result).toEqual(new Date(2026, 0, 1));
+ });
+ });
+
+ describe('잘못된 형식', () => {
+ it('빈 문자열', () => {
+ expect(parseLocalizedDate('', year)).toBeNull();
+ });
+
+ it('인식 불가 형식', () => {
+ expect(parseLocalizedDate('2026-03-18', year)).toBeNull();
+ });
+
+ it('잘못된 영어 월 이름', () => {
+ expect(parseLocalizedDate('04 Smarch', year)).toBeNull();
+ });
+ });
+});
+
+describe('parseSectionDateRange', () => {
+ const year = 2026;
+
+ it('한국어 범위 파싱', () => {
+ const result = parseSectionDateRange('1주차 [3월18일 - 3월24일]', year);
+ expect(result).not.toBeNull();
+ expect(result!.start).toEqual(new Date(2026, 2, 18));
+ expect(result!.end.getMonth()).toBe(2);
+ expect(result!.end.getDate()).toBe(24);
+ expect(result!.end.getHours()).toBe(23);
+ });
+
+ it('영어 범위 파싱', () => {
+ const result = parseSectionDateRange('Week 1 [18 March - 24 March]', year);
+ expect(result).not.toBeNull();
+ expect(result!.start).toEqual(new Date(2026, 2, 18));
+ expect(result!.end.getDate()).toBe(24);
+ });
+
+ it('일본어 범위 파싱', () => {
+ const result = parseSectionDateRange('第1週 [03/18 - 03/24]', year);
+ expect(result).not.toBeNull();
+ expect(result!.start).toEqual(new Date(2026, 2, 18));
+ });
+
+ it('중국어 범위 파싱', () => {
+ const result = parseSectionDateRange('第1周 [03月18日 - 03月24日]', year);
+ expect(result).not.toBeNull();
+ expect(result!.start).toEqual(new Date(2026, 2, 18));
+ });
+
+ it('대괄호 없으면 null', () => {
+ expect(parseSectionDateRange('1주차 3월18일', year)).toBeNull();
+ });
+
+ it('하이픈 없으면 null', () => {
+ expect(parseSectionDateRange('[3월18일]', year)).toBeNull();
+ });
+
+ it('end 날짜가 23:59:59로 설정됨', () => {
+ const result = parseSectionDateRange('[3월18일 - 3월24일]', year);
+ expect(result!.end.getHours()).toBe(23);
+ expect(result!.end.getMinutes()).toBe(59);
+ expect(result!.end.getSeconds()).toBe(59);
+ });
+});
diff --git a/src/__tests__/dateUtils.test.ts b/src/__tests__/dateUtils.test.ts
new file mode 100644
index 0000000..9b729c0
--- /dev/null
+++ b/src/__tests__/dateUtils.test.ts
@@ -0,0 +1,342 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import {
+ parseDate,
+ isCurrentDateInRange,
+ isCurrentDateByDate,
+ isWithinSevenDays,
+ extractEndDate,
+ calculateDueDate,
+ calculateRemainingTime,
+ timeAgo,
+} from '@/lib/dateUtils';
+
+describe('parseDate', () => {
+ it('하이픈 구분 날짜를 파싱', () => {
+ const date = parseDate('2024-03-15 09:00');
+ expect(date).toBeInstanceOf(Date);
+ expect(date.getFullYear()).toBe(2024);
+ expect(date.getMonth()).toBe(2); // 0-indexed
+ expect(date.getDate()).toBe(15);
+ });
+
+ it('하이픈을 슬래시로 변환 (Safari 호환)', () => {
+ const date = parseDate('2024-01-01');
+ expect(date.getFullYear()).toBe(2024);
+ });
+
+ it('시간 포함 문자열도 파싱', () => {
+ const date = parseDate('2024-12-31 23:59');
+ expect(date.getMonth()).toBe(11);
+ expect(date.getDate()).toBe(31);
+ });
+
+ it('하이픈이 없는 문자열은 그대로 Date 생성자로 전달', () => {
+ const date = parseDate('2024/03/15');
+ expect(date.getFullYear()).toBe(2024);
+ });
+
+ it('초까지 포함된 날짜 문자열', () => {
+ const date = parseDate('2024-06-15 09:30:45');
+ expect(date.getHours()).toBe(9);
+ expect(date.getMinutes()).toBe(30);
+ });
+});
+
+describe('isCurrentDateInRange', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2024-06-15 12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('현재 날짜가 범위 내이면 true', () => {
+ expect(isCurrentDateInRange('2024-06-01 00:00 ~ 2024-06-30 23:59')).toBe(true);
+ });
+
+ it('현재 날짜가 범위 전이면 false', () => {
+ expect(isCurrentDateInRange('2024-07-01 00:00 ~ 2024-07-30 23:59')).toBe(false);
+ });
+
+ it('현재 날짜가 범위 후이면 false', () => {
+ expect(isCurrentDateInRange('2024-01-01 00:00 ~ 2024-01-31 23:59')).toBe(false);
+ });
+
+ it('null이면 false', () => {
+ expect(isCurrentDateInRange(null)).toBe(false);
+ });
+
+ it('빈 문자열이면 false', () => {
+ expect(isCurrentDateInRange('')).toBe(false);
+ });
+
+ it('" ~ "가 없으면 false', () => {
+ expect(isCurrentDateInRange('2024-06-01')).toBe(false);
+ });
+
+ it('" ~ " 뒤가 비어있으면 false', () => {
+ expect(isCurrentDateInRange('2024-06-01 ~ ')).toBe(false);
+ });
+
+ it('시작일과 같은 날이면 true', () => {
+ expect(isCurrentDateInRange('2024-06-15 00:00 ~ 2024-06-30 23:59')).toBe(true);
+ });
+
+ it('종료일과 같은 시간이면 true (<=)', () => {
+ vi.setSystemTime(new Date('2024-06-30 23:59:00'));
+ expect(isCurrentDateInRange('2024-06-01 00:00 ~ 2024-06-30 23:59')).toBe(true);
+ });
+
+ it('"~"만 있고 공백이 없어도 분리됨 (includes " ~ " 실패)', () => {
+ expect(isCurrentDateInRange('2024-06-01~2024-06-30')).toBe(false);
+ });
+
+ it('시작과 종료가 동일한 시간이면 그 정확한 시점에 true', () => {
+ vi.setSystemTime(new Date('2024-06-15 10:00:00'));
+ expect(isCurrentDateInRange('2024-06-15 10:00 ~ 2024-06-15 10:00')).toBe(true);
+ });
+});
+
+describe('isCurrentDateByDate', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2024-06-15 12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('미래 날짜이면 true', () => {
+ expect(isCurrentDateByDate('2024-12-31')).toBe(true);
+ });
+
+ it('과거 날짜이면 false', () => {
+ expect(isCurrentDateByDate('2024-01-01')).toBe(false);
+ });
+
+ it('null이면 false', () => {
+ expect(isCurrentDateByDate(null)).toBe(false);
+ });
+
+ it('빈 문자열이면 false', () => {
+ expect(isCurrentDateByDate('')).toBe(false);
+ });
+
+ it('길이 1 이하 문자열이면 false', () => {
+ expect(isCurrentDateByDate('X')).toBe(false);
+ });
+});
+
+describe('isWithinSevenDays', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2024-06-15 12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('3일 후는 7일 이내', () => {
+ expect(isWithinSevenDays('2024-06-18 12:00:00')).toBe(true);
+ });
+
+ it('정확히 7일 후는 7일 이내', () => {
+ expect(isWithinSevenDays('2024-06-22 12:00:00')).toBe(true);
+ });
+
+ it('8일 후는 7일 초과', () => {
+ expect(isWithinSevenDays('2024-06-23 12:00:01')).toBe(false);
+ });
+
+ it('과거 날짜는 false', () => {
+ expect(isWithinSevenDays('2024-06-10 12:00:00')).toBe(false);
+ });
+
+ it('오늘은 true (diff = 0)', () => {
+ expect(isWithinSevenDays('2024-06-15 12:00:00')).toBe(true);
+ });
+
+ it('7일 + 1초 후는 false', () => {
+ // 정확히 7일 = 604800000ms, 1초 추가
+ const target = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000 + 1000);
+ expect(isWithinSevenDays(target.toISOString())).toBe(false);
+ });
+
+ it('1초 후는 true', () => {
+ const target = new Date(Date.now() + 1000);
+ expect(isWithinSevenDays(target.toISOString())).toBe(true);
+ });
+});
+
+describe('extractEndDate', () => {
+ it('" ~ "로 분리된 범위에서 끝 날짜 추출', () => {
+ expect(extractEndDate('2024-01-01 ~ 2024-06-30')).toBe('2024-06-30');
+ });
+
+ it('null이면 null', () => {
+ expect(extractEndDate(null)).toBeNull();
+ });
+
+ it('" ~ "가 없으면 undefined → null', () => {
+ expect(extractEndDate('2024-06-30')).toBeNull();
+ });
+
+ it('빈 문자열이면 null (split 결과 [1]이 undefined)', () => {
+ expect(extractEndDate('')).toBeNull();
+ });
+});
+
+describe('calculateDueDate', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2024-06-15 12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('null이면 noInfo 상태', () => {
+ const result = calculateDueDate(null);
+ expect(result.status).toBe('noInfo');
+ });
+
+ it('유효하지 않은 날짜면 invalid 상태', () => {
+ const result = calculateDueDate('invalid-date');
+ expect(result.status).toBe('invalid');
+ });
+
+ it('이미 지난 날짜면 expired 상태', () => {
+ const result = calculateDueDate('2024-06-14 12:00:00');
+ expect(result.status).toBe('expired');
+ });
+
+ it('1일 이상 남으면 daysLeft 상태', () => {
+ const result = calculateDueDate('2024-06-20 12:00:00');
+ expect(result.status).toBe('daysLeft');
+ });
+
+ it('1일 미만(시간 단위) 남으면 urgent 상태', () => {
+ const result = calculateDueDate('2024-06-15 18:00:00');
+ expect(result.status).toBe('urgent');
+ });
+
+ it('몇 분만 남으면 urgent + 분 단위 메시지', () => {
+ const result = calculateDueDate('2024-06-15 12:30:00');
+ expect(result.status).toBe('urgent');
+ expect(result.message).toContain('date.minutesLater');
+ });
+
+ it('정확히 현재 시간이면 expired', () => {
+ const result = calculateDueDate('2024-06-15 12:00:00');
+ expect(result.status).toBe('expired');
+ });
+
+ it('정확히 24시간 후면 daysLeft (days=1)', () => {
+ const result = calculateDueDate('2024-06-16 12:00:00');
+ expect(result.status).toBe('daysLeft');
+ expect(result.message).toContain('days:1');
+ });
+
+ it('23시간 59분 후면 urgent (hours 단위)', () => {
+ const result = calculateDueDate('2024-06-16 11:59:00');
+ expect(result.status).toBe('urgent');
+ expect(result.message).toContain('date.hoursLater');
+ });
+
+ it('1초 후면 urgent + 0분 → hours가 0이므로 minutesLater', () => {
+ const result = calculateDueDate('2024-06-15 12:00:01');
+ expect(result.status).toBe('urgent');
+ expect(result.message).toContain('date.minutesLater');
+ });
+
+ it('borderColor가 상태에 따라 다름', () => {
+ expect(calculateDueDate(null).borderColor).toBe('border-amber-500');
+ expect(calculateDueDate('2024-06-14').borderColor).toBe('border-red-950');
+ expect(calculateDueDate('2024-06-20').borderColor).toBe('border-amber-500');
+ expect(calculateDueDate('2024-06-15 18:00:00').borderColor).toBe('border-red-700');
+ });
+});
+
+describe('calculateRemainingTime', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2024-06-15 12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('null이면 noInfo', () => {
+ expect(calculateRemainingTime(null)).toContain('date.noInfo');
+ });
+
+ it('과거 시간이면 expired', () => {
+ expect(calculateRemainingTime('2024-01-01 00:00:00')).toContain('date.expired');
+ });
+
+ it('1일 이상 남으면 days/hours/minutes 포함', () => {
+ const result = calculateRemainingTime('2024-06-20 15:30:00');
+ expect(result).toContain('date.remaining');
+ });
+
+ it('1일 미만이면 hours/minutes만', () => {
+ const result = calculateRemainingTime('2024-06-15 18:30:00');
+ expect(result).toContain('date.remainingHoursMinutes');
+ });
+
+ it('1시간 미만이면 minutes만', () => {
+ const result = calculateRemainingTime('2024-06-15 12:45:00');
+ expect(result).toContain('date.remainingMinutes');
+ });
+});
+
+describe('timeAgo', () => {
+ beforeEach(() => {
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date('2024-06-15 12:00:00'));
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('방금 전 (0분 차이)', () => {
+ expect(timeAgo(Date.now())).toContain('date.justNow');
+ });
+
+ it('분 단위', () => {
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000;
+ expect(timeAgo(fiveMinAgo)).toContain('date.minutesAgo');
+ });
+
+ it('시간 단위', () => {
+ const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000;
+ expect(timeAgo(twoHoursAgo)).toContain('date.hoursAgo');
+ });
+
+ it('일 단위', () => {
+ const threeDaysAgo = Date.now() - 3 * 24 * 60 * 60 * 1000;
+ expect(timeAgo(threeDaysAgo)).toContain('date.daysAgo');
+ });
+
+ it('59초 전은 justNow (분 단위 미만)', () => {
+ const fiftyNineSecAgo = Date.now() - 59 * 1000;
+ expect(timeAgo(fiftyNineSecAgo)).toContain('date.justNow');
+ });
+
+ it('정확히 60초 전은 minutesAgo', () => {
+ const oneMinAgo = Date.now() - 60 * 1000;
+ expect(timeAgo(oneMinAgo)).toContain('date.minutesAgo');
+ });
+
+ it('정확히 1시간 전은 hoursAgo', () => {
+ const oneHourAgo = Date.now() - 60 * 60 * 1000;
+ expect(timeAgo(oneHourAgo)).toContain('date.hoursAgo');
+ });
+});
diff --git a/src/__tests__/deduplicateInto.test.ts b/src/__tests__/deduplicateInto.test.ts
new file mode 100644
index 0000000..6a20a71
--- /dev/null
+++ b/src/__tests__/deduplicateInto.test.ts
@@ -0,0 +1,78 @@
+import { describe, it, expect } from 'vitest';
+import { deduplicateInto } from '@/lib/deduplicateInto';
+
+describe('deduplicateInto', () => {
+ it('빈 source는 target에 영향 없음', () => {
+ const target = [{ id: '1' }];
+ const seen = new Set();
+ deduplicateInto(target, [], seen, (item) => item.id);
+ expect(target).toHaveLength(1);
+ });
+
+ it('중복 없는 source는 전부 추가', () => {
+ const target: { id: string }[] = [];
+ const seen = new Set();
+ deduplicateInto(target, [{ id: '1' }, { id: '2' }], seen, (item) => item.id);
+ expect(target).toHaveLength(2);
+ });
+
+ it('source 내부 중복은 첫 번째만 추가', () => {
+ const target: { id: string }[] = [];
+ const seen = new Set();
+ deduplicateInto(target, [{ id: '1' }, { id: '1' }], seen, (item) => item.id);
+ expect(target).toHaveLength(1);
+ });
+
+ it('target에 이미 있는 키는 추가하지 않음 (seen으로 판단)', () => {
+ const target = [{ id: '1' }];
+ const seen = new Set(['1']);
+ deduplicateInto(target, [{ id: '1' }, { id: '2' }], seen, (item) => item.id);
+ expect(target).toHaveLength(2);
+ expect(target[1].id).toBe('2');
+ });
+
+ it('seen Set이 호출 후 업데이트됨', () => {
+ const target: { id: string }[] = [];
+ const seen = new Set();
+ deduplicateInto(target, [{ id: 'a' }, { id: 'b' }], seen, (item) => item.id);
+ expect(seen.has('a')).toBe(true);
+ expect(seen.has('b')).toBe(true);
+ });
+
+ it('여러 번 호출 시 seen이 누적되어 중복 방지', () => {
+ const target: { id: string }[] = [];
+ const seen = new Set();
+ deduplicateInto(target, [{ id: '1' }], seen, (item) => item.id);
+ deduplicateInto(target, [{ id: '1' }, { id: '2' }], seen, (item) => item.id);
+ expect(target).toHaveLength(2);
+ expect(target.map((t) => t.id)).toEqual(['1', '2']);
+ });
+
+ it('커스텀 getKey 함수 사용', () => {
+ type Item = { courseId: string; week: number };
+ const target: Item[] = [];
+ const seen = new Set();
+ const items: Item[] = [
+ { courseId: 'C1', week: 1 },
+ { courseId: 'C1', week: 1 }, // 중복
+ { courseId: 'C1', week: 2 },
+ ];
+ deduplicateInto(target, items, seen, (item) => `${item.courseId}-${item.week}`);
+ expect(target).toHaveLength(2);
+ });
+
+ it('빈 키 문자열도 정상 동작', () => {
+ const target: { id: string }[] = [];
+ const seen = new Set();
+ deduplicateInto(target, [{ id: '' }, { id: '' }], seen, (item) => item.id);
+ expect(target).toHaveLength(1);
+ });
+
+ it('target과 source가 같은 배열 참조여도 동작 (무한루프 아님 - for..of snapshot)', () => {
+ const arr = [{ id: '1' }];
+ const seen = new Set();
+ // source의 for..of는 호출 시점의 길이 기준이므로 안전
+ deduplicateInto(arr, [{ id: '2' }], seen, (item) => item.id);
+ expect(arr).toHaveLength(2);
+ });
+});
diff --git a/src/__tests__/fetchHtml.test.ts b/src/__tests__/fetchHtml.test.ts
new file mode 100644
index 0000000..a2c71a5
--- /dev/null
+++ b/src/__tests__/fetchHtml.test.ts
@@ -0,0 +1,75 @@
+import { describe, it, expect } from 'vitest';
+import { getText, getHref } from '@/lib/fetchHtml';
+
+function el(html: string): Element {
+ const div = document.createElement('div');
+ div.innerHTML = html;
+ return div;
+}
+
+describe('getText', () => {
+ it('셀렉터로 텍스트 추출', () => {
+ const parent = el('Hello ');
+ expect(getText(parent, '.name')).toBe('Hello');
+ });
+
+ it('텍스트 앞뒤 공백 trim', () => {
+ const parent = el(' Hello ');
+ expect(getText(parent, '.name')).toBe('Hello');
+ });
+
+ it('셀렉터가 매칭 안 되면 null', () => {
+ const parent = el('Hello ');
+ expect(getText(parent, '.missing')).toBeNull();
+ });
+
+ it('빈 텍스트 요소는 falsy이므로 null', () => {
+ const parent = el(' ');
+ expect(getText(parent, '.name')).toBeNull();
+ });
+
+ it('공백만 있는 텍스트는 trim 후 빈 문자열 → null', () => {
+ const parent = el(' ');
+ expect(getText(parent, '.name')).toBeNull();
+ });
+
+ it('중첩 요소의 텍스트도 포함', () => {
+ const parent = el('Bold text
');
+ expect(getText(parent, '.wrap')).toBe('Bold text');
+ });
+
+ it('여러 매칭 시 첫 번째 요소만 반환', () => {
+ const parent = el('First Second ');
+ expect(getText(parent, '.item')).toBe('First');
+ });
+});
+
+describe('getHref', () => {
+ it('a 태그의 href 추출', () => {
+ const parent = el('Link ');
+ const result = getHref(parent, '.link');
+ expect(result).toContain('example.com');
+ });
+
+ it('셀렉터가 매칭 안 되면 null', () => {
+ const parent = el('No link ');
+ expect(getHref(parent, 'a')).toBeNull();
+ });
+
+ it('href가 없는 a 태그는 빈 문자열 → falsy → null', () => {
+ const parent = el('No href ');
+ expect(getHref(parent, '.link')).toBeNull();
+ });
+
+ it('a가 아닌 요소에 href가 없으면 null', () => {
+ const parent = el('Not a link
');
+ expect(getHref(parent, '.link')).toBeNull();
+ });
+
+ it('상대 경로도 추출 (jsdom이 절대 경로로 변환할 수 있음)', () => {
+ const parent = el('Link ');
+ const result = getHref(parent, '.link');
+ expect(result).toBeTruthy();
+ expect(result).toContain('/path/to/page');
+ });
+});
diff --git a/src/__tests__/filterData.test.ts b/src/__tests__/filterData.test.ts
new file mode 100644
index 0000000..d108c6e
--- /dev/null
+++ b/src/__tests__/filterData.test.ts
@@ -0,0 +1,376 @@
+import { describe, it, expect } from 'vitest';
+import { filterVods, filterAssigns, filterQuizzes } from '@/lib/filterData';
+import { Vod, Assign, Quiz, Filters } from '@/types';
+
+const makeVod = (overrides: Partial = {}): Vod => ({
+ courseId: 'C1',
+ courseTitle: '프로그래밍',
+ prof: '김교수',
+ week: 1,
+ subject: '1주차',
+ title: '강의1',
+ url: '/v1',
+ range: '2024-01-01 ~ 2024-01-07',
+ length: '30:00',
+ isAttendance: 'O',
+ weeklyAttendance: 'O',
+ ...overrides,
+});
+
+const makeAssign = (overrides: Partial = {}): Assign => ({
+ courseId: 'C1',
+ courseTitle: '프로그래밍',
+ prof: '김교수',
+ subject: '1주차',
+ title: '과제1',
+ url: '/a1',
+ isSubmit: false,
+ dueDate: '2024-06-30',
+ ...overrides,
+});
+
+const makeQuiz = (overrides: Partial = {}): Quiz => ({
+ courseId: 'C1',
+ courseTitle: '프로그래밍',
+ prof: '김교수',
+ subject: '1주차',
+ title: '퀴즈1',
+ url: '/q1',
+ isSubmit: false,
+ dueDate: '2024-06-30',
+ ...overrides,
+});
+
+const emptyFilters: Filters = { courseTitles: [] };
+
+describe('filterVods', () => {
+ it('필터 없이 전체 반환', () => {
+ const vods = [makeVod(), makeVod({ title: '강의2' })];
+ const result = filterVods(vods, emptyFilters, '', 'range');
+ expect(result).toHaveLength(2);
+ });
+
+ it('과목 필터로 특정 과목만 반환', () => {
+ const vods = [
+ makeVod({ courseTitle: '수학' }),
+ makeVod({ courseTitle: '영어' }),
+ ];
+ const filters: Filters = { courseTitles: ['수학'] };
+ const result = filterVods(vods, filters, '', 'range');
+ expect(result).toHaveLength(1);
+ expect(result[0].courseTitle).toBe('수학');
+ });
+
+ it('검색어로 제목 필터링', () => {
+ const vods = [
+ makeVod({ title: '파이썬 기초' }),
+ makeVod({ title: '자바 고급' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '파이썬', 'range');
+ expect(result).toHaveLength(1);
+ });
+
+ it('검색어로 교수명 필터링', () => {
+ const vods = [
+ makeVod({ prof: '김교수' }),
+ makeVod({ prof: '이교수' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '이교수', 'range');
+ expect(result).toHaveLength(1);
+ });
+
+ it('검색어 대소문자 무시', () => {
+ const vods = [makeVod({ title: 'Python Basics' })];
+ const result = filterVods(vods, emptyFilters, 'python', 'range');
+ expect(result).toHaveLength(1);
+ });
+
+ it('출석 상태 필터 - 출석만', () => {
+ const vods = [
+ makeVod({ isAttendance: 'O' }),
+ makeVod({ isAttendance: 'X', title: '강의2' }),
+ ];
+ // i18n mock: attendance.attended
+ const filters: Filters = { courseTitles: [], attendanceStatuses: ['attendance.attended'] };
+ const result = filterVods(vods, filters, '', 'range');
+ expect(result).toHaveLength(1);
+ expect(result[0].isAttendance).toBe('O');
+ });
+
+ it('정렬: 미출석이 먼저 (isAttendance 기준)', () => {
+ const vods = [
+ makeVod({ isAttendance: 'O', title: '출석' }),
+ makeVod({ isAttendance: 'X', title: '미출석' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '', 'range');
+ // isAttended('O') = true → 출석이 앞으로? 아니, 코드에서 attendanceA ? -1 : 1
+ // 실제로는 출석이 먼저 정렬됨 (출석 완료한 것을 상단)
+ // 아... 코드를 다시 보면: attendanceA ? -1 : 1 → 출석이면 -1 → 출석이 먼저
+ expect(result[0].isAttendance).toBe('O');
+ });
+
+ it('정렬: title 기준', () => {
+ const vods = [
+ makeVod({ isAttendance: 'X', title: 'B강의' }),
+ makeVod({ isAttendance: 'X', title: 'A강의' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '', 'title');
+ expect(result[0].title).toBe('A강의');
+ expect(result[1].title).toBe('B강의');
+ });
+
+ it('정렬: range 기준, null은 뒤로', () => {
+ const vods = [
+ makeVod({ isAttendance: 'X', range: null, title: 'A' }),
+ makeVod({ isAttendance: 'X', range: '2024-01-01 ~ 2024-01-07', title: 'B' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '', 'range');
+ expect(result[0].range).not.toBeNull();
+ expect(result[1].range).toBeNull();
+ });
+
+ it('빈 배열이면 빈 배열 반환', () => {
+ expect(filterVods([], emptyFilters, '', 'range')).toEqual([]);
+ });
+
+ it('출석 상태 필터가 빈 배열이면 전체 반환', () => {
+ const vods = [makeVod({ isAttendance: 'O' }), makeVod({ isAttendance: 'X', title: '강의2' })];
+ const filters: Filters = { courseTitles: [], attendanceStatuses: [] };
+ const result = filterVods(vods, filters, '', 'range');
+ expect(result).toHaveLength(2);
+ });
+
+ it('정렬: 양쪽 모두 range null이면 순서 유지 (0 반환)', () => {
+ const vods = [
+ makeVod({ isAttendance: 'X', range: null, title: 'B' }),
+ makeVod({ isAttendance: 'X', range: null, title: 'A' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '', 'range');
+ // 둘 다 null이면 0 반환 → 원래 순서 유지
+ expect(result[0].title).toBe('B');
+ });
+
+ it('정렬: 출석 상태가 같으면 range로 정렬', () => {
+ const vods = [
+ makeVod({ isAttendance: 'O', range: '2024-02-01 ~ 2024-02-07', title: 'B' }),
+ makeVod({ isAttendance: 'O', range: '2024-01-01 ~ 2024-01-07', title: 'A' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '', 'range');
+ expect(result[0].title).toBe('A');
+ });
+
+ it('검색어로 과목명 필터링', () => {
+ const vods = [
+ makeVod({ courseTitle: '컴퓨터구조' }),
+ makeVod({ courseTitle: '운영체제', title: '강의2' }),
+ ];
+ const result = filterVods(vods, emptyFilters, '컴퓨터', 'range');
+ expect(result).toHaveLength(1);
+ expect(result[0].courseTitle).toBe('컴퓨터구조');
+ });
+
+ it('과목 필터 + 검색어 동시 적용', () => {
+ const vods = [
+ makeVod({ courseTitle: '수학', title: '미분' }),
+ makeVod({ courseTitle: '수학', title: '적분' }),
+ makeVod({ courseTitle: '영어', title: '미분' }),
+ ];
+ const filters: Filters = { courseTitles: ['수학'] };
+ const result = filterVods(vods, filters, '적분', 'range');
+ expect(result).toHaveLength(1);
+ expect(result[0].title).toBe('적분');
+ });
+});
+
+describe('filterAssigns', () => {
+ it('필터 없이 전체 반환', () => {
+ const assigns = [makeAssign(), makeAssign({ title: '과제2' })];
+ const result = filterAssigns(assigns, emptyFilters, '', 'dueDate');
+ expect(result).toHaveLength(2);
+ });
+
+ it('과목 필터', () => {
+ const assigns = [
+ makeAssign({ courseTitle: '수학' }),
+ makeAssign({ courseTitle: '영어' }),
+ ];
+ const filters: Filters = { courseTitles: ['수학'] };
+ const result = filterAssigns(assigns, filters, '', 'dueDate');
+ expect(result).toHaveLength(1);
+ });
+
+ it('제출 상태 필터 - 미제출만', () => {
+ const assigns = [
+ makeAssign({ isSubmit: false }),
+ makeAssign({ isSubmit: true, title: '과제2' }),
+ ];
+ const filters: Filters = { courseTitles: [], submitStatuses: [false] };
+ const result = filterAssigns(assigns, filters, '', 'dueDate');
+ expect(result).toHaveLength(1);
+ expect(result[0].isSubmit).toBe(false);
+ });
+
+ it('정렬: 미제출이 먼저', () => {
+ const assigns = [
+ makeAssign({ isSubmit: true, title: '제출됨' }),
+ makeAssign({ isSubmit: false, title: '미제출' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '', 'dueDate');
+ expect(result[0].isSubmit).toBe(false);
+ });
+
+ it('정렬: dueDate null은 맨 뒤', () => {
+ const assigns = [
+ makeAssign({ isSubmit: false, dueDate: null, title: 'A' }),
+ makeAssign({ isSubmit: false, dueDate: '2024-06-15', title: 'B' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '', 'dueDate');
+ expect(result[0].dueDate).toBe('2024-06-15');
+ expect(result[1].dueDate).toBeNull();
+ });
+
+ it('정렬: 이른 마감이 먼저', () => {
+ const assigns = [
+ makeAssign({ isSubmit: false, dueDate: '2024-07-01', title: 'B' }),
+ makeAssign({ isSubmit: false, dueDate: '2024-06-15', title: 'A' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '', 'dueDate');
+ expect(result[0].title).toBe('A');
+ });
+
+ it('정렬: title 기준', () => {
+ const assigns = [
+ makeAssign({ isSubmit: false, title: 'B과제' }),
+ makeAssign({ isSubmit: false, title: 'A과제' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '', 'title');
+ expect(result[0].title).toBe('A과제');
+ });
+
+ it('검색어로 과목명 필터링', () => {
+ const assigns = [
+ makeAssign({ courseTitle: '데이터베이스' }),
+ makeAssign({ courseTitle: '네트워크' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '데이터', 'dueDate');
+ expect(result).toHaveLength(1);
+ });
+
+ it('정렬: dueDate가 같으면 순서 유지', () => {
+ const assigns = [
+ makeAssign({ isSubmit: false, dueDate: '2024-06-30', title: 'B' }),
+ makeAssign({ isSubmit: false, dueDate: '2024-06-30', title: 'A' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '', 'dueDate');
+ // 같은 dueDate → 0 반환 → 원래 순서
+ expect(result[0].title).toBe('B');
+ });
+
+ it('정렬: 양쪽 모두 dueDate null이면 순서 유지', () => {
+ const assigns = [
+ makeAssign({ isSubmit: false, dueDate: null, title: 'B' }),
+ makeAssign({ isSubmit: false, dueDate: null, title: 'A' }),
+ ];
+ const result = filterAssigns(assigns, emptyFilters, '', 'dueDate');
+ expect(result[0].title).toBe('B');
+ });
+
+ it('submitStatuses 필터가 빈 배열이면 전체 반환', () => {
+ const assigns = [
+ makeAssign({ isSubmit: true, title: 'A' }),
+ makeAssign({ isSubmit: false, title: 'B' }),
+ ];
+ const filters: Filters = { courseTitles: [], submitStatuses: [] };
+ const result = filterAssigns(assigns, filters, '', 'dueDate');
+ expect(result).toHaveLength(2);
+ });
+
+ it('제출/미제출 모두 포함하는 submitStatuses 필터', () => {
+ const assigns = [
+ makeAssign({ isSubmit: true, title: 'A' }),
+ makeAssign({ isSubmit: false, title: 'B' }),
+ ];
+ const filters: Filters = { courseTitles: [], submitStatuses: [true, false] };
+ const result = filterAssigns(assigns, filters, '', 'dueDate');
+ expect(result).toHaveLength(2);
+ });
+});
+
+describe('filterQuizzes', () => {
+ it('필터 없이 전체 반환', () => {
+ const quizzes = [makeQuiz(), makeQuiz({ title: '퀴즈2' })];
+ const result = filterQuizzes(quizzes, emptyFilters, '', 'dueDate');
+ expect(result).toHaveLength(2);
+ });
+
+ it('제출 상태 필터', () => {
+ const quizzes = [
+ makeQuiz({ isSubmit: false }),
+ makeQuiz({ isSubmit: true, title: '퀴즈2' }),
+ ];
+ const filters: Filters = { courseTitles: [], submitStatuses: [true] };
+ const result = filterQuizzes(quizzes, filters, '', 'dueDate');
+ expect(result).toHaveLength(1);
+ expect(result[0].isSubmit).toBe(true);
+ });
+
+ it('정렬: 미제출 우선', () => {
+ const quizzes = [
+ makeQuiz({ isSubmit: true, title: '제출됨' }),
+ makeQuiz({ isSubmit: false, title: '미제출' }),
+ ];
+ const result = filterQuizzes(quizzes, emptyFilters, '', 'dueDate');
+ expect(result[0].isSubmit).toBe(false);
+ });
+
+ it('정렬: dueDate null은 맨 뒤', () => {
+ const quizzes = [
+ makeQuiz({ isSubmit: false, dueDate: null, title: 'A' }),
+ makeQuiz({ isSubmit: false, dueDate: '2024-06-15', title: 'B' }),
+ ];
+ const result = filterQuizzes(quizzes, emptyFilters, '', 'dueDate');
+ expect(result[0].dueDate).toBe('2024-06-15');
+ });
+
+ it('검색어와 과목 필터 동시 적용', () => {
+ const quizzes = [
+ makeQuiz({ courseTitle: '수학', title: '기말 퀴즈' }),
+ makeQuiz({ courseTitle: '수학', title: '중간 퀴즈' }),
+ makeQuiz({ courseTitle: '영어', title: '기말 퀴즈' }),
+ ];
+ const filters: Filters = { courseTitles: ['수학'] };
+ const result = filterQuizzes(quizzes, filters, '기말', 'dueDate');
+ expect(result).toHaveLength(1);
+ expect(result[0].title).toBe('기말 퀴즈');
+ expect(result[0].courseTitle).toBe('수학');
+ });
+
+ it('정렬: 이른 마감이 먼저 (퀴즈)', () => {
+ const quizzes = [
+ makeQuiz({ isSubmit: false, dueDate: '2024-07-15', title: 'B' }),
+ makeQuiz({ isSubmit: false, dueDate: '2024-06-15', title: 'A' }),
+ ];
+ const result = filterQuizzes(quizzes, emptyFilters, '', 'dueDate');
+ expect(result[0].title).toBe('A');
+ });
+
+ it('정렬: title 기준 (퀴즈)', () => {
+ const quizzes = [
+ makeQuiz({ isSubmit: false, title: 'B퀴즈' }),
+ makeQuiz({ isSubmit: false, title: 'A퀴즈' }),
+ ];
+ const result = filterQuizzes(quizzes, emptyFilters, '', 'title');
+ expect(result[0].title).toBe('A퀴즈');
+ });
+
+ it('여러 과목 필터 동시 적용', () => {
+ const quizzes = [
+ makeQuiz({ courseTitle: '수학', title: '퀴즈1' }),
+ makeQuiz({ courseTitle: '영어', title: '퀴즈2' }),
+ makeQuiz({ courseTitle: '물리', title: '퀴즈3' }),
+ ];
+ const filters: Filters = { courseTitles: ['수학', '영어'] };
+ const result = filterQuizzes(quizzes, filters, '', 'dueDate');
+ expect(result).toHaveLength(2);
+ });
+});
diff --git a/src/__tests__/generateKey.test.ts b/src/__tests__/generateKey.test.ts
new file mode 100644
index 0000000..b4aee94
--- /dev/null
+++ b/src/__tests__/generateKey.test.ts
@@ -0,0 +1,60 @@
+import { describe, it, expect } from 'vitest';
+import { makeVodKey, makeItemKey, makeVodGroupKey } from '@/lib/generateKey';
+
+describe('makeVodKey', () => {
+ it('courseId-title-week 형식의 키 생성', () => {
+ expect(makeVodKey('123', '강의1', 3)).toBe('123-강의1-3');
+ });
+
+ it('빈 문자열도 키 생성', () => {
+ expect(makeVodKey('', '', 0)).toBe('--0');
+ });
+
+ it('특수문자 포함', () => {
+ expect(makeVodKey('c-1', 'A/B', 1)).toBe('c-1-A/B-1');
+ });
+});
+
+describe('makeItemKey', () => {
+ it('courseId-title-dueDate 형식의 키 생성', () => {
+ expect(makeItemKey('123', '과제1', '2024-06-15')).toBe('123-과제1-2024-06-15');
+ });
+
+ it('빈 값 처리', () => {
+ expect(makeItemKey('', '', '')).toBe('--');
+ });
+});
+
+describe('makeVodGroupKey', () => {
+ it('courseId-subject-range 형식의 키 생성', () => {
+ expect(makeVodGroupKey('123', '1주차', '2024-01-01 ~ 2024-01-07'))
+ .toBe('123-1주차-2024-01-01 ~ 2024-01-07');
+ });
+
+ it('range가 null이면 null 포함', () => {
+ expect(makeVodGroupKey('123', '1주차', null)).toBe('123-1주차-null');
+ });
+
+ it('같은 courseId+subject이지만 다른 range면 다른 키', () => {
+ const key1 = makeVodGroupKey('123', '1주차', '2024-01-01 ~ 2024-01-07');
+ const key2 = makeVodGroupKey('123', '1주차', '2024-01-08 ~ 2024-01-14');
+ expect(key1).not.toBe(key2);
+ });
+
+ it('한글/특수문자 포함 키 생성', () => {
+ expect(makeVodGroupKey('C-1', '1주차 (보충)', '2024-01-01 ~ 2024-01-07'))
+ .toBe('C-1-1주차 (보충)-2024-01-01 ~ 2024-01-07');
+ });
+});
+
+describe('키 충돌 검증', () => {
+ it('makeVodKey - 서로 다른 입력은 다른 키 생성', () => {
+ expect(makeVodKey('C1', '강의1', 1)).not.toBe(makeVodKey('C1', '강의1', 2));
+ expect(makeVodKey('C1', '강의1', 1)).not.toBe(makeVodKey('C2', '강의1', 1));
+ expect(makeVodKey('C1', '강의1', 1)).not.toBe(makeVodKey('C1', '강의2', 1));
+ });
+
+ it('makeItemKey - 서로 다른 입력은 다른 키 생성', () => {
+ expect(makeItemKey('C1', '과제', '2024-01')).not.toBe(makeItemKey('C1', '과제', '2024-02'));
+ });
+});
diff --git a/src/__tests__/lmsKeywords.test.ts b/src/__tests__/lmsKeywords.test.ts
new file mode 100644
index 0000000..0fd7517
--- /dev/null
+++ b/src/__tests__/lmsKeywords.test.ts
@@ -0,0 +1,97 @@
+import { describe, it, expect } from 'vitest';
+import { normalizeLmsDate, normalizeLmsRange, NOT_SUBMITTED, BULK_APPROVED } from '@/lib/lmsKeywords';
+
+describe('normalizeLmsDate', () => {
+ it('null 반환', () => {
+ expect(normalizeLmsDate(null)).toBeNull();
+ });
+
+ it('빈 문자열은 falsy이므로 null 반환', () => {
+ expect(normalizeLmsDate('')).toBeNull();
+ });
+
+ it('이미 ISO-like 형식(ko/en)이면 그대로 반환', () => {
+ expect(normalizeLmsDate('2024-03-15 09:00')).toBe('2024-03-15 09:00');
+ });
+
+ it('ISO 형식에 초까지 포함되어도 그대로 반환', () => {
+ expect(normalizeLmsDate('2024-03-15 09:00:00')).toBe('2024-03-15 09:00:00');
+ });
+
+ it('일본어 형식 파싱 (요일 포함)', () => {
+ expect(normalizeLmsDate('2023年 04月 24日(月曜日) 10:50')).toBe('2023-04-24 10:50');
+ });
+
+ it('중국어 형식 파싱 (星期 포함)', () => {
+ expect(normalizeLmsDate('2023年04月24日 星期一 10:50')).toBe('2023-04-24 10:50');
+ });
+
+ it('일본어 형식 - 공백 없는 경우', () => {
+ expect(normalizeLmsDate('2023年04月24日 10:50')).toBe('2023-04-24 10:50');
+ });
+
+ it('앞뒤 공백은 trim', () => {
+ expect(normalizeLmsDate(' 2024-03-15 09:00 ')).toBe('2024-03-15 09:00');
+ });
+
+ it('매칭 안 되는 형식은 원본 반환', () => {
+ expect(normalizeLmsDate('March 15, 2024')).toBe('March 15, 2024');
+ });
+});
+
+describe('normalizeLmsRange', () => {
+ it('null 반환', () => {
+ expect(normalizeLmsRange(null)).toBeNull();
+ });
+
+ it('~ 없는 단일 날짜는 normalizeLmsDate로 처리', () => {
+ expect(normalizeLmsRange('2023年04月24日 10:50')).toBe('2023-04-24 10:50');
+ });
+
+ it('ko/en range 정규화', () => {
+ expect(normalizeLmsRange('2024-03-15 09:00 ~ 2024-03-22 23:59'))
+ .toBe('2024-03-15 09:00 ~ 2024-03-22 23:59');
+ });
+
+ it('ja range 정규화', () => {
+ expect(normalizeLmsRange('2023年 04月 24日(月曜日) 10:50 ~ 2023年 05月 01日(月曜日) 23:59'))
+ .toBe('2023-04-24 10:50 ~ 2023-05-01 23:59');
+ });
+
+ it('zh range 정규화', () => {
+ expect(normalizeLmsRange('2023年04月24日 星期一 10:50 ~ 2023年05月01日 星期一 23:59'))
+ .toBe('2023-04-24 10:50 ~ 2023-05-01 23:59');
+ });
+
+ it('빈 문자열은 falsy이므로 null 반환', () => {
+ expect(normalizeLmsRange('')).toBeNull();
+ });
+});
+
+describe('NOT_SUBMITTED', () => {
+ it('한국어 포함', () => {
+ expect(NOT_SUBMITTED).toContain('미제출');
+ });
+
+ it('영어 포함', () => {
+ expect(NOT_SUBMITTED).toContain('Not submitted');
+ });
+
+ it('일본어 포함', () => {
+ expect(NOT_SUBMITTED).toContain('提出なし');
+ });
+
+ it('중국어 포함', () => {
+ expect(NOT_SUBMITTED).toContain('没有作业');
+ });
+});
+
+describe('BULK_APPROVED', () => {
+ it('한국어 포함', () => {
+ expect(BULK_APPROVED).toContain('일괄출석인정');
+ });
+
+ it('영어 포함', () => {
+ expect(BULK_APPROVED).toContain('Batch attendance');
+ });
+});
diff --git a/src/__tests__/parseCourses.test.ts b/src/__tests__/parseCourses.test.ts
new file mode 100644
index 0000000..dfa80c8
--- /dev/null
+++ b/src/__tests__/parseCourses.test.ts
@@ -0,0 +1,238 @@
+import { describe, it, expect } from 'vitest';
+import { parseCoursesFromDOM } from '@/lib/parseCourses';
+
+function setupDOM(html: string) {
+ document.body.innerHTML = html;
+}
+
+describe('parseCoursesFromDOM', () => {
+ it('정상적인 과목 리스트 파싱', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses).toHaveLength(1);
+ expect(courses[0].courseId).toBe('12345');
+ expect(courses[0].courseTitle).toBe('프로그래밍 기초');
+ expect(courses[0].prof).toBe('김교수');
+ });
+
+ it('course_link가 없는 li는 무시', () => {
+ setupDOM(`
+
+ `);
+ expect(parseCoursesFromDOM()).toEqual([]);
+ });
+
+ it('빈 DOM이면 빈 배열', () => {
+ setupDOM('');
+ expect(parseCoursesFromDOM()).toEqual([]);
+ });
+
+ it('커뮤니티(비교과) 과목 식별', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses[0].isCommunity).toBe(true);
+ });
+
+ it('일반 과목은 isCommunity가 false', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses[0].isCommunity).toBe(false);
+ });
+
+ it('제목에서 "new" 텍스트 제거', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses[0].courseTitle).toBe('자료구조');
+ });
+
+ it('제목에서 대괄호 제거', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses[0].courseTitle).toBe(' 알고리즘');
+ });
+
+ it('courseId가 없는 URL은 무시', () => {
+ setupDOM(`
+
+ `);
+ expect(parseCoursesFromDOM()).toEqual([]);
+ });
+
+ it('교수명이 없는 항목은 무시', () => {
+ setupDOM(`
+
+ `);
+ expect(parseCoursesFromDOM()).toEqual([]);
+ });
+
+ it('여러 과목 동시 파싱', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses).toHaveLength(2);
+ });
+
+ it('h1 태그에서도 제목 파싱', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses).toHaveLength(1);
+ expect(courses[0].courseTitle).toBe('컴파일러');
+ });
+
+ it('h2 태그에서도 제목 파싱', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses).toHaveLength(1);
+ expect(courses[0].courseTitle).toBe('네트워크');
+ });
+
+ it('"New" 대문자도 제거 (case insensitive)', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses[0].courseTitle).toBe('운영체제');
+ });
+
+ it('제목이 빈 문자열이면 무시 (courseTitle falsy)', () => {
+ setupDOM(`
+
+ `);
+ expect(parseCoursesFromDOM()).toEqual([]);
+ });
+
+ it('유효한 항목과 무효한 항목 혼합 시 유효한 것만 반환', () => {
+ setupDOM(`
+
+ `);
+ const courses = parseCoursesFromDOM();
+ expect(courses).toHaveLength(1);
+ expect(courses[0].courseTitle).toBe('유효과목');
+ });
+});
diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts
new file mode 100644
index 0000000..0b4f49a
--- /dev/null
+++ b/src/__tests__/setup.ts
@@ -0,0 +1,15 @@
+import { vi } from 'vitest';
+
+// Mock i18n - 키를 그대로 반환하되, interpolation 값이 있으면 포함
+vi.mock('@/i18n', () => ({
+ default: {
+ t: (key: string, options?: Record) => {
+ const opts = { ...options };
+ delete opts.ns;
+ const params = Object.entries(opts)
+ .map(([k, v]) => `${k}:${v}`)
+ .join(',');
+ return params ? `${key}{${params}}` : key;
+ },
+ },
+}));
diff --git a/src/__tests__/storage.test.ts b/src/__tests__/storage.test.ts
new file mode 100644
index 0000000..bb0690f
--- /dev/null
+++ b/src/__tests__/storage.test.ts
@@ -0,0 +1,99 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { saveDataToStorage, loadDataFromStorage, loadAndTransform } from '@/lib/storage';
+
+// chrome.storage.local mock
+const mockStorage: Record = {};
+
+beforeEach(() => {
+ Object.keys(mockStorage).forEach((key) => delete mockStorage[key]);
+
+ vi.stubGlobal('chrome', {
+ storage: {
+ local: {
+ set: vi.fn((items: Record, cb?: () => void) => {
+ Object.assign(mockStorage, items);
+ cb?.();
+ }),
+ get: vi.fn((keys: string[], cb: (result: Record) => void) => {
+ const result: Record = {};
+ keys.forEach((key) => {
+ if (mockStorage[key] !== undefined) result[key] = mockStorage[key];
+ });
+ cb(result);
+ }),
+ },
+ },
+ });
+});
+
+describe('saveDataToStorage', () => {
+ it('데이터를 chrome.storage.local에 저장', () => {
+ saveDataToStorage('testKey', { value: 42 });
+ expect(chrome.storage.local.set).toHaveBeenCalledWith({ testKey: { value: 42 } }, expect.any(Function));
+ expect(mockStorage['testKey']).toEqual({ value: 42 });
+ });
+
+ it('문자열 데이터 저장', () => {
+ saveDataToStorage('str', 'hello');
+ expect(mockStorage['str']).toBe('hello');
+ });
+
+ it('배열 데이터 저장', () => {
+ saveDataToStorage('arr', [1, 2, 3]);
+ expect(mockStorage['arr']).toEqual([1, 2, 3]);
+ });
+});
+
+describe('loadDataFromStorage', () => {
+ it('저장된 데이터를 콜백으로 반환', () => {
+ mockStorage['myKey'] = { name: 'test' };
+ const callback = vi.fn();
+
+ loadDataFromStorage('myKey', callback);
+ expect(callback).toHaveBeenCalledWith({ name: 'test' });
+ });
+
+ it('데이터가 없으면 null 반환', () => {
+ const callback = vi.fn();
+
+ loadDataFromStorage('nonExistent', callback);
+ expect(callback).toHaveBeenCalledWith(null);
+ });
+});
+
+describe('loadAndTransform', () => {
+ it('JSON 문자열을 파싱하고 변환 함수 적용', () => {
+ mockStorage['items'] = JSON.stringify([1, 2, 3]);
+ const callback = vi.fn();
+
+ loadAndTransform('items', (data) => data.reduce((a, b) => a + b, 0), callback);
+ expect(callback).toHaveBeenCalledWith(6);
+ });
+
+ it('이미 파싱된 데이터에 변환 함수 적용', () => {
+ mockStorage['items'] = [10, 20];
+ const callback = vi.fn();
+
+ loadAndTransform('items', (data) => data.length, callback);
+ expect(callback).toHaveBeenCalledWith(2);
+ });
+
+ it('데이터가 없으면 콜백 호출 안 함', () => {
+ const callback = vi.fn();
+
+ loadAndTransform('missing', (data) => data, callback);
+ expect(callback).not.toHaveBeenCalled();
+ });
+
+ it('잘못된 JSON이면 콜백 호출 안 함', () => {
+ mockStorage['bad'] = '{invalid json';
+ const callback = vi.fn();
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ loadAndTransform('bad', (data) => data, callback);
+ expect(callback).not.toHaveBeenCalled();
+ expect(consoleSpy).toHaveBeenCalled();
+
+ consoleSpy.mockRestore();
+ });
+});
diff --git a/src/__tests__/stringUtils.test.ts b/src/__tests__/stringUtils.test.ts
new file mode 100644
index 0000000..7110c26
--- /dev/null
+++ b/src/__tests__/stringUtils.test.ts
@@ -0,0 +1,66 @@
+import { describe, it, expect } from 'vitest';
+import { removeSquareBrackets, formatDateString } from '@/lib/stringUtils';
+
+describe('removeSquareBrackets', () => {
+ it('대괄호와 내용을 제거', () => {
+ expect(removeSquareBrackets('[A반] 프로그래밍')).toBe(' 프로그래밍');
+ });
+
+ it('여러 대괄호를 모두 제거', () => {
+ expect(removeSquareBrackets('[A반] [2학기] 프로그래밍')).toBe(' 프로그래밍');
+ });
+
+ it('대괄호가 없으면 원본 반환', () => {
+ expect(removeSquareBrackets('프로그래밍')).toBe('프로그래밍');
+ });
+
+ it('빈 문자열 처리', () => {
+ expect(removeSquareBrackets('')).toBe('');
+ });
+
+ it('빈 대괄호 제거', () => {
+ expect(removeSquareBrackets('[] 과목')).toBe(' 과목');
+ });
+
+ it('중첩 대괄호: [^\\]]*는 ]를 포함하지 않으므로 [[내부] 매칭 → "]" 남음', () => {
+ // 정규식 /\[[^\]]*\]/g: [[내부] 가 첫 매치 → 제거 → "]" 남음
+ expect(removeSquareBrackets('[[내부]]')).toBe(']');
+ });
+
+ it('닫히지 않은 대괄호는 유지', () => {
+ expect(removeSquareBrackets('[미완성')).toBe('[미완성');
+ });
+
+ it('숫자가 포함된 대괄호 제거', () => {
+ expect(removeSquareBrackets('[001] 과제')).toBe(' 과제');
+ });
+
+ it('특수문자가 포함된 대괄호 제거', () => {
+ expect(removeSquareBrackets('[A-1반] 수업')).toBe(' 수업');
+ });
+});
+
+describe('formatDateString', () => {
+ it('전체 날짜 범위를 축약 형식으로 변환', () => {
+ expect(formatDateString('2024-03-15 09:00:00 ~ 2024-03-22 23:59:00'))
+ .toBe('24.03.15 09:00 ~ 24.03.22 23:59');
+ });
+
+ it('null이면 i18n 키 반환', () => {
+ const result = formatDateString(null);
+ expect(result).toBe('date.noDeadline');
+ });
+
+ it('매칭되지 않는 형식은 원본 그대로 반환', () => {
+ expect(formatDateString('마감 없음')).toBe('마감 없음');
+ });
+
+ it('빈 문자열은 falsy이므로 noDeadline 반환', () => {
+ expect(formatDateString('')).toBe('date.noDeadline');
+ });
+
+ it('초 부분이 없는 형식은 원본 반환', () => {
+ expect(formatDateString('2024-03-15 09:00 ~ 2024-03-22 23:59'))
+ .toBe('2024-03-15 09:00 ~ 2024-03-22 23:59');
+ });
+});
diff --git a/src/__tests__/summarizeCourseData.test.ts b/src/__tests__/summarizeCourseData.test.ts
new file mode 100644
index 0000000..67ee53a
--- /dev/null
+++ b/src/__tests__/summarizeCourseData.test.ts
@@ -0,0 +1,116 @@
+import { describe, it, expect } from 'vitest';
+import { summarizeVods } from '@/lib/summarizeCourseData';
+import { Vod } from '@/types';
+
+const makeVod = (overrides: Partial = {}): Vod => ({
+ courseId: 'C1',
+ courseTitle: '프로그래밍',
+ prof: '김교수',
+ week: 1,
+ subject: '1주차',
+ title: '강의1',
+ url: '/v1',
+ range: '2024-01-01 ~ 2024-01-07',
+ length: '30:00',
+ isAttendance: 'O',
+ weeklyAttendance: 'O',
+ ...overrides,
+});
+
+describe('summarizeVods', () => {
+ it('빈 배열이면 done=0, total=0', () => {
+ expect(summarizeVods([])).toEqual({ done: 0, total: 0 });
+ });
+
+ it('같은 그룹의 VOD들은 하나로 카운트', () => {
+ const vods = [
+ makeVod({ title: '파트1' }),
+ makeVod({ title: '파트2' }),
+ ]; // 같은 courseId+subject+range → 1그룹
+ const result = summarizeVods(vods);
+ expect(result.total).toBe(1);
+ });
+
+ it('다른 그룹은 별도 카운트', () => {
+ const vods = [
+ makeVod({ subject: '1주차' }),
+ makeVod({ subject: '2주차' }),
+ ];
+ const result = summarizeVods(vods);
+ expect(result.total).toBe(2);
+ });
+
+ it('weeklyAttendance가 O이면 done 카운트', () => {
+ const vods = [
+ makeVod({ subject: '1주차', weeklyAttendance: 'O' }),
+ makeVod({ subject: '2주차', weeklyAttendance: 'X' }),
+ ];
+ const result = summarizeVods(vods);
+ expect(result.done).toBe(1);
+ expect(result.total).toBe(2);
+ });
+
+ it('그룹의 첫 번째 항목의 weeklyAttendance로 판단', () => {
+ const vods = [
+ makeVod({ title: '파트1', weeklyAttendance: 'O' }),
+ makeVod({ title: '파트2', weeklyAttendance: 'X' }),
+ ]; // 같은 그룹 → 첫 번째(O) 기준
+ const result = summarizeVods(vods);
+ expect(result.done).toBe(1);
+ });
+
+ it('모두 출석이면 done === total', () => {
+ const vods = [
+ makeVod({ subject: '1주차', weeklyAttendance: 'O' }),
+ makeVod({ subject: '2주차', weeklyAttendance: 'O' }),
+ ];
+ const result = summarizeVods(vods);
+ expect(result.done).toBe(result.total);
+ });
+
+ it('모두 미출석이면 done === 0', () => {
+ const vods = [
+ makeVod({ subject: '1주차', weeklyAttendance: 'X' }),
+ makeVod({ subject: '2주차', weeklyAttendance: 'X' }),
+ ];
+ const result = summarizeVods(vods);
+ expect(result.done).toBe(0);
+ });
+
+ it('다른 courseId인 같은 subject+range는 별도 그룹', () => {
+ const vods = [
+ makeVod({ courseId: 'C1', subject: '1주차' }),
+ makeVod({ courseId: 'C2', subject: '1주차' }),
+ ];
+ const result = summarizeVods(vods);
+ expect(result.total).toBe(2);
+ });
+
+ it('같은 courseId+subject이지만 다른 range는 별도 그룹', () => {
+ const vods = [
+ makeVod({ range: '2024-01-01 ~ 2024-01-07' }),
+ makeVod({ range: '2024-01-08 ~ 2024-01-14' }),
+ ];
+ const result = summarizeVods(vods);
+ expect(result.total).toBe(2);
+ });
+
+ it('weeklyAttendance가 "o" (소문자)이면 isAttended → done 카운트', () => {
+ const vods = [makeVod({ subject: '1주차', weeklyAttendance: 'o' })];
+ const result = summarizeVods(vods);
+ expect(result.done).toBe(1);
+ });
+
+ it('weeklyAttendance가 빈 문자열이면 미출석', () => {
+ const vods = [makeVod({ subject: '1주차', weeklyAttendance: '' })];
+ const result = summarizeVods(vods);
+ expect(result.done).toBe(0);
+ });
+
+ it('단일 VOD 단일 그룹', () => {
+ const vods = [makeVod()];
+ const result = summarizeVods(vods);
+ expect(result.total).toBe(1);
+ expect(result.done).toBe(1);
+ });
+});
diff --git a/src/__tests__/transformCalendarEvents.test.ts b/src/__tests__/transformCalendarEvents.test.ts
new file mode 100644
index 0000000..302b72b
--- /dev/null
+++ b/src/__tests__/transformCalendarEvents.test.ts
@@ -0,0 +1,143 @@
+import { describe, it, expect } from 'vitest';
+import { vodGroupsToEvents, dueDateItemToEvent } from '@/lib/transformCalendarEvents';
+import { Vod } from '@/types';
+
+const makeVod = (overrides: Partial = {}): Vod => ({
+ courseId: 'C1',
+ courseTitle: '[A반] 프로그래밍',
+ prof: '김교수',
+ week: 1,
+ subject: '[1주차] 개론',
+ title: '강의1',
+ url: '/v1',
+ range: '2024-01-01 09:00 ~ 2024-01-07 23:59',
+ length: '30:00',
+ isAttendance: 'O',
+ weeklyAttendance: 'O',
+ ...overrides,
+});
+
+describe('vodGroupsToEvents', () => {
+ it('같은 courseId+subject+range는 하나의 이벤트로 그룹핑', () => {
+ const vods = [
+ makeVod({ title: '파트1' }),
+ makeVod({ title: '파트2' }),
+ ];
+ const events = vodGroupsToEvents(vods);
+ expect(events).toHaveLength(1);
+ expect(events[0].type).toBe('vod');
+ });
+
+ it('다른 subject면 별도 이벤트', () => {
+ const vods = [
+ makeVod({ subject: '1주차' }),
+ makeVod({ subject: '2주차' }),
+ ];
+ const events = vodGroupsToEvents(vods);
+ expect(events).toHaveLength(2);
+ });
+
+ it('range에서 시작/종료 날짜 파싱', () => {
+ const events = vodGroupsToEvents([makeVod()]);
+ expect(events[0].start).toBeInstanceOf(Date);
+ expect(events[0].end).toBeInstanceOf(Date);
+ });
+
+ it('range가 null이면 start/end도 null', () => {
+ const events = vodGroupsToEvents([makeVod({ range: null })]);
+ expect(events[0].start).toBeNull();
+ expect(events[0].end).toBeNull();
+ });
+
+ it('대괄호가 title/subject에서 제거됨', () => {
+ const events = vodGroupsToEvents([makeVod()]);
+ expect(events[0].title).not.toContain('[');
+ expect(events[0].subject).not.toContain('[');
+ });
+
+ it('빈 배열이면 빈 이벤트 배열', () => {
+ expect(vodGroupsToEvents([])).toEqual([]);
+ });
+
+ it('다른 courseId면 같은 subject+range여도 별도 이벤트', () => {
+ const vods = [
+ makeVod({ courseId: 'C1' }),
+ makeVod({ courseId: 'C2' }),
+ ];
+ const events = vodGroupsToEvents(vods);
+ expect(events).toHaveLength(2);
+ });
+
+ it('같은 courseId+subject이지만 다른 range면 별도 이벤트', () => {
+ const vods = [
+ makeVod({ range: '2024-01-01 09:00 ~ 2024-01-07 23:59' }),
+ makeVod({ range: '2024-01-08 09:00 ~ 2024-01-14 23:59' }),
+ ];
+ const events = vodGroupsToEvents(vods);
+ expect(events).toHaveLength(2);
+ });
+
+ it('그룹의 첫 번째 VOD 기준으로 title/subject 설정', () => {
+ const vods = [
+ makeVod({ courseTitle: '[A반] 수학', subject: '[1주차] 개론', title: '파트1' }),
+ makeVod({ courseTitle: '[B반] 수학', subject: '[2주차] 심화', title: '파트2' }),
+ ];
+ // 같은 courseId+subject+range이면 하나로 묶이므로, 다른 subject면 별도
+ const events = vodGroupsToEvents(vods);
+ expect(events).toHaveLength(2);
+ });
+});
+
+describe('dueDateItemToEvent', () => {
+ it('assign 타입 이벤트 생성', () => {
+ const item = { courseId: 'C1', courseTitle: '[A반] 수학', title: '과제1', dueDate: '2024-06-30' };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.type).toBe('assign');
+ expect(event.start).toBeInstanceOf(Date);
+ expect(event.title).toBe(' 수학');
+ });
+
+ it('quiz 타입 이벤트 생성', () => {
+ const item = { courseId: 'C1', courseTitle: '영어', title: '퀴즈1', dueDate: '2024-06-30' };
+ const event = dueDateItemToEvent(item, 'quiz');
+ expect(event.type).toBe('quiz');
+ });
+
+ it('dueDate가 null이면 start/end null', () => {
+ const item = { courseId: 'C1', courseTitle: '수학', title: '과제1', dueDate: null };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.start).toBeNull();
+ expect(event.end).toBeNull();
+ });
+
+ it('id는 courseId+title+dueDate 조합', () => {
+ const item = { courseId: 'C1', courseTitle: '수학', title: '과제1', dueDate: '2024-06-30' };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.id).toBe('C1과제12024-06-30');
+ });
+
+ it('dueDate가 startOfDay로 정규화됨', () => {
+ const item = { courseId: 'C1', courseTitle: '수학', title: '과제1', dueDate: '2024-06-30 15:30:00' };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.start!.getHours()).toBe(0);
+ expect(event.start!.getMinutes()).toBe(0);
+ });
+
+ it('start와 end가 동일한 Date 값', () => {
+ const item = { courseId: 'C1', courseTitle: '수학', title: '과제1', dueDate: '2024-06-30' };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.start!.getTime()).toBe(event.end!.getTime());
+ });
+
+ it('subject는 item의 title에서 대괄호 제거한 값', () => {
+ const item = { courseId: 'C1', courseTitle: '수학', title: '[3주차] 리포트', dueDate: '2024-06-30' };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.subject).toBe(' 리포트');
+ });
+
+ it('dueDate null일 때 id에 null 문자열 포함', () => {
+ const item = { courseId: 'C1', courseTitle: '수학', title: '과제1', dueDate: null };
+ const event = dueDateItemToEvent(item, 'assign');
+ expect(event.id).toBe('C1과제1null');
+ });
+});
diff --git a/src/__tests__/transformCourseData.test.ts b/src/__tests__/transformCourseData.test.ts
new file mode 100644
index 0000000..bc64f6a
--- /dev/null
+++ b/src/__tests__/transformCourseData.test.ts
@@ -0,0 +1,154 @@
+import { describe, it, expect } from 'vitest';
+import { mergeVodWithAttendance, mergeDueDateItems } from '@/lib/transformCourseData';
+import { CourseBase } from '@/types';
+
+const baseCourse: CourseBase = {
+ courseId: 'C1',
+ courseTitle: '프로그래밍',
+ prof: '김교수',
+};
+
+describe('mergeVodWithAttendance', () => {
+ it('title+week가 일치하는 항목만 병합', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의1', url: '/v1', range: '2024-01-01 ~ 2024-01-07', length: '30:00' },
+ { week: 2, subject: '2주차', title: '강의2', url: '/v2', range: null, length: '45:00' },
+ ];
+ const attendanceList = [
+ { title: '강의1', isAttendance: 'O', weeklyAttendance: 'O', week: 1 },
+ // week 2의 출석 데이터 없음
+ ];
+
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result).toHaveLength(1);
+ expect(result[0].title).toBe('강의1');
+ expect(result[0].isAttendance).toBe('O');
+ expect(result[0].courseId).toBe('C1');
+ });
+
+ it('빈 vodList이면 빈 배열 반환', () => {
+ const result = mergeVodWithAttendance(baseCourse, [], []);
+ expect(result).toEqual([]);
+ });
+
+ it('빈 attendanceList이면 모든 VOD 누락', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의1', url: '/v1', range: null, length: '30:00' },
+ ];
+ const result = mergeVodWithAttendance(baseCourse, vodList, []);
+ expect(result).toEqual([]);
+ });
+
+ it('같은 title이지만 다른 week이면 매칭 안 됨', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의1', url: '/v1', range: null, length: '30:00' },
+ ];
+ const attendanceList = [
+ { title: '강의1', isAttendance: 'O', weeklyAttendance: 'O', week: 2 },
+ ];
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result).toEqual([]);
+ });
+
+ it('같은 week이지만 다른 title이면 매칭 안 됨', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의A', url: '/v1', range: null, length: '30:00' },
+ ];
+ const attendanceList = [
+ { title: '강의B', isAttendance: 'O', weeklyAttendance: 'O', week: 1 },
+ ];
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result).toEqual([]);
+ });
+
+ it('동일 title+week 출석이 여러 개면 마지막이 사용됨 (Map 덮어쓰기)', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의1', url: '/v1', range: null, length: '30:00' },
+ ];
+ const attendanceList = [
+ { title: '강의1', isAttendance: 'X', weeklyAttendance: 'X', week: 1 },
+ { title: '강의1', isAttendance: 'O', weeklyAttendance: 'O', week: 1 },
+ ];
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result[0].isAttendance).toBe('O');
+ });
+
+ it('course 정보가 결과에 포함됨', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의1', url: '/v1', range: null, length: '30:00' },
+ ];
+ const attendanceList = [
+ { title: '강의1', isAttendance: 'O', weeklyAttendance: 'O', week: 1 },
+ ];
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result[0].courseTitle).toBe('프로그래밍');
+ expect(result[0].prof).toBe('김교수');
+ });
+});
+
+describe('mergeDueDateItems', () => {
+ it('course 정보를 각 항목에 병합', () => {
+ const items = [
+ { title: '과제1', dueDate: '2024-06-30' },
+ { title: '과제2', dueDate: null },
+ ];
+ const result = mergeDueDateItems(baseCourse, items);
+ expect(result).toHaveLength(2);
+ expect(result[0].courseId).toBe('C1');
+ expect(result[0].title).toBe('과제1');
+ expect(result[1].dueDate).toBeNull();
+ });
+
+ it('빈 배열이면 빈 배열 반환', () => {
+ expect(mergeDueDateItems(baseCourse, [])).toEqual([]);
+ });
+
+ it('추가 필드가 있어도 보존됨', () => {
+ const items = [{ title: '과제1', dueDate: '2024-06-30', extra: 'value' }];
+ const result = mergeDueDateItems(baseCourse, items);
+ expect(result[0].extra).toBe('value');
+ });
+
+ it('item의 필드가 course 필드와 겹치면 item이 우선', () => {
+ // T에 courseTitle이 있으면 spread 순서에 의해 item 값이 우선
+ const items = [{ title: '과제1', dueDate: '2024-06-30', courseTitle: '재정의됨' } as { title: string; dueDate: string; courseTitle: string }];
+ const result = mergeDueDateItems(baseCourse, items);
+ expect(result[0].courseTitle).toBe('재정의됨');
+ });
+});
+
+describe('mergeVodWithAttendance - 추가 edge cases', () => {
+ it('대량 데이터 매칭', () => {
+ const vodList = Array.from({ length: 100 }, (_, i) => ({
+ week: i, subject: `${i}주차`, title: `강의${i}`, url: `/v${i}`, range: null, length: '30:00',
+ }));
+ const attendanceList = Array.from({ length: 100 }, (_, i) => ({
+ title: `강의${i}`, isAttendance: i % 2 === 0 ? 'O' : 'X', weeklyAttendance: 'O', week: i,
+ }));
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result).toHaveLength(100);
+ });
+
+ it('title에 특수문자가 포함된 경우 정확한 매칭', () => {
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의-1(A)', url: '/v1', range: null, length: '30:00' },
+ ];
+ const attendanceList = [
+ { title: '강의-1(A)', isAttendance: 'O', weeklyAttendance: 'O', week: 1 },
+ ];
+ const result = mergeVodWithAttendance(baseCourse, vodList, attendanceList);
+ expect(result).toHaveLength(1);
+ });
+
+ it('isCommunity 필드가 course에서 전파됨', () => {
+ const communityCourse: CourseBase = { ...baseCourse, isCommunity: true };
+ const vodList = [
+ { week: 1, subject: '1주차', title: '강의1', url: '/v1', range: null, length: '30:00' },
+ ];
+ const attendanceList = [
+ { title: '강의1', isAttendance: 'O', weeklyAttendance: 'O', week: 1 },
+ ];
+ const result = mergeVodWithAttendance(communityCourse, vodList, attendanceList);
+ expect(result[0].isCommunity).toBe(true);
+ });
+});
diff --git a/src/assets/gmail.png b/src/assets/gmail.png
new file mode 100644
index 0000000..1648f51
Binary files /dev/null and b/src/assets/gmail.png differ
diff --git a/src/background.ts b/src/background.ts
index 13084f1..9b6fe48 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -1,93 +1,24 @@
-interface AlarmDetails {
- title: string;
- message: string;
-}
-
-const alarmsMap: { [alarmId: string]: AlarmDetails } = {};
-
-function parseDateTime(dateTimeStr: string): Date | null {
- const parts = dateTimeStr.split(' ');
- if (parts.length !== 2) {
- return null;
- }
-
- const [datePart, timePart] = parts;
- const dateComponents = datePart.split('-');
- const timeComponents = timePart.split(':');
-
- if (dateComponents.length !== 3 || (timeComponents.length !== 2 && timeComponents.length !== 3)) {
- return null;
- }
-
- const year = parseInt(dateComponents[0], 10);
- const month = parseInt(dateComponents[1], 10) - 1;
- const day = parseInt(dateComponents[2], 10);
- const hour = parseInt(timeComponents[0], 10);
- const minute = parseInt(timeComponents[1], 10);
- const second = timeComponents.length === 3 ? parseInt(timeComponents[2], 10) : 0;
-
- return new Date(year, month, day, hour, minute, second);
-}
-
-function schedulePreEventAlarm(alarmId: string, dateTimeStr: string, title: string, message: string): void {
- const eventDate = parseDateTime(dateTimeStr);
- if (!eventDate) {
- console.error('Invalid date time string:', dateTimeStr);
- return;
- }
-
- const alarmTime = eventDate.getTime() - 24 * 60 * 60 * 1000;
- const now = Date.now();
-
- if (alarmTime <= now) {
- console.warn('Alarm time is in the past. Cannot schedule alarm.');
- return;
- }
-
- alarmsMap[alarmId] = { title, message };
-
- chrome.alarms.create(alarmId, { when: alarmTime });
- console.log(`Alarm [${alarmId}] scheduled for ${new Date(alarmTime).toString()}`);
-}
-
-function cancelAlarm(alarmId: string): void {
- chrome.alarms.clear(alarmId, (wasCleared: boolean) => {
- if (wasCleared) {
- delete alarmsMap[alarmId];
- }
- });
-}
-
-/**
- * 알람이 발생했을 때 해당 알람의 정보를 기반으로 알림(notification)을 띄우는 리스너
- */
-chrome.alarms.onAlarm.addListener((alarm) => {
- const alarmId = alarm.name;
- const details = alarmsMap[alarmId];
- if (details) {
- chrome.notifications.create(`${alarmId}-notification`, {
- type: 'basic',
- iconUrl: 'images/icon/icon-128.png',
- title: details.title,
- message: details.message,
- priority: 2,
+chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
+ if (message.action === 'getAuthToken') {
+ chrome.identity.getAuthToken({ interactive: message.interactive ?? true }, (token) => {
+ if (chrome.runtime.lastError || !token) {
+ sendResponse({ token: null, error: chrome.runtime.lastError?.message });
+ } else {
+ sendResponse({ token });
+ }
});
- delete alarmsMap[alarmId];
+ return true;
+ } else if (message.action === 'removeCachedAuthToken') {
+ chrome.identity.removeCachedAuthToken({ token: message.token }, () => {
+ sendResponse({ success: true });
+ });
+ return true;
}
});
-/**
- * 메시지를 통해 알람 예약 및 취소 요청을 처리하는 리스너
- */
-chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
- if (message.action === 'scheduleAlarm') {
- const { alarmId, dateTime, title, message: alarmMessage } = message;
- schedulePreEventAlarm(alarmId, dateTime, title, alarmMessage);
- sendResponse({ status: 'scheduled', alarmId });
- } else if (message.action === 'cancelAlarm') {
- const { alarmId } = message;
- cancelAlarm(alarmId);
- sendResponse({ status: 'cancelled', alarmId });
+chrome.runtime.onInstalled.addListener((details) => {
+ if (details.reason === 'install') {
+ chrome.tabs.create({ url: 'https://hs-shell.github.io/dotbugi/' });
}
});
diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx
new file mode 100644
index 0000000..ba43c94
--- /dev/null
+++ b/src/components/ui/context-menu.tsx
@@ -0,0 +1,202 @@
+import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { useShadowRoot } from "@/popover/lib/ShadowRootContext"
+
+const ContextMenu = ContextMenuPrimitive.Root
+
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger
+
+const ContextMenuGroup = ContextMenuPrimitive.Group
+
+const ContextMenuPortal = ContextMenuPrimitive.Portal
+
+const ContextMenuSub = ContextMenuPrimitive.Sub
+
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
+
+const ContextMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+
+const ContextMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+
+const ContextMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const shadowRoot = useShadowRoot();
+ return (
+
+
+
+ )
+})
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
+
+const ContextMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+
+const ContextMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+ContextMenuCheckboxItem.displayName =
+ ContextMenuPrimitive.CheckboxItem.displayName
+
+const ContextMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+
+const ContextMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+
+const ContextMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+
+const ContextMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+ContextMenuShortcut.displayName = "ContextMenuShortcut"
+
+export {
+ ContextMenu,
+ ContextMenuTrigger,
+ ContextMenuContent,
+ ContextMenuItem,
+ ContextMenuCheckboxItem,
+ ContextMenuRadioItem,
+ ContextMenuLabel,
+ ContextMenuSeparator,
+ ContextMenuShortcut,
+ ContextMenuGroup,
+ ContextMenuPortal,
+ ContextMenuSub,
+ ContextMenuSubContent,
+ ContextMenuSubTrigger,
+ ContextMenuRadioGroup,
+}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..682aea0
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,203 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { useShadowRoot } from "@/popover/lib/ShadowRootContext"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => {
+ const shadowRoot = useShadowRoot();
+ return (
+
+
+
+ )
+})
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ svg]:size-4 [&>svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index 6c0c2db..c26903f 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/utils';
-import { useShadowRoot } from '@/lib/ShadowRootContext';
+import { useShadowRoot } from '@/popover/lib/ShadowRootContext';
const Popover = PopoverPrimitive.Root;
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
index a84242c..cd37473 100644
--- a/src/components/ui/select.tsx
+++ b/src/components/ui/select.tsx
@@ -1,14 +1,15 @@
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { Check, ChevronDown, ChevronUp } from "lucide-react"
+import * as React from 'react';
+import * as SelectPrimitive from '@radix-ui/react-select';
+import { Check, ChevronDown, ChevronUp } from 'lucide-react';
-import { cn } from "@/lib/utils"
+import { cn } from '@/lib/utils';
+import { useShadowRoot } from '@/popover/lib/ShadowRootContext';
-const Select = SelectPrimitive.Root
+const Select = SelectPrimitive.Root;
-const SelectGroup = SelectPrimitive.Group
+const SelectGroup = SelectPrimitive.Group;
-const SelectValue = SelectPrimitive.Value
+const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef,
@@ -17,7 +18,7 @@ const SelectTrigger = React.forwardRef<
span]:line-clamp-1",
+ 'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className
)}
{...props}
@@ -27,8 +28,8 @@ const SelectTrigger = React.forwardRef<
-))
-SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+));
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = React.forwardRef<
React.ElementRef,
@@ -36,16 +37,13 @@ const SelectScrollUpButton = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+));
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = React.forwardRef<
React.ElementRef,
@@ -53,61 +51,56 @@ const SelectScrollDownButton = React.forwardRef<
>(({ className, ...props }, ref) => (
-))
-SelectScrollDownButton.displayName =
- SelectPrimitive.ScrollDownButton.displayName
+));
+SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
->(({ className, children, position = "popper", ...props }, ref) => (
-
-
-
- (({ className, children, position = 'popper', ...props }, ref) => {
+ const shadowRoot = useShadowRoot();
+ return (
+
+
- {children}
-
-
-
-
-))
-SelectContent.displayName = SelectPrimitive.Content.displayName
+
+
+ {children}
+
+
+
+
+ );
+});
+SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-))
-SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+));
+SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef,
@@ -116,32 +109,28 @@ const SelectItem = React.forwardRef<
-
+
-
+
{children}
-))
-SelectItem.displayName = SelectPrimitive.Item.displayName
+));
+SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-))
-SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+));
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
@@ -154,4 +143,4 @@ export {
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
-}
+};
diff --git a/src/constants/constant.ts b/src/constants/links.ts
similarity index 79%
rename from src/constants/constant.ts
rename to src/constants/links.ts
index 53601fd..7164f33 100644
--- a/src/constants/constant.ts
+++ b/src/constants/links.ts
@@ -1,7 +1,6 @@
export const BASE_LINK = 'https://learn.hansung.ac.kr' as const;
export const getVodPageLink = (courseId: string) => {
return BASE_LINK + `/report/ubcompletion/user_progress_a.php?id=${courseId}`;
- return BASE_LINK + `/course/view.php?id=${courseId}`;
};
export const getAssignPageLink = (courseId: string) => {
return BASE_LINK + `/mod/assign/index.php?id=${courseId}`;
@@ -12,3 +11,6 @@ export const getQuizPageLink = (courseId: string) => {
export const getIndexPageLink = (courseId: string) => {
return BASE_LINK + `/course/view.php?id=${courseId}`;
};
+export const getVodProgressPageLink = (courseId: string) => {
+ return BASE_LINK + `/report/ubcompletion/user_progress.php?id=${courseId}`;
+};
diff --git a/src/content/App.tsx b/src/content/App.tsx
deleted file mode 100644
index 97371a9..0000000
--- a/src/content/App.tsx
+++ /dev/null
@@ -1,349 +0,0 @@
-import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
-import { useEffect, useMemo, useState } from 'react';
-import icon from '@/assets/icon.png';
-import exit from '@/assets/exit.png';
-import { Assign, CourseBase, Filters, Quiz, TAB_TYPE, Vod } from './types';
-import { ListFilter, OctagonAlert, RefreshCw, Search } from 'lucide-react';
-import filter from '@/assets/filter.svg';
-import PopoverFooter from './components/PopoverFooter';
-import { Spinner } from '@/components/ui/spinner';
-import { useGetCourses } from '@/hooks/useGetCourse';
-import Video from './components/Video';
-import Assignment from './components/Assignment';
-import QuizTab from './components/QuizTab';
-import { Button } from '@/components/ui/button';
-import FilterBadge from './components/FilterBadge';
-
-import FilterPanel from './components/FilterPanel';
-import { filterVods, filterAssigns, filterQuizes } from '@/lib/filterData';
-import PendingDialogWithBeforeUnload from './components/PendingDialog';
-import StickyPopoverTrigger from './StickyPopoverTrigger';
-import { useCourseData } from '@/hooks/useCourseData';
-
-const attendanceOptions = ['출석', '결석'];
-const submitOptions = [
- { label: '제출완료', value: true },
- { label: '제출필요', value: false },
-];
-
-export default function App() {
- const { courses } = useGetCourses();
- const typeCourses: CourseBase[] = courses;
-
- // 데이터 관련 상태를 useCourseData 커스텀 훅으로 관리
- const { vods, assigns, quizes, isPending, remainingTime, isError, updateData, setIsPending } =
- useCourseData(typeCourses);
-
- // activeTab의 타입을 TAB_TYPE으로 지정
- const [activeTab, setActiveTab] = useState(TAB_TYPE.VIDEO);
- const [isOpen, setIsOpen] = useState(false);
- const [searchTerm, setSearchTerm] = useState('');
- const [vodSortBy] = useState('isAttendance');
- const [assignSortBy] = useState('isSubmit');
- const [quizSortBy] = useState('dueDate');
- const [isFilterOpen, setIsFilterOpen] = useState(false);
-
- // 필터 상태 관리 - Record을 사용하여 TAB_TYPE을 키로 지정
- const [filters, setFilters] = useState>({
- VIDEO: { courseTitles: [], attendanceStatuses: [] },
- ASSIGN: { courseTitles: [], submitStatuses: [] },
- QUIZ: { courseTitles: [] },
- });
-
- useEffect(() => {
- setSearchTerm('');
- }, [activeTab]);
-
- useEffect(() => {
- setSearchTerm('');
- setIsFilterOpen(false);
- }, [activeTab]);
-
- // 필터 옵션 추출
- const courseTitlesMap = useMemo(
- () => ({
- VIDEO: Array.from(new Set(vods.map((vod) => vod.courseTitle))),
- ASSIGN: Array.from(new Set(assigns.map((assign) => assign.courseTitle))),
- QUIZ: Array.from(new Set(quizes.map((quiz) => quiz.courseTitle))),
- }),
- [vods, assigns, quizes]
- );
-
- // 필터 적용
- const filteredVods = useMemo(() => {
- return filterVods(vods, filters[activeTab], searchTerm, vodSortBy);
- }, [vods, filters, activeTab, searchTerm, vodSortBy]);
-
- const filteredAssigns = useMemo(() => {
- return filterAssigns(assigns, filters[activeTab], searchTerm, assignSortBy);
- }, [assigns, filters, activeTab, searchTerm, assignSortBy]);
-
- const filteredQuizes = useMemo(() => {
- return filterQuizes(quizes, filters[activeTab], searchTerm, quizSortBy);
- }, [quizes, filters, activeTab, searchTerm, quizSortBy]);
-
- // Vods용 필터 핸들러
- const handleAttendanceFilterChange = (status: string) => {
- setFilters((prev) => {
- const current = prev[activeTab].attendanceStatuses || [];
- const updated = current.includes(status) ? current.filter((s) => s !== status) : [...current, status];
- return {
- ...prev,
- [activeTab]: {
- ...prev[activeTab],
- attendanceStatuses: updated,
- },
- };
- });
- };
-
- // Assigns용 필터 핸들러
- const handleSubmitFilterChange = (isSubmit: boolean) => {
- setFilters((prev) => {
- const current = prev[activeTab].submitStatuses || [];
- const updated = current.includes(isSubmit) ? current.filter((s) => s !== isSubmit) : [...current, isSubmit];
- return {
- ...prev,
- [activeTab]: {
- ...prev[activeTab],
- submitStatuses: updated,
- },
- };
- });
- };
-
- // CourseTitle 필터 핸들러
- const handleCourseTitleChange = (courseTitle: string) => {
- setFilters((prev) => {
- const current = prev[activeTab].courseTitles;
- const updated = current.includes(courseTitle)
- ? current.filter((title) => title !== courseTitle)
- : [...current, courseTitle];
- return {
- ...prev,
- [activeTab]: {
- ...prev[activeTab],
- courseTitles: updated,
- },
- };
- });
- };
-
- const isFilterSet = useMemo(() => {
- const currentFilters = filters[activeTab];
- const { courseTitles, attendanceStatuses, submitStatuses } = currentFilters;
- return (
- (courseTitles && courseTitles.length > 0) ||
- (attendanceStatuses && attendanceStatuses.length > 0) ||
- (submitStatuses && submitStatuses.length > 0)
- );
- }, [filters, activeTab]);
-
- const clearFilters = () => {
- setFilters((prev) => ({
- ...prev,
- [activeTab]: {
- courseTitles: [],
- ...(activeTab === 'VIDEO' ? { attendanceStatuses: [] } : {}),
- ...(activeTab === 'ASSIGN' ? { submitStatuses: [] } : {}),
- },
- }));
- };
-
- return (
- <>
- {}} />
-
-
-
- {isOpen ? (
- {
- setIsOpen(!isOpen);
- e.preventDefault();
- }}
- draggable={false}
- className="rounded-2xl w-32 h-32 shadow-2xl shadow-zinc-900 cursor-pointer"
- alt="Close"
- />
- ) : (
- {
- setIsOpen(!isOpen);
- e.preventDefault();
- }}
- draggable={false}
- className="rounded-2xl w-32 h-32 shadow-2xl shadow-zinc-900 cursor-pointer"
- alt="Open"
- />
- )}
-
-
-
-
-
-
- {activeTab === 'VIDEO'
- ? '온라인 강의 목록'
- : activeTab === 'ASSIGN'
- ? '과제 목록'
- : activeTab === 'QUIZ'
- ? '퀴즈 목록'
- : '오류'}
-
-
- = 30
- ? 'text-amber-500 font-semibold'
- : 'text-zinc-400'
- : Math.floor(remainingTime / 60) >= 1
- ? 'text-amber-500 font-semibold'
- : 'text-zinc-400'
- }`}
- >
- {remainingTime < 60
- ? `${Math.round(remainingTime)}분 전`
- : `${Math.floor(remainingTime / 60)}시간 전`}
-
- {
- if (isPending || remainingTime <= 1) {
- return;
- }
- setIsPending(true);
- updateData();
- }}
- >
-
-
-
-
-
-
- setSearchTerm(e.target.value)}
- autoFocus={true}
- className="bg-zinc-50 rounded-xl border border-zinc-300 w-full text-lg h-12 pl-12 pr-4 placeholder-gray-400 font-medium py-0 outline-none focus:ring-0 focus:border-zinc-300 focus:bg-slate-50 transition-all duration-200"
- />
-
-
-
- {filters[activeTab].courseTitles.map((title) => (
- handleCourseTitleChange(title)} />
- ))}
- {activeTab === 'VIDEO' &&
- filters[activeTab].attendanceStatuses &&
- filters[activeTab].attendanceStatuses.map((status) => (
- handleAttendanceFilterChange(status)}
- />
- ))}
- {activeTab === 'ASSIGN' &&
- filters[activeTab].submitStatuses &&
- filters[activeTab].submitStatuses.map((status) => (
- handleSubmitFilterChange(status)}
- />
- ))}
-
-
- {/* 고정된 필터 아이콘 영역 */}
-
-
-
- setIsFilterOpen((prev) => !prev)}
- className="flex justify-self-end rounded-lg gap-1 bg-white hover:bg-zinc-100 transition-all duration-200 mb-2 mr-5 ml-2 p-2"
- >
- {isFilterSet ? (
-
- ) : (
-
- )}
-
-
-
-
- {
- clearFilters();
- }}
- >
- 모두 지우기
-
- setIsFilterOpen(false)}
- >
- 닫기
-
-
-
-
-
-
-
- {isPending ? (
-
-
-
- ) : (
- <>
- {isError ? (
-
-
-
오류가 발생했습니다.
-
{
- location.reload();
- }}
- className="py-4 text-xl font-medium underline text-zinc-500 hover:text-zinc-950 hover:cursor-pointer transition-all duration-200"
- >
- 페이지 새로고침
-
-
- ) : (
- <>
- {activeTab === 'VIDEO' &&
}
- {activeTab === 'ASSIGN' &&
}
- {activeTab === 'QUIZ' &&
}
- >
- )}
- >
- )}
-
-
-
-
- >
- );
-}
diff --git a/src/content/components/Assignment.tsx b/src/content/components/Assignment.tsx
deleted file mode 100644
index 99a6589..0000000
--- a/src/content/components/Assignment.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { calculateDueDate, calculateRemainingTime } from '@/lib/utils';
-import { Card, CardFooter, CardHeader } from '@/components/ui/card';
-import { BadgeCheck, Clock, Siren, TriangleAlert } from 'lucide-react';
-import { Tooltip } from '@radix-ui/react-tooltip';
-import { TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import thung from '@/assets/thung.png';
-import { Assign } from '../types';
-
-interface Props {
- courseData: Assign[];
-}
-export default function Assignment({ courseData }: Props) {
- if (!courseData || courseData.length === 0) {
- return (
-
-
-
- 과제가 없습니다
-
-
- );
- }
-
- return (
-
- {courseData.map((course, index) => {
- if (!course) return null;
-
- const isDueDateSame = true;
- const timeDifference = calculateDueDate(course.dueDate!);
-
- return (
-
window.open(`${course.url}`, '_blank')}
- key={`${course.title}-${index}`}
- className={`cursor-pointer w-full rounded-2xl shadow-md bg-white overflow-hidden border-0 border-l-4 ${course.isSubmit ? 'border-green-500' : timeDifference.borderColor} hover:bg-zinc-100 transition-all duration-200`}
- >
-
-
-
{course.courseTitle}
-
{course.title}
-
-
-
-
-
-
-
-
- {isDueDateSame ? timeDifference.message : '확인 필요'}
-
-
-
-
- {calculateRemainingTime(course.dueDate)}
-
-
-
- {course.isSubmit ? (
-
- ) : timeDifference.textColor.includes('red') ? (
-
- ) : (
-
- )}
-
{course.isSubmit ? '제출 완료' : '제출 필요'}
-
-
-
- );
- })}
-
- );
-}
diff --git a/src/content/components/PendingDialog.tsx b/src/content/components/PendingDialog.tsx
deleted file mode 100644
index 4a2f43f..0000000
--- a/src/content/components/PendingDialog.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useState, useEffect } from 'react';
-import ReactDOM from 'react-dom';
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
-import { Button } from '@/components/ui/button';
-import { Loader2 } from 'lucide-react';
-import styles from '@/styles/shadow.css?inline';
-import { createShadowRoot } from '@/lib/createShadowRoot';
-
-interface PendingDialogProps {
- isPending: boolean;
- onClose?: () => void;
-}
-
-export default function PendingDialog({ isPending, onClose }: PendingDialogProps) {
- const [open, setOpen] = useState(false);
- const [cancelEnabled, setCancelEnabled] = useState(false);
- const [modalContainer, setModalContainer] = useState(null);
- const [hostElement, setHostElement] = useState(null);
-
- useEffect(() => {
- let host = document.getElementById('shadow-modal-host') as HTMLElement | null;
- if (!host) {
- host = document.createElement('div');
- host.id = 'shadow-modal-host';
- host.style.zIndex = '9999';
- host.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
- document.body.prepend(host);
- }
- setHostElement(host);
- const newShadowRoot = createShadowRoot(
- host,
- [
- styles,
- `
- :host {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 9999;
- }
- `,
- ],
- 'modal-container'
- );
- setModalContainer(newShadowRoot);
- }, []);
-
- // open 상태에 따라 host의 display 업데이트
- useEffect(() => {
- if (hostElement) {
- hostElement.style.display = open ? 'flex' : 'none';
- }
- }, [open, hostElement]);
-
- // isPending 상태에 따라 모달 열림/닫힘 관리
- useEffect(() => {
- if (isPending) {
- setOpen(true);
- setCancelEnabled(false);
- } else {
- setOpen(false);
- setCancelEnabled(false);
- }
- }, [isPending]);
-
- // 모달이 열리면 스크롤을 막고, 닫히면 복구
- useEffect(() => {
- if (open) {
- document.body.style.overflow = 'hidden';
- } else {
- document.body.style.overflow = '';
- }
- }, [open]);
-
- // 모달이 열릴 때 15초 후에 취소 버튼 활성화
- useEffect(() => {
- let timer: NodeJS.Timeout;
- if (open) {
- timer = setTimeout(() => {
- setCancelEnabled(true);
- }, 15000);
- }
- return () => {
- clearTimeout(timer);
- };
- }, [open]);
-
- const handleClose = () => {
- // 취소 버튼이 활성화된 경우에만 닫힘 동작 실행
- if (cancelEnabled) {
- setOpen(false);
- if (onClose) {
- onClose();
- }
- }
- };
-
- const modalContent = (
-
-
- 요청 진행 중
- 요청이 진행중입니다. 잠시만 기다려주세요.
-
-
-
-
- 취소
-
-
-
- );
-
- if (!modalContainer) return null;
- return ReactDOM.createPortal(modalContent, modalContainer);
-}
diff --git a/src/content/components/PopoverFooter.tsx b/src/content/components/PopoverFooter.tsx
deleted file mode 100644
index 5ee1908..0000000
--- a/src/content/components/PopoverFooter.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { NotebookText, VideoIcon, Zap } from 'lucide-react';
-import { TAB_TYPE } from '../types';
-
-interface Prop {
- activeTab: string;
- setActiveTab: (tabType: TAB_TYPE) => void;
-}
-
-export default function PopoverFooter({ activeTab, setActiveTab }: Prop) {
- return (
-
-
setActiveTab(TAB_TYPE.VIDEO)}
- >
-
- 강의
-
-
setActiveTab(TAB_TYPE.ASSIGN)}
- >
-
- 과제
-
-
setActiveTab(TAB_TYPE.QUIZ)}
- >
-
- 퀴즈
-
-
- );
-}
diff --git a/src/content/components/PopoverHeader.tsx b/src/content/components/PopoverHeader.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/src/content/components/QuizTab.tsx b/src/content/components/QuizTab.tsx
deleted file mode 100644
index a420bb3..0000000
--- a/src/content/components/QuizTab.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { calculateDueDate, calculateRemainingTime } from '@/lib/utils';
-import { Quiz } from '../types';
-import { Card, CardFooter, CardHeader } from '@/components/ui/card';
-import { Clock, Siren, TriangleAlert } from 'lucide-react';
-import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import thung from '@/assets/thung.png';
-
-interface Props {
- courseData: Quiz[];
-}
-
-export default function QuizTab({ courseData }: Props) {
- if (!courseData || courseData.length === 0) {
- return (
-
-
-
- 퀴즈가 없습니다
-
-
- );
- }
-
- return (
-
- {courseData.map((course, index) => {
- if (!course) return null;
-
- const isDueDateSame = true;
- const timeDifference = calculateDueDate(course.dueDate!);
-
- return (
-
window.open(`${course.url}`, '_blank')}
- key={`${course.title}-${index}`}
- className={`cursor-pointer w-full rounded-2xl shadow-md bg-white overflow-hidden border-0 border-l-4 ${timeDifference.borderColor} hover:bg-zinc-100 transition-all duration-200`}
- >
-
-
-
{course.courseTitle}
-
{course.title}
-
-
-
-
-
-
-
-
- {isDueDateSame ? timeDifference.message : '확인 필요'}
-
-
-
-
- {calculateRemainingTime(course.dueDate)}
-
-
-
-
- {timeDifference.textColor.includes('red') ? (
-
- ) : (
-
- )}
-
-
{'직접 확인'}
-
-
-
- );
- })}
-
- );
-}
diff --git a/src/content/components/Video.tsx b/src/content/components/Video.tsx
deleted file mode 100644
index 685e4bf..0000000
--- a/src/content/components/Video.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import { useState } from 'react';
-import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card';
-import { Vod } from '../types';
-import {
- calculateRemainingTimeByRange,
- calculateTimeDifference,
- formatDateString,
- isCurrentDateInRange,
-} from '@/lib/utils';
-import { BadgeCheck, ChevronDown, ChevronUp, Clock, Siren, TriangleAlert } from 'lucide-react';
-
-import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-
-import thung from '@/assets/thung.png';
-
-interface Props {
- courseData: Vod[];
-}
-
-export default function Video({ courseData }: Props) {
- const [expandedCards, setExpandedCards] = useState<{ [key: string]: boolean }>({});
-
- const toggleCard = (courseId: string) => {
- setExpandedCards((prev) => ({ ...prev, [courseId]: !prev[courseId] }));
- };
-
- const groupedData = courseData.reduce(
- (acc, item) => {
- const key = `${item.courseId}-${item.subject}-${item.range}`;
- if (!acc[key]) {
- acc[key] = [];
- }
- acc[key].push(item);
- return acc;
- },
- {} as Record
- );
-
- if (!courseData || courseData.length === 0) {
- return (
-
-
-
- 강의가 없습니다
-
-
- );
- }
-
- const sortedVodGroups = Object.values(groupedData).sort((groupA, groupB) => {
- const firstA = groupA[0];
- const firstB = groupB[0];
-
- const isAX = firstA.weeklyAttendance.toUpperCase().startsWith('X');
- const isBX = firstB.weeklyAttendance.toUpperCase().startsWith('X');
-
- // X가 있는 항목을 먼저 정렬
- if (isAX && !isBX) return -1;
- if (!isAX && isBX) return 1;
-
- const rangeA = firstA.range;
- const rangeB = firstB.range;
- const isRangeANull = rangeA === null;
- const isRangeBNull = rangeB === null;
-
- // isCurrentDateInRange가 true인 항목을 먼저 정렬 (X와 O 모두)
- const isCurrentDateInRangeA = isCurrentDateInRange(firstA.range);
- const isCurrentDateInRangeB = isCurrentDateInRange(firstB.range);
-
- if (isAX) {
- // X일 때는 isCurrentDateInRange가 true인 항목을 먼저 배치, 그 다음 null
- if (isCurrentDateInRangeA && !isCurrentDateInRangeB) return -1;
- if (!isCurrentDateInRangeA && isCurrentDateInRangeB) return 1;
- if (isRangeANull && !isRangeBNull) return 1;
- if (!isRangeANull && isRangeBNull) return -1;
- } else {
- // O일 때는 isCurrentDateInRange가 true인 항목을 먼저 배치, 그 다음 null, 그 다음 시간순 정렬
- if (isCurrentDateInRangeA && !isCurrentDateInRangeB) return -1;
- if (!isCurrentDateInRangeA && isCurrentDateInRangeB) return 1;
- if (isRangeANull && !isRangeBNull) return 1;
- if (!isRangeANull && isRangeBNull) return -1;
-
- // rangeStart 날짜 기준으로 시간순으로 정렬
- if (!isRangeANull && !isRangeBNull) {
- const rangeStartA = rangeA.split(' ~ ')[0];
- const rangeStartB = rangeB.split(' ~ ')[0];
- const dateA = new Date(rangeStartA);
- const dateB = new Date(rangeStartB);
-
- if (dateA < dateB) return -1;
- if (dateA > dateB) return 1;
- }
- }
-
- // courseTitle로 기본 정렬
- if (firstA.courseTitle < firstB.courseTitle) return -1;
- if (firstA.courseTitle > firstB.courseTitle) return 1;
-
- return 0;
- });
-
- return (
-
- {sortedVodGroups.map((vods, index) => {
- if (!vods || vods.length === 0) return null;
-
- const sortedVods = vods.slice().sort((a, b) => {
- const isAX = a.isAttendance.toUpperCase().startsWith('X');
- const isBX = b.isAttendance.toUpperCase().startsWith('X');
- if (isAX && !isBX) return -1;
- if (!isAX && isBX) return 1;
-
- if (a.range && b.range) {
- const rangeStartA = a.range.split(' ~ ')[0];
- const rangeStartB = b.range.split(' ~ ')[0];
- const dateA = new Date(rangeStartA);
- const dateB = new Date(rangeStartB);
- if (dateA < dateB) return -1;
- if (dateA > dateB) return 1;
- }
-
- if (a.courseTitle < b.courseTitle) return -1;
- if (a.courseTitle > b.courseTitle) return 1;
-
- return 0;
- });
-
- const item = vods[0];
- const isDueDateSame = true;
- const timeDifference = calculateTimeDifference(item.range);
- const isExpanded = expandedCards[`${item.title}-${index}`] || false;
-
- return (
-
- toggleCard(`${item.title}-${index}`)}
- >
-
-
{item.courseTitle}
-
{item.subject}
-
- {isExpanded ? : }
-
- {isExpanded && (
-
- {sortedVods.map((vod, vodIndex) => {
- return (
- window.open(`${vod.url.replace('view', 'viewer')}`, '_blank', 'VodContentWindow')}
- >
-
- {vod.title}
-
-
- {formatDateString(vod.range)},{' '}
-
- {vod.length}
-
-
-
- );
- })}
-
- )}
-
-
-
-
-
-
- {isDueDateSame ? timeDifference.message : '확인 필요'}
-
-
-
-
- {calculateRemainingTimeByRange(vods[0].range)}
-
-
-
-
- {item.weeklyAttendance.toLocaleLowerCase().trim() === 'o' ? (
-
- ) : timeDifference.message.includes('시간') ? (
-
- ) : (
-
- )}
-
-
- {item.weeklyAttendance.toLocaleLowerCase().trim() === 'o' ? '출석' : '결석'}
-
-
-
-
- );
- })}
-
- );
-}
diff --git a/src/hooks/useCalendarEvents.ts b/src/hooks/useCalendarEvents.ts
index cc5f1ac..60ad11f 100644
--- a/src/hooks/useCalendarEvents.ts
+++ b/src/hooks/useCalendarEvents.ts
@@ -1,100 +1,30 @@
import { useState, useEffect } from 'react';
-import { startOfDay } from 'date-fns';
-import { loadDataFromStorage } from '@/lib/storage';
-import { removeSquareBrackets } from '@/lib/utils';
-import { Vod, Assign, Quiz } from '@/content/types';
+import { loadAndTransform } from '@/lib/storage';
+import { Vod, Assign, Quiz } from '@/types';
+import { CalendarEvent, vodGroupsToEvents, dueDateItemToEvent } from '@/lib/transformCalendarEvents';
-export type CalendarEvent = {
- id: string;
- type: 'vod' | 'assign' | 'quiz';
- title: string;
- subject: string;
- start: Date | null;
- end: Date | null;
-};
+export type { CalendarEvent };
function useCalendarEvents() {
const [events, setEvents] = useState([]);
- const loadEvents = (storageKey: string, transform: (data: T[]) => CalendarEvent[]) => {
- loadDataFromStorage(storageKey, (data: string | null) => {
- if (!data) return;
-
- let parsedData: T[];
- if (typeof data === 'string') {
- try {
- parsedData = JSON.parse(data);
- } catch (error) {
- console.error(`JSON 파싱 에러 (${storageKey}):`, error);
- return;
- }
- } else {
- parsedData = data;
- }
-
- const eventsData = transform(parsedData);
- setEvents((prev) => [...prev, ...eventsData]);
- });
- };
-
useEffect(() => {
- loadEvents('vod', (vods) => {
- const groupedData = vods.reduce(
- (acc, item) => {
- const key = `${item.courseId}-${item.subject}-${item.range}`;
- if (!acc[key]) {
- acc[key] = [];
- }
- acc[key].push(item);
- return acc;
- },
- {} as Record
- );
+ const appendEvents = (newEvents: CalendarEvent[]) => {
+ setEvents((prev) => [...prev, ...newEvents]);
+ };
- return Object.entries(groupedData).map(([key, vodItems]) => {
- const range = vodItems[0].range;
- const [start, end] = range ? range.split(' ~ ') : [null, null];
- return {
- id: key,
- type: 'vod',
- start: start ? new Date(start.replace(/-/g, '/')) : null,
- end: end ? new Date(end.replace(/-/g, '/')) : null,
- title: removeSquareBrackets(vodItems[0].courseTitle),
- subject: removeSquareBrackets(vodItems[0].subject),
- };
- });
- });
+ loadAndTransform('vod', vodGroupsToEvents, appendEvents);
- // assign 데이터 로딩 및 변환
- loadEvents('assign', (assigns) =>
- assigns.map((assign) => {
- const dueDate = assign.dueDate;
- const normalizedDate = dueDate ? startOfDay(new Date(dueDate)) : null;
- return {
- id: assign.courseId + assign.title + assign.dueDate,
- type: 'assign',
- start: normalizedDate,
- end: normalizedDate,
- title: removeSquareBrackets(assign.courseTitle),
- subject: removeSquareBrackets(assign.title),
- };
- })
+ loadAndTransform(
+ 'assign',
+ (assigns) => assigns.map((a) => dueDateItemToEvent(a, 'assign')),
+ appendEvents,
);
- // quiz 데이터 로딩 및 변환
- loadEvents('quiz', (quizzes) =>
- quizzes.map((quiz) => {
- const dueDate = quiz.dueDate;
- const normalizedDate = dueDate ? startOfDay(new Date(dueDate)) : null;
- return {
- id: quiz.courseId + quiz.title + quiz.dueDate,
- type: 'quiz',
- start: normalizedDate,
- end: normalizedDate,
- title: removeSquareBrackets(quiz.courseTitle),
- subject: removeSquareBrackets(quiz.title),
- };
- })
+ loadAndTransform(
+ 'quiz',
+ (quizzes) => quizzes.map((q) => dueDateItemToEvent(q, 'quiz')),
+ appendEvents,
);
}, []);
diff --git a/src/hooks/useCardData.ts b/src/hooks/useCardData.ts
index 53cf9db..693dc22 100644
--- a/src/hooks/useCardData.ts
+++ b/src/hooks/useCardData.ts
@@ -1,114 +1,36 @@
import { useState, useEffect } from 'react';
-import { loadDataFromStorage } from '@/lib/storage';
-import { Vod, Assign, Quiz } from '@/content/types';
+import { loadAndTransform } from '@/lib/storage';
+import { Vod, Assign, Quiz } from '@/types';
+import { CardData, summarizeVods } from '@/lib/summarizeCourseData';
-export type CardData = {
- type: 'vod' | 'assign' | 'quiz';
- done: number;
- total: number;
-};
+export type { CardData };
-function useCardData() {
- const [vodSummary, setVodSummary] = useState([]);
- const [assignSummary, setAssignSummary] = useState([]);
- const [quizSummary, setQuizSummary] = useState([]);
-
- const loadEvents = (
- storageKey: string,
- transform: (data: T[]) => CardData[],
- setter: (data: CardData[]) => void
- ) => {
- loadDataFromStorage(storageKey, (data: string | null) => {
- if (!data) return;
+type Summaries = Record<'vod' | 'assign' | 'quiz', CardData>;
- let parsedData: T[];
- if (typeof data === 'string') {
- try {
- parsedData = JSON.parse(data);
- } catch (error) {
- console.error(`JSON 파싱 에러 (${storageKey}):`, error);
- return;
- }
- } else {
- parsedData = data;
- }
+const EMPTY: CardData = { done: 0, total: 0 };
- const eventsData = transform(parsedData);
- setter(eventsData);
- });
+function useCardData() {
+ const [summaries, setSummaries] = useState({ vod: EMPTY, assign: EMPTY, quiz: EMPTY });
+ const updateKey = (key: keyof Summaries) => (data: CardData) => {
+ setSummaries((prev) => ({ ...prev, [key]: data }));
};
useEffect(() => {
- loadEvents(
- 'vod',
- (vods) => {
- const groupedData = vods.reduce(
- (acc, item) => {
- const key = `${item.courseId}-${item.subject}-${item.range}`;
- if (!acc[key]) {
- acc[key] = [];
- }
- acc[key].push(item);
- return acc;
- },
- {} as Record
- );
-
- let done = 0;
- Object.values(groupedData).forEach((vodItems) => {
- if (vodItems[0].weeklyAttendance.toLowerCase() === 'o') {
- done += 1;
- }
- });
-
- return [
- {
- type: 'vod',
- done,
- total: Object.keys(groupedData).length,
- },
- ];
- },
- setVodSummary
- );
-
- loadEvents(
- 'assign',
- (assigns) => {
- const total = assigns.length;
- let done = 0;
- assigns.forEach((assign) => {
- if (assign.isSubmit) done += 1;
- });
- return [
- {
- type: 'assign',
- done,
- total,
- },
- ];
- },
- setAssignSummary
- );
-
- loadEvents(
- 'quiz',
- (quizzes) => {
- const total = quizzes.length;
- const done = 0;
- return [
- {
- type: 'quiz',
- done,
- total,
- },
- ];
- },
- setQuizSummary
- );
+ loadAndTransform('vod', summarizeVods, updateKey('vod'));
+
+ loadAndTransform('assign', (assigns) => ({
+ done: assigns.filter((a) => a.isSubmit).length,
+ total: assigns.length,
+ }), updateKey('assign'));
+
+ // QuizData에 완료 여부 필드가 없어 done은 항상 0
+ loadAndTransform('quiz', (quizzes) => ({
+ done: 0,
+ total: quizzes.length,
+ }), updateKey('quiz'));
}, []);
- return { vodSummary, assignSummary, quizSummary };
+ return summaries;
}
export default useCardData;
diff --git a/src/hooks/useCourseData.tsx b/src/hooks/useCourseData.tsx
index 546cf64..0932043 100644
--- a/src/hooks/useCourseData.tsx
+++ b/src/hooks/useCourseData.tsx
@@ -1,196 +1,295 @@
-import { useState, useEffect, useCallback } from 'react';
-import { Vod, Assign, Quiz, CourseBase } from '@/content/types';
+import { useState, useEffect, useCallback, useRef } from 'react';
+import { Vod, Assign, Quiz, CourseBase } from '@/types';
import { loadDataFromStorage, saveDataToStorage } from '@/lib/storage';
-import { requestData } from '@/lib/fetchCourseData';
+import { scrapeCourseData } from '@/lib/fetchCourseData';
import { isCurrentDateByDate, isCurrentDateInRange } from '@/lib/utils';
-import { makeAssignKey, makeQuizKey, makeVodKey } from '@/utils/generate-key';
+import { makeItemKey, makeVodKey } from '@/lib/generateKey';
+import { mergeVodWithAttendance, mergeDueDateItems } from '@/lib/transformCourseData';
+import { deduplicateInto } from '@/lib/deduplicateInto';
+import { loadMockData } from '@/mocks/loadMockData';
+import {
+ REFRESH_INTERVAL_MS,
+ CACHE_TTL_MINUTES,
+ CACHE_TTL_MS,
+ getLastRequestTime,
+ setLastRequestTime,
+ clearLastRequestTime,
+} from '@/lib/cache';
+
+const skipDateFilter = !!import.meta.env.VITE_MOCK_SKIP_DATE_FILTER;
+
+function filterVodsForDisplay(vods: Vod[]): Vod[] {
+ return skipDateFilter ? vods : vods.filter((v) => isCurrentDateInRange(v.range));
+}
+
+function filterItemsForDisplay(items: T[]): T[] {
+ return skipDateFilter ? items : items.filter((item) => item.dueDate === null || isCurrentDateByDate(item.dueDate));
+}
export function useCourseData(courses: CourseBase[]) {
const [vods, setVods] = useState([]);
const [assigns, setAssigns] = useState([]);
- const [quizes, setQuizes] = useState([]);
+ const [quizzes, setQuizzes] = useState([]);
+ const quizzesRef = useRef(quizzes);
+ quizzesRef.current = quizzes;
const [refreshTime, setRefreshTime] = useState(null);
- const [isPending, setIsPending] = useState(false);
+ const [isPending, setIsPending] = useState(false);
const [remainingTime, setRemainingTime] = useState(0);
const [isError, setIsError] = useState(false);
+ const [pendingCourseIds, setPendingCourseIds] = useState>(new Set());
+
+ const useMockData = import.meta.env.VITE_MOCK && !import.meta.env.VITE_MOCK_COURSES;
+
+ const applyMock = useCallback(async () => {
+ const mock = await loadMockData();
+ setVods(mock.vods);
+ setAssigns(mock.assigns);
+ setQuizzes(mock.quizzes);
+ }, []);
- // dev 모드: mock 데이터 로드
useEffect(() => {
- if (!import.meta.env.VITE_MOCK) return;
+ if (!useMockData) return;
let cancelled = false;
- import('@/mocks/mockData').then(({ mockVods, mockAssigns, mockQuizes }) => {
+ applyMock().then(() => {
if (cancelled) return;
- setVods(mockVods);
- setAssigns(mockAssigns);
- setQuizes(mockQuizes);
setRefreshTime(new Date().toLocaleTimeString());
setRemainingTime(5);
});
return () => {
cancelled = true;
};
- }, []);
+ }, [applyMock, useMockData]);
- // dev 모드에서는 실제 fetch를 하지 않음
- const updateData = useCallback(async () => {
- if (import.meta.env.VITE_MOCK) {
- const { mockVods, mockAssigns, mockQuizes } = await import('@/mocks/mockData');
- setVods(mockVods);
- setAssigns(mockAssigns);
- setQuizes(mockQuizes);
+ const refreshCourseData = useCallback(async () => {
+ if (useMockData) {
+ await applyMock();
setRefreshTime(new Date().toLocaleTimeString());
setRemainingTime(0);
+ setIsPending(false);
return;
}
+
try {
setIsError(false);
setIsPending(true);
- const currentTime = new Date().getTime();
+ const fetchedAt = Date.now();
- const tempVods: Vod[] = [];
- const tempAssigns: Assign[] = [];
- const tempQuizes: Quiz[] = [];
+ const vods: Vod[] = [];
+ const assigns: Assign[] = [];
+ const quizzes: Quiz[] = [];
+ const seenVods = new Set();
+ const seenAssigns = new Set();
+ const seenQuizzes = new Set();
- const vodSet = new Set(tempVods.map((v) => makeVodKey(v.courseId, v.title, v.week)));
- const assignSet = new Set(tempAssigns.map((a) => makeAssignKey(a.courseId, a.title, a.dueDate ? a.dueDate : '')));
- const quizSet = new Set(tempQuizes.map((q) => makeQuizKey(q.courseId, q.title, q.dueDate ? q.dueDate : '')));
+ // 현재 상태의 퀴즈 데이터를 courseId별로 그룹핑 (마감된 퀴즈 캐시용)
+ const cachedQuizMap = new Map();
+ for (const q of quizzesRef.current) {
+ const arr = cachedQuizMap.get(q.courseId) ?? [];
+ arr.push(q);
+ cachedQuizMap.set(q.courseId, arr);
+ }
await Promise.all(
courses.map(async (course) => {
- const result = await requestData(course.courseId);
-
- result.vodDataArray.forEach((vodData) => {
- result.vodAttendanceArray.forEach((vodAttendanceData) => {
- const vodKey = makeVodKey(course.courseId, vodData.title, vodData.week);
- if (
- vodAttendanceData.title === vodData.title &&
- vodAttendanceData.week === vodData.week &&
- isCurrentDateInRange(vodData.range)
- ) {
- if (!vodSet.has(vodKey)) {
- vodSet.add(vodKey);
- tempVods.push({
- courseId: course.courseId,
- prof: course.prof,
- courseTitle: course.courseTitle,
- week: vodAttendanceData.week,
- title: vodData.title,
- isAttendance: vodAttendanceData.isAttendance,
- weeklyAttendance: vodAttendanceData.weeklyAttendance,
- length: vodData.length,
- range: vodData.range,
- subject: vodData.subject,
- url: vodData.url,
- });
- }
- }
- });
- });
-
- result.assignDataArray.forEach((assignData) => {
- const assignKey = makeAssignKey(
- course.courseId,
- assignData.title,
- assignData.dueDate ? assignData.dueDate : ''
- );
- if (!assignSet.has(assignKey) && isCurrentDateByDate(assignData.dueDate)) {
- console.info(assignKey);
- assignSet.add(assignKey);
- tempAssigns.push({
- courseId: course.courseId,
- prof: course.prof,
- courseTitle: course.courseTitle,
- subject: assignData.subject,
- title: assignData.title,
- dueDate: assignData.dueDate,
- isSubmit: assignData.isSubmit,
- url: assignData.url,
- });
- }
- });
-
- result.quizDataArray.forEach((quizData) => {
- const quizKey = makeQuizKey(course.courseId, quizData.title, quizData.dueDate ? quizData.dueDate : '');
- if (!quizSet.has(quizKey) && isCurrentDateByDate(quizData.dueDate)) {
- console.info(quizKey);
- quizSet.add(quizKey);
- tempQuizes.push({
- courseId: course.courseId,
- prof: course.prof,
- courseTitle: course.courseTitle,
- subject: quizData.subject,
- title: quizData.title,
- dueDate: quizData.dueDate,
- url: quizData.url,
- });
- }
- });
+ const scraped = await scrapeCourseData(course.courseId, cachedQuizMap.get(course.courseId), course.isCommunity);
+
+ deduplicateInto(
+ vods,
+ mergeVodWithAttendance(course, scraped.vodDataArray, scraped.vodAttendanceArray),
+ seenVods,
+ (v) => makeVodKey(v.courseId, v.title, v.week),
+ );
+ deduplicateInto(
+ assigns,
+ mergeDueDateItems(course, scraped.assignDataArray),
+ seenAssigns,
+ (a) => makeItemKey(a.courseId, a.title, a.dueDate ?? ''),
+ );
+ deduplicateInto(
+ quizzes,
+ mergeDueDateItems(course, scraped.quizDataArray),
+ seenQuizzes,
+ (q) => makeItemKey(q.courseId, q.title, q.dueDate ?? ''),
+ );
})
);
- setVods(tempVods);
- setAssigns(tempAssigns);
- setQuizes(tempQuizes);
+ // Storage: 전체 데이터 저장 (강의 페이지 등에서 사용)
+ saveDataToStorage('vod', vods);
+ saveDataToStorage('assign', assigns);
+ saveDataToStorage('quiz', quizzes);
- saveDataToStorage('vod', tempVods);
- saveDataToStorage('assign', tempAssigns);
- saveDataToStorage('quiz', tempQuizes);
+ // State: 대시보드 표시용 날짜 필터링 적용
+ setVods(filterVodsForDisplay(vods));
+ setAssigns(filterItemsForDisplay(assigns));
+ setQuizzes(filterItemsForDisplay(quizzes));
- setRefreshTime(new Date(currentTime).toLocaleTimeString());
+ setRefreshTime(new Date(fetchedAt).toLocaleTimeString());
setRemainingTime(0);
- localStorage.setItem('lastRequestTime', currentTime.toString());
- saveDataToStorage('lastRequestTime', currentTime.toString());
-
- setIsPending(false);
+ setLastRequestTime(fetchedAt);
+ saveDataToStorage('lastRequestTime', fetchedAt.toString());
} catch (error) {
console.warn(error);
- localStorage.removeItem('lastRequestTime');
+ clearLastRequestTime();
setIsError(true);
+ } finally {
setIsPending(false);
}
- }, [courses]);
+ }, [courses, applyMock, useMockData]);
+
+ const addCourseData = useCallback(async (course: CourseBase) => {
+ setPendingCourseIds((prev) => new Set(prev).add(course.courseId));
+ try {
+ const scraped = await scrapeCourseData(course.courseId, undefined, course.isCommunity);
+
+ const newVods = mergeVodWithAttendance(course, scraped.vodDataArray, scraped.vodAttendanceArray);
+ const newAssigns = mergeDueDateItems(course, scraped.assignDataArray);
+ const newQuizzes = mergeDueDateItems(course, scraped.quizDataArray);
+
+ // Storage: 전체 데이터 저장
+ loadDataFromStorage('vod', (stored) => {
+ const all = [...(stored ?? [])];
+ const seen = new Set(all.map((v) => makeVodKey(v.courseId, v.title, v.week)));
+ deduplicateInto(all, newVods, seen, (v) => makeVodKey(v.courseId, v.title, v.week));
+ saveDataToStorage('vod', all);
+ });
+ loadDataFromStorage('assign', (stored) => {
+ const all = [...(stored ?? [])];
+ const seen = new Set(all.map((a) => makeItemKey(a.courseId, a.title, a.dueDate ?? '')));
+ deduplicateInto(all, newAssigns, seen, (a) => makeItemKey(a.courseId, a.title, a.dueDate ?? ''));
+ saveDataToStorage('assign', all);
+ });
+ loadDataFromStorage('quiz', (stored) => {
+ const all = [...(stored ?? [])];
+ const seen = new Set(all.map((q) => makeItemKey(q.courseId, q.title, q.dueDate ?? '')));
+ deduplicateInto(all, newQuizzes, seen, (q) => makeItemKey(q.courseId, q.title, q.dueDate ?? ''));
+ saveDataToStorage('quiz', all);
+ });
+
+ // State: 대시보드 표시용 필터링된 데이터만
+ const filteredNewVods = filterVodsForDisplay(newVods);
+ const filteredNewAssigns = filterItemsForDisplay(newAssigns);
+ const filteredNewQuizzes = filterItemsForDisplay(newQuizzes);
+
+ setVods((prev) => {
+ const seen = new Set(prev.map((v) => makeVodKey(v.courseId, v.title, v.week)));
+ const merged = [...prev];
+ deduplicateInto(merged, filteredNewVods, seen, (v) => makeVodKey(v.courseId, v.title, v.week));
+ return merged;
+ });
+
+ setAssigns((prev) => {
+ const seen = new Set(prev.map((a) => makeItemKey(a.courseId, a.title, a.dueDate ?? '')));
+ const merged = [...prev];
+ deduplicateInto(merged, filteredNewAssigns, seen, (a) => makeItemKey(a.courseId, a.title, a.dueDate ?? ''));
+ return merged;
+ });
+
+ setQuizzes((prev) => {
+ const seen = new Set(prev.map((q) => makeItemKey(q.courseId, q.title, q.dueDate ?? '')));
+ const merged = [...prev];
+ deduplicateInto(merged, filteredNewQuizzes, seen, (q) => makeItemKey(q.courseId, q.title, q.dueDate ?? ''));
+ return merged;
+ });
+ } catch (error) {
+ console.warn('[Dotbugi] 강의 추가 데이터 fetch 실패:', error);
+ } finally {
+ setPendingCourseIds((prev) => {
+ const next = new Set(prev);
+ next.delete(course.courseId);
+ return next;
+ });
+ }
+ }, []);
+
+ const removeCourseData = useCallback((courseId: string) => {
+ // Storage: 전체 데이터에서 해당 강의 제거
+ loadDataFromStorage('vod', (stored) => {
+ if (stored) saveDataToStorage('vod', stored.filter((v) => v.courseId !== courseId));
+ });
+ loadDataFromStorage('assign', (stored) => {
+ if (stored) saveDataToStorage('assign', stored.filter((a) => a.courseId !== courseId));
+ });
+ loadDataFromStorage('quiz', (stored) => {
+ if (stored) saveDataToStorage('quiz', stored.filter((q) => q.courseId !== courseId));
+ });
+
+ // State: 대시보드에서 제거
+ setVods((prev) => prev.filter((v) => v.courseId !== courseId));
+ setAssigns((prev) => prev.filter((a) => a.courseId !== courseId));
+ setQuizzes((prev) => prev.filter((q) => q.courseId !== courseId));
+ }, []);
+ // 캐시 TTL 기반 자동 갱신 타이머
useEffect(() => {
- let timer: ReturnType;
- if (remainingTime >= 1440) {
- updateData();
- } else {
- timer = setTimeout(() => {
- setRemainingTime((prev) => prev + 1);
- }, 60 * 1000);
+ if (remainingTime >= CACHE_TTL_MINUTES) {
+ refreshCourseData();
+ return;
}
- return () => {
- clearTimeout(timer);
+ const timer = setTimeout(() => setRemainingTime((prev) => prev + 1), REFRESH_INTERVAL_MS);
+ return () => clearTimeout(timer);
+ }, [remainingTime, refreshCourseData]);
+
+ // 탭이 다시 보일 때 캐시 만료 확인 후 즉시 갱신
+ useEffect(() => {
+ const handleVisibility = () => {
+ if (document.visibilityState !== 'visible') return;
+ const lastRequest = getLastRequestTime();
+ if (!lastRequest) return;
+ const elapsed = Date.now() - lastRequest;
+ if (elapsed >= CACHE_TTL_MS) {
+ refreshCourseData();
+ } else {
+ setRemainingTime(elapsed / REFRESH_INTERVAL_MS);
+ }
};
- }, [remainingTime, updateData]);
+ document.addEventListener('visibilitychange', handleVisibility);
+ return () => document.removeEventListener('visibilitychange', handleVisibility);
+ }, [refreshCourseData]);
+ // 초기 로드: 캐시 확인 후 fetch 또는 캐시 사용 (최초 1회만 실행)
+ const initialLoadDone = useRef(false);
useEffect(() => {
if (!courses || courses.length === 0) return;
+ if (initialLoadDone.current) return;
+ initialLoadDone.current = true;
+
+ const lastRequest = getLastRequestTime();
+ const now = Date.now();
- const lastRequestTime = localStorage.getItem('lastRequestTime');
- const currentTime = new Date().getTime();
- const oneDay = 60 * 60 * 1000 * 24;
+ if (lastRequest) {
+ setRefreshTime(new Date(lastRequest).toLocaleTimeString());
+ }
- if (lastRequestTime) setRefreshTime(new Date(parseInt(lastRequestTime, 10)).toLocaleTimeString());
- if (!lastRequestTime || currentTime - parseInt(lastRequestTime, 10) >= oneDay) {
+ if (!lastRequest || now - lastRequest >= CACHE_TTL_MS) {
setIsPending(true);
- updateData();
+ refreshCourseData();
} else {
- const minutes = (currentTime - parseInt(lastRequestTime, 10)) / (60 * 1000);
- setRemainingTime(minutes);
- loadDataFromStorage('vod', (data) => {
- if (!data) return;
- setVods((data as Vod[]).filter((vod) => isCurrentDateInRange(vod.range)));
+ setRemainingTime((now - lastRequest) / REFRESH_INTERVAL_MS);
+ loadDataFromStorage('vod', (data) => {
+ if (data) setVods(filterVodsForDisplay(data));
});
- loadDataFromStorage('assign', (data) => {
- if (!data) return;
- setAssigns((data as Assign[]).filter((assign) => isCurrentDateByDate(assign.dueDate)));
+ loadDataFromStorage('assign', (data) => {
+ if (data) setAssigns(filterItemsForDisplay(data));
});
- loadDataFromStorage('quiz', (data) => {
- if (!data) return;
- setQuizes((data as Quiz[]).filter((quiz) => isCurrentDateByDate(quiz.dueDate)));
+ loadDataFromStorage('quiz', (data) => {
+ if (data) setQuizzes(filterItemsForDisplay(data));
});
}
- }, [courses, updateData]);
- return { vods, assigns, quizes, isPending, remainingTime, refreshTime, isError, updateData, setIsPending };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [courses]);
+
+ return {
+ vods,
+ assigns,
+ quizzes,
+ isPending,
+ remainingTime,
+ refreshTime,
+ isError,
+ refreshCourseData,
+ addCourseData,
+ removeCourseData,
+ pendingCourseIds,
+ };
}
diff --git a/src/hooks/useDashboardFilters.ts b/src/hooks/useDashboardFilters.ts
new file mode 100644
index 0000000..7680280
--- /dev/null
+++ b/src/hooks/useDashboardFilters.ts
@@ -0,0 +1,101 @@
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { Assign, Filters, Quiz, TAB_TYPE, Vod } from '@/types';
+import { filterVods, filterAssigns, filterQuizzes } from '@/lib/filterData';
+
+interface UseDashboardFiltersParams {
+ vods: Vod[];
+ assigns: Assign[];
+ quizzes: Quiz[];
+ activeTab: TAB_TYPE;
+}
+
+const INITIAL_FILTERS: Record = {
+ VIDEO: { courseTitles: [], attendanceStatuses: [] },
+ ASSIGN: { courseTitles: [], submitStatuses: [] },
+ QUIZ: { courseTitles: [], submitStatuses: [] },
+ SETTING: { courseTitles: [] },
+};
+
+function uniqueTitles(items: { courseTitle: string }[]): string[] {
+ return [...new Set(items.map((item) => item.courseTitle))];
+}
+
+export function useDashboardFilters({ vods, assigns, quizzes, activeTab }: UseDashboardFiltersParams) {
+ const [searchTerm, setSearchTerm] = useState('');
+ const [isFilterOpen, setIsFilterOpen] = useState(false);
+ const [filters, setFilters] = useState(INITIAL_FILTERS);
+
+ useEffect(() => {
+ setSearchTerm('');
+ setIsFilterOpen(false);
+ }, [activeTab]);
+
+ const courseTitlesMap = useMemo(
+ () => ({
+ VIDEO: uniqueTitles(vods),
+ ASSIGN: uniqueTitles(assigns),
+ QUIZ: uniqueTitles(quizzes),
+ SETTING: [],
+ }),
+ [vods, assigns, quizzes]
+ );
+
+ const filteredVods = useMemo(
+ () => filterVods(vods, filters[activeTab], searchTerm, 'isAttendance'),
+ [vods, filters, activeTab, searchTerm]
+ );
+
+ const filteredAssigns = useMemo(
+ () => filterAssigns(assigns, filters[activeTab], searchTerm, 'isSubmit'),
+ [assigns, filters, activeTab, searchTerm]
+ );
+
+ const filteredQuizzes = useMemo(
+ () => filterQuizzes(quizzes, filters[activeTab], searchTerm, 'dueDate'),
+ [quizzes, filters, activeTab, searchTerm]
+ );
+
+ const handleFilterChange = useCallback(
+ (field: K, value: Filters[K] extends (infer T)[] | undefined ? T : never) => {
+ setFilters((prev) => {
+ const current = (prev[activeTab][field] as (typeof value)[]) || [];
+ const updated = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
+ return { ...prev, [activeTab]: { ...prev[activeTab], [field]: updated } };
+ });
+ },
+ [activeTab]
+ );
+
+ const handleCourseTitleChange = useCallback(
+ (title: string) => handleFilterChange('courseTitles', title),
+ [handleFilterChange]
+ );
+ const handleAttendanceFilterChange = useCallback(
+ (status: string) => handleFilterChange('attendanceStatuses', status),
+ [handleFilterChange]
+ );
+ const handleSubmitFilterChange = useCallback(
+ (isSubmit: boolean) => handleFilterChange('submitStatuses', isSubmit),
+ [handleFilterChange]
+ );
+
+ const clearFilters = useCallback(() => {
+ setFilters((prev) => ({ ...prev, [activeTab]: { ...INITIAL_FILTERS[activeTab] } }));
+ }, [activeTab]);
+
+ return {
+ searchTerm,
+ setSearchTerm,
+ isFilterOpen,
+ setIsFilterOpen,
+ filters,
+ courseTitlesMap,
+ filteredVods,
+ filteredAssigns,
+ filteredQuizzes,
+ handleCourseTitleChange,
+ handleAttendanceFilterChange,
+ handleSubmitFilterChange,
+ clearFilters,
+ };
+}
diff --git a/src/hooks/useGetCourse.ts b/src/hooks/useGetCourse.ts
deleted file mode 100644
index 392be39..0000000
--- a/src/hooks/useGetCourse.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { CourseBase } from '@/content/types';
-import { saveDataToStorage } from '@/lib/storage';
-import { removeSquareBrackets } from '@/lib/utils';
-import { useState, useEffect } from 'react';
-
-export interface UseCourseResult {
- courses: CourseBase[];
-}
-
-export const useGetCourses = (): UseCourseResult => {
- const [courses, setCourses] = useState([]);
- useEffect(() => {
- if (import.meta.env.VITE_MOCK) {
- import('@/mocks/mockData').then(({ mockCourses }) => {
- setCourses(mockCourses);
- saveDataToStorage('courses', JSON.stringify(mockCourses));
- console.info('[Dotbugi] DEV 모드: mock 강의 목록 사용');
- });
- return;
- }
- if (!document) return;
- const courseData = Array.from(document.querySelectorAll('.course_box'));
- const data = courseData
- .map((div) => {
- const label = div.querySelector('.course_link .course-name .course-label')?.textContent?.trim() || null;
- if (!label || label === '커뮤니티') return null;
- const a = div.querySelector('a');
- const url = new URL((a as HTMLAnchorElement).href);
- const urlParams = new URLSearchParams(url.search);
- const courseId = urlParams.get('id') || '';
- const titleSection = div.querySelector('.course_link .course-name .course-title');
- const prof = titleSection?.querySelector('p')?.textContent?.trim() || '';
- let courseTitle = titleSection?.querySelector('h1, h2, h3')?.textContent?.replace(/new/i, '').trim() || '';
- courseTitle = removeSquareBrackets(courseTitle);
- return { courseId, courseTitle, prof };
- })
- .filter(
- (item): item is { courseId: string; courseTitle: string; prof: string } =>
- item !== null && item.courseId !== '' && item.courseTitle !== '' && item.prof !== ''
- );
-
- setCourses(data);
- saveDataToStorage('courses', JSON.stringify(data));
- console.info('[Dotbugi] 강의 목록:', data);
- }, []);
-
- return { courses };
-};
diff --git a/src/hooks/useGetCourses.ts b/src/hooks/useGetCourses.ts
new file mode 100644
index 0000000..4a60ccf
--- /dev/null
+++ b/src/hooks/useGetCourses.ts
@@ -0,0 +1,86 @@
+import { CourseBase } from '@/types';
+import { saveDataToStorage, loadDataFromStorage } from '@/lib/storage';
+import { parseCoursesFromDOM } from '@/lib/parseCourses';
+import { useState, useEffect, useCallback, useMemo } from 'react';
+
+function getMockCourses(): CourseBase[] | null {
+ const raw = import.meta.env.VITE_MOCK_COURSES;
+ if (!raw) return null;
+ try {
+ const parsed = JSON.parse(raw);
+ if (Array.isArray(parsed) && parsed.length > 0) return parsed;
+ } catch {
+ console.error('[Dotbugi] VITE_MOCK_COURSES 파싱 실패');
+ }
+ return null;
+}
+
+export const useGetCourses = () => {
+ const [allCourses, setAllCourses] = useState([]);
+ const [trackedCourseIds, setTrackedCourseIdsState] = useState(null);
+
+ const setTrackedCourseIds = useCallback((ids: string[]) => {
+ setTrackedCourseIdsState(ids);
+ saveDataToStorage('trackedCourseIds', ids);
+ }, []);
+
+ useEffect(() => {
+ const initCourses = (courses: CourseBase[]) => {
+ setAllCourses(courses);
+ saveDataToStorage('courses', JSON.stringify(courses));
+
+ loadDataFromStorage('trackedCourseIds', (savedIds) => {
+ if (savedIds && savedIds.length > 0) {
+ setTrackedCourseIdsState(savedIds);
+ } else {
+ // 최초 사용: 비교과(커뮤니티) 제외하고 전부 트래킹
+ const defaultIds = courses
+ .filter((c) => !c.isCommunity)
+ .map((c) => c.courseId);
+ setTrackedCourseIdsState(defaultIds);
+ saveDataToStorage('trackedCourseIds', defaultIds);
+ }
+ });
+ };
+
+ if (import.meta.env.VITE_MOCK) {
+ const envCourses = getMockCourses();
+ if (envCourses) {
+ // MOCK_COURSES 모드: chrome.storage 없이 전부 트래킹
+ setAllCourses(envCourses);
+ const defaultIds = envCourses.map((c) => c.courseId);
+ setTrackedCourseIdsState(defaultIds);
+ return;
+ }
+ import('@/mocks/mockData').then(({ mockCourses }) => {
+ initCourses(mockCourses);
+ });
+ return;
+ }
+
+ const parsed = parseCoursesFromDOM();
+ initCourses(parsed);
+ }, []);
+
+ // DOM 토글에서 trackedCourseIds가 변경되면 React 상태 동기화
+ useEffect(() => {
+ const listener = (changes: { [key: string]: chrome.storage.StorageChange }) => {
+ if (changes.trackedCourseIds) {
+ const newIds = changes.trackedCourseIds.newValue as string[] | undefined;
+ if (newIds) {
+ setTrackedCourseIdsState(newIds);
+ }
+ }
+ };
+ chrome.storage.onChanged.addListener(listener);
+ return () => chrome.storage.onChanged.removeListener(listener);
+ }, []);
+
+ const trackedCourses = useMemo(() => {
+ if (!trackedCourseIds) return [];
+ const idSet = new Set(trackedCourseIds);
+ return allCourses.filter((c) => idSet.has(c.courseId));
+ }, [allCourses, trackedCourseIds]);
+
+ return { allCourses, trackedCourses, trackedCourseIds: trackedCourseIds ?? [], setTrackedCourseIds };
+};
diff --git a/src/hooks/useHiddenTasks.ts b/src/hooks/useHiddenTasks.ts
new file mode 100644
index 0000000..3642442
--- /dev/null
+++ b/src/hooks/useHiddenTasks.ts
@@ -0,0 +1,49 @@
+import { useState, useEffect, useCallback } from 'react';
+import { saveDataToStorage, loadDataFromStorage } from '@/lib/storage';
+
+const STORAGE_KEY = 'hiddenTaskUrls';
+
+export function useHiddenTasks() {
+ const [hiddenUrls, setHiddenUrls] = useState>(new Set());
+
+ useEffect(() => {
+ loadDataFromStorage(STORAGE_KEY, (data) => {
+ if (data) setHiddenUrls(new Set(data));
+ });
+ }, []);
+
+ const persist = useCallback((urls: Set) => {
+ saveDataToStorage(STORAGE_KEY, Array.from(urls));
+ }, []);
+
+ const hideTask = useCallback((url: string) => {
+ setHiddenUrls((prev) => {
+ const next = new Set(prev);
+ next.add(url);
+ persist(next);
+ return next;
+ });
+ }, [persist]);
+
+ const hideTasks = useCallback((urls: string[]) => {
+ setHiddenUrls((prev) => {
+ const next = new Set(prev);
+ for (const url of urls) next.add(url);
+ persist(next);
+ return next;
+ });
+ }, [persist]);
+
+ const unhideTask = useCallback((url: string) => {
+ setHiddenUrls((prev) => {
+ const next = new Set(prev);
+ next.delete(url);
+ persist(next);
+ return next;
+ });
+ }, [persist]);
+
+ const isHidden = useCallback((url: string) => hiddenUrls.has(url), [hiddenUrls]);
+
+ return { hiddenUrls, hideTask, hideTasks, unhideTask, isHidden };
+}
diff --git a/src/hooks/useRemainingTime.ts b/src/hooks/useRemainingTime.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/i18n/LanguageSwitcher.tsx b/src/i18n/LanguageSwitcher.tsx
new file mode 100644
index 0000000..14ca450
--- /dev/null
+++ b/src/i18n/LanguageSwitcher.tsx
@@ -0,0 +1,25 @@
+import { useTranslation } from 'react-i18next';
+import { SUPPORTED_LANGUAGES, changeLanguage, type LanguageCode } from '@/i18n';
+import { Globe } from 'lucide-react';
+
+export default function LanguageSwitcher() {
+ const { i18n, t } = useTranslation('common');
+
+ return (
+
+
+ changeLanguage(e.target.value as LanguageCode)}
+ className="text-sm bg-transparent border border-gray-300 rounded-md px-2 py-1 text-gray-700 outline-none focus:border-slate-400 w-full cursor-pointer"
+ aria-label={t('language')}
+ >
+ {SUPPORTED_LANGUAGES.map((lang) => (
+
+ {lang.label}
+
+ ))}
+
+
+ );
+}
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
new file mode 100644
index 0000000..957306a
--- /dev/null
+++ b/src/i18n/index.ts
@@ -0,0 +1,67 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+
+import koCommon from './locales/ko/common.json';
+import koPopover from './locales/ko/popover.json';
+import koPlayer from './locales/ko/player.json';
+
+import enCommon from './locales/en/common.json';
+import enPopover from './locales/en/popover.json';
+import enPlayer from './locales/en/player.json';
+
+import jaCommon from './locales/ja/common.json';
+import jaPopover from './locales/ja/popover.json';
+import jaPlayer from './locales/ja/player.json';
+
+import zhCommon from './locales/zh/common.json';
+import zhPopover from './locales/zh/popover.json';
+import zhPlayer from './locales/zh/player.json';
+
+export const SUPPORTED_LANGUAGES = [
+ { code: 'ko', label: '한국어' },
+ { code: 'en', label: 'English' },
+ { code: 'ja', label: '日本語' },
+ { code: 'zh', label: '中文' },
+] as const;
+
+export type LanguageCode = (typeof SUPPORTED_LANGUAGES)[number]['code'];
+
+function detectLanguage(): LanguageCode {
+ const navLang = navigator.language.split('-')[0];
+ const supported = SUPPORTED_LANGUAGES.map((l) => l.code) as readonly string[];
+ return supported.includes(navLang) ? (navLang as LanguageCode) : 'ko';
+}
+
+const resources = {
+ ko: { common: koCommon, popover: koPopover, player: koPlayer },
+ en: { common: enCommon, popover: enPopover, player: enPlayer },
+ ja: { common: jaCommon, popover: jaPopover, player: jaPlayer },
+ zh: { common: zhCommon, popover: zhPopover, player: zhPlayer },
+};
+
+i18n.use(initReactI18next).init({
+ resources,
+ lng: detectLanguage(),
+ fallbackLng: 'ko',
+ defaultNS: 'common',
+ ns: ['common', 'popover', 'player'],
+ interpolation: { escapeValue: false },
+});
+
+// chrome.storage에서 저장된 언어 설정을 불러와서 적용
+if (typeof chrome !== 'undefined' && chrome.storage?.local) {
+ chrome.storage.local.get('language', (result) => {
+ if (result.language && i18n.language !== result.language) {
+ i18n.changeLanguage(result.language);
+ }
+ });
+}
+
+export function changeLanguage(lng: LanguageCode) {
+ i18n.changeLanguage(lng);
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
+ chrome.storage.local.set({ language: lng });
+ }
+}
+
+export default i18n;
diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json
new file mode 100644
index 0000000..8bb05ba
--- /dev/null
+++ b/src/i18n/locales/en/common.json
@@ -0,0 +1,132 @@
+{
+ "vod": "Lectures",
+ "assign": "Assignments",
+ "quiz": "Quizzes",
+ "setting": "Setting",
+ "dashboard": "Dashboard",
+ "close": "Close",
+ "clearAll": "Clear All",
+ "search": "Search",
+ "cancel": "Cancel",
+ "notFound": "404 Not Found",
+ "attendance": {
+ "attended": "Attended",
+ "absent": "Absent"
+ },
+ "submit": {
+ "done": "Submitted",
+ "needed": "Not Submitted",
+ "doneSpaced": "Submitted",
+ "neededSpaced": "Not Submitted"
+ },
+ "status": {
+ "notStarted": "Not Started",
+ "completed": "Completed",
+ "inProgress": "In Progress",
+ "checkManually": "Check Manually",
+ "checkManuallyNoSpace": "Check Manually"
+ },
+ "date": {
+ "noDeadline": "No Deadline",
+ "noInfo": "No Info",
+ "expired": "Expired",
+ "daysLater": "In {{days}} days",
+ "hoursLater": "In {{hours}} hours",
+ "minutesLater": "In {{minutes}} min",
+ "remaining": "{{days}}d {{hours}}h {{minutes}}m left",
+ "remainingHoursMinutes": "{{hours}}h {{minutes}}m left",
+ "remainingMinutes": "{{minutes}}m left",
+ "justNow": "Just now",
+ "daysAgo": "{{days}}d {{hours}}h {{minutes}}m ago",
+ "hoursAgo": "{{hours}}h {{minutes}}m ago",
+ "minutesAgo": "{{minutes}}m ago",
+ "ago": "ago",
+ "minutesAgoShort": "{{minutes}}m ago",
+ "hoursAgoShort": "{{hours}}h ago",
+ "monthYear": "MMMM yyyy",
+ "weekdays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+ },
+ "count": "{{count}}",
+ "countZero": "0",
+ "percentComplete": "{{percent}}% Done",
+ "error": {
+ "occurred": "An error occurred.",
+ "refreshPage": "Refresh Page"
+ },
+ "noRecords": "No records found.",
+ "empty": {
+ "vod": "No lectures",
+ "assign": "No assignments",
+ "quiz": "No quizzes"
+ },
+ "progress": "Progress",
+ "attendancePeriod": "Attendance Period",
+ "filterSet": "Filter Applied",
+ "language": "Language",
+ "contact": "Contact Us",
+ "calendar": {
+ "bannerTitle": "Google Calendar Sync",
+ "bannerDesc": "Manage your lectures, assignments, and quizzes on Google Calendar with Dotbugi",
+ "login": "Log In",
+ "sync": "Sync Tasks",
+ "syncShort": "Sync",
+ "logout": "Log Out",
+ "syncing": "Syncing...",
+ "syncSuccess": "{{count}} events added",
+ "syncNoNew": "Already up to date",
+ "syncFailed": "Failed to add {{failed}} events",
+ "syncPartial": "{{added}} added, {{failed}} failed",
+ "tokenExpired": "Google login has expired",
+ "tokenExpiredAction": "Log in again"
+ },
+ "refresh": "Refresh",
+ "refreshing": "Refreshing...",
+ "refreshSuccess": "Refresh complete",
+ "bubble": {
+ "tasks": "{{count}} left to go!",
+ "allDone": "All done!",
+ "updatedAgo": "As of {{time}}"
+ },
+ "courses": {
+ "title": "Manage Courses",
+ "desc": "Select which courses to track",
+ "community": "Community"
+ },
+ "hide": {
+ "task": "Hide this task",
+ "title": "Hidden Tasks",
+ "empty": "No hidden tasks",
+ "unhide": "Unhide"
+ },
+ "github": {
+ "likeProject": "Like this project?",
+ "starOnGithub": "Star us on GitHub!"
+ },
+ "courseStatus": {
+ "vod": {
+ "completed": "Attended",
+ "inProgress": "Watching",
+ "absent": "Absent",
+ "pending": "Not Watched"
+ },
+ "assign": {
+ "completed": "Submitted",
+ "late": "Late Submission",
+ "absent": "Not Submitted (Closed)",
+ "pending": "Not Submitted"
+ },
+ "quiz": {
+ "completed": "Taken",
+ "absent": "Not Taken (Closed)",
+ "pending": "Not Taken"
+ },
+ "hidden": "Hidden",
+ "currentWeek": "This Week",
+ "goToCurrentWeek": "Go to this week",
+ "bar": {
+ "loading": "Loading info...",
+ "success": "Info loaded",
+ "error": "Failed to load info"
+ }
+ }
+}
diff --git a/src/i18n/locales/en/player.json b/src/i18n/locales/en/player.json
new file mode 100644
index 0000000..6264d64
--- /dev/null
+++ b/src/i18n/locales/en/player.json
@@ -0,0 +1,13 @@
+{
+ "noVideoInfo": "No video information",
+ "startPrompt": "Press the Start button to begin auto-play",
+ "stopWatching": "Stop",
+ "expectedEnd": "Done by {{time}}",
+ "doneWatching": "Completed",
+ "startWatching": "Start",
+ "lectureList": "Lecture List",
+ "noVideos": "No videos to watch",
+ "refreshPrompt": "Click Dotbugi to refresh lectures",
+ "appIcon": "Dotbugi Icon",
+ "appName": "Dotbugi 🔎"
+}
diff --git a/src/i18n/locales/en/popover.json b/src/i18n/locales/en/popover.json
new file mode 100644
index 0000000..b0b40e5
--- /dev/null
+++ b/src/i18n/locales/en/popover.json
@@ -0,0 +1,13 @@
+{
+ "header": {
+ "vodList": "Online Lectures",
+ "assignList": "Assignments",
+ "quizList": "Quizzes",
+ "setting": "Setting"
+ },
+ "pending": {
+ "title": "Request in Progress",
+ "message": "Please wait while the request is being processed.",
+ "autoClose": "This window will close automatically when done."
+ }
+}
diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json
new file mode 100644
index 0000000..6010c23
--- /dev/null
+++ b/src/i18n/locales/ja/common.json
@@ -0,0 +1,132 @@
+{
+ "vod": "講義",
+ "assign": "課題",
+ "quiz": "クイズ",
+ "setting": "設定",
+ "dashboard": "ダッシュボード",
+ "close": "閉じる",
+ "clearAll": "すべてクリア",
+ "search": "検索",
+ "cancel": "キャンセル",
+ "notFound": "404 Not Found",
+ "attendance": {
+ "attended": "出席",
+ "absent": "欠席"
+ },
+ "submit": {
+ "done": "提出済み",
+ "needed": "未提出",
+ "doneSpaced": "提出済み",
+ "neededSpaced": "未提出"
+ },
+ "status": {
+ "notStarted": "未学習",
+ "completed": "学習完了",
+ "inProgress": "学習中",
+ "checkManually": "直接確認",
+ "checkManuallyNoSpace": "直接確認"
+ },
+ "date": {
+ "noDeadline": "期限なし",
+ "noInfo": "情報なし",
+ "expired": "締切",
+ "daysLater": "{{days}}日後",
+ "hoursLater": "{{hours}}時間後",
+ "minutesLater": "{{minutes}}分後",
+ "remaining": "{{days}}日{{hours}}時間{{minutes}}分 残り",
+ "remainingHoursMinutes": "{{hours}}時間{{minutes}}分 残り",
+ "remainingMinutes": "{{minutes}}分 残り",
+ "justNow": "たった今",
+ "daysAgo": "{{days}}日{{hours}}時間{{minutes}}分前",
+ "hoursAgo": "{{hours}}時間{{minutes}}分前",
+ "minutesAgo": "{{minutes}}分前",
+ "ago": "前",
+ "minutesAgoShort": "{{minutes}}分前",
+ "hoursAgoShort": "{{hours}}時間前",
+ "monthYear": "yyyy年 MM月",
+ "weekdays": ["日", "月", "火", "水", "木", "金", "土"]
+ },
+ "count": "{{count}} 件",
+ "countZero": "0 件",
+ "percentComplete": "{{percent}}% 完了",
+ "error": {
+ "occurred": "エラーが発生しました。",
+ "refreshPage": "ページを更新"
+ },
+ "noRecords": "記録がありません。",
+ "empty": {
+ "vod": "講義がありません",
+ "assign": "課題がありません",
+ "quiz": "クイズがありません"
+ },
+ "progress": "進捗率",
+ "attendancePeriod": "出席認定期間",
+ "filterSet": "フィルター適用中",
+ "language": "言語",
+ "contact": "お問い合わせ",
+ "calendar": {
+ "bannerTitle": "Google Calendar 連携",
+ "bannerDesc": "Dotbugiで講義・課題・クイズの日程をGoogle Calendarで管理しましょう",
+ "login": "ログイン",
+ "sync": "タスク同期",
+ "syncShort": "同期",
+ "logout": "ログアウト",
+ "syncing": "同期中...",
+ "syncSuccess": "{{count}}件の予定を追加しました",
+ "syncNoNew": "すでに最新です",
+ "syncFailed": "{{failed}}件の追加に失敗しました",
+ "syncPartial": "{{added}}件追加、{{failed}}件失敗",
+ "tokenExpired": "Googleログインの期限が切れました",
+ "tokenExpiredAction": "再ログイン"
+ },
+ "refresh": "更新",
+ "refreshing": "更新中...",
+ "refreshSuccess": "更新完了",
+ "bubble": {
+ "tasks": "あと{{count}}件あるよ!",
+ "allDone": "全部完了!",
+ "updatedAgo": "{{time}} 時点"
+ },
+ "courses": {
+ "title": "受講科目管理",
+ "desc": "追跡する科目を選択してください",
+ "community": "コミュニティ"
+ },
+ "hide": {
+ "task": "このタスクを非表示",
+ "title": "非表示のタスク",
+ "empty": "非表示のタスクはありません",
+ "unhide": "再表示"
+ },
+ "github": {
+ "likeProject": "このプロジェクトが気に入りましたか?",
+ "starOnGithub": "GitHubでスターをお願いします!"
+ },
+ "courseStatus": {
+ "vod": {
+ "completed": "出席",
+ "inProgress": "視聴中",
+ "absent": "欠席",
+ "pending": "未視聴"
+ },
+ "assign": {
+ "completed": "提出済み",
+ "late": "遅れた提出",
+ "absent": "未提出 (締切)",
+ "pending": "未提出"
+ },
+ "quiz": {
+ "completed": "受験済み",
+ "absent": "未受験 (締切)",
+ "pending": "未受験"
+ },
+ "hidden": "非表示",
+ "currentWeek": "今週",
+ "goToCurrentWeek": "今週へ移動",
+ "bar": {
+ "loading": "情報を読み込み中...",
+ "success": "情報の読み込み完了",
+ "error": "情報の読み込みに失敗しました"
+ }
+ }
+}
diff --git a/src/i18n/locales/ja/player.json b/src/i18n/locales/ja/player.json
new file mode 100644
index 0000000..b1a46e4
--- /dev/null
+++ b/src/i18n/locales/ja/player.json
@@ -0,0 +1,13 @@
+{
+ "noVideoInfo": "映像情報がありません",
+ "startPrompt": "自動受講を開始するには受講開始ボタンを押してください",
+ "stopWatching": "受講終了",
+ "expectedEnd": "{{time}} 完了予定",
+ "doneWatching": "受講完了",
+ "startWatching": "受講開始",
+ "lectureList": "講義リスト",
+ "noVideos": "受講する映像がありません",
+ "refreshPrompt": "Dotbugiをクリックして講義を更新してください",
+ "appIcon": "Dotbugiアイコン",
+ "appName": "Dotbugi 🔎"
+}
diff --git a/src/i18n/locales/ja/popover.json b/src/i18n/locales/ja/popover.json
new file mode 100644
index 0000000..b925ddd
--- /dev/null
+++ b/src/i18n/locales/ja/popover.json
@@ -0,0 +1,13 @@
+{
+ "header": {
+ "vodList": "オンライン講義一覧",
+ "assignList": "課題一覧",
+ "quizList": "クイズ一覧",
+ "setting": "設定"
+ },
+ "pending": {
+ "title": "リクエスト処理中",
+ "message": "リクエストを処理しています。しばらくお待ちください。",
+ "autoClose": "完了すると自動的にウィンドウが閉じます。"
+ }
+}
diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json
new file mode 100644
index 0000000..7efde54
--- /dev/null
+++ b/src/i18n/locales/ko/common.json
@@ -0,0 +1,132 @@
+{
+ "vod": "강의",
+ "assign": "과제",
+ "quiz": "퀴즈",
+ "setting": "설정",
+ "dashboard": "대시보드",
+ "close": "닫기",
+ "clearAll": "모두 지우기",
+ "search": "검색",
+ "cancel": "취소",
+ "notFound": "404 Not Found",
+ "attendance": {
+ "attended": "출석",
+ "absent": "결석"
+ },
+ "submit": {
+ "done": "제출완료",
+ "needed": "제출필요",
+ "doneSpaced": "제출 완료",
+ "neededSpaced": "제출 필요"
+ },
+ "status": {
+ "notStarted": "학습전",
+ "completed": "학습완료",
+ "inProgress": "학습중",
+ "checkManually": "직접 확인",
+ "checkManuallyNoSpace": "직접확인"
+ },
+ "date": {
+ "noDeadline": "기한없음",
+ "noInfo": "정보없음",
+ "expired": "마감",
+ "daysLater": "{{days}}일 후",
+ "hoursLater": "{{hours}}시간 후",
+ "minutesLater": "{{minutes}}분 후",
+ "remaining": "{{days}}일 {{hours}}시간 {{minutes}}분 남음",
+ "remainingHoursMinutes": "{{hours}}시간 {{minutes}}분 남음",
+ "remainingMinutes": "{{minutes}}분 남음",
+ "justNow": "지금 막",
+ "daysAgo": "{{days}}일 {{hours}}시간 {{minutes}}분 전",
+ "hoursAgo": "{{hours}}시간 {{minutes}}분 전",
+ "minutesAgo": "{{minutes}}분 전",
+ "ago": "전",
+ "minutesAgoShort": "{{minutes}}분 전",
+ "hoursAgoShort": "{{hours}}시간 전",
+ "monthYear": "yyyy년 MM월",
+ "weekdays": ["일", "월", "화", "수", "목", "금", "토"]
+ },
+ "count": "{{count}} 개",
+ "countZero": "0 개",
+ "percentComplete": "{{percent}}% 완료",
+ "error": {
+ "occurred": "오류가 발생했습니다.",
+ "refreshPage": "페이지 새로고침"
+ },
+ "noRecords": "기록이 존재하지 않습니다.",
+ "empty": {
+ "vod": "강의가 없습니다",
+ "assign": "과제가 없습니다",
+ "quiz": "퀴즈가 없습니다"
+ },
+ "progress": "진도율",
+ "attendancePeriod": "출석인정기간",
+ "filterSet": "필터 설정됨",
+ "language": "언어",
+ "contact": "문의하기",
+ "calendar": {
+ "bannerTitle": "Google Calendar 연동",
+ "bannerDesc": "돋부기를 통해 강의, 과제, 퀴즈 일정을 Google Calendar로 관리하세요",
+ "login": "로그인",
+ "sync": "태스크 동기화",
+ "syncShort": "동기화",
+ "logout": "로그아웃",
+ "syncing": "동기화 중...",
+ "syncSuccess": "{{count}}개 일정이 추가되었어요",
+ "syncNoNew": "이미 최신 상태예요",
+ "syncFailed": "{{failed}}개 일정 추가에 실패했어요",
+ "syncPartial": "{{added}}개 추가, {{failed}}개 실패",
+ "tokenExpired": "Google 로그인이 만료되었어요",
+ "tokenExpiredAction": "다시 로그인"
+ },
+ "refresh": "새로고침",
+ "refreshing": "새로고침 중...",
+ "refreshSuccess": "새로고침 완료",
+ "bubble": {
+ "tasks": "아직 {{count}}개 남았어요!",
+ "allDone": "모두 완료했어요!",
+ "updatedAgo": "{{time}} 기준"
+ },
+ "courses": {
+ "title": "수강 과목 관리",
+ "desc": "추적할 과목을 선택하세요",
+ "community": "비교과"
+ },
+ "hide": {
+ "task": "이 태스크 숨기기",
+ "title": "숨긴 태스크",
+ "empty": "숨긴 태스크가 없습니다",
+ "unhide": "숨김 해제"
+ },
+ "github": {
+ "likeProject": "이 프로젝트가 마음에 드시나요?",
+ "starOnGithub": "GitHub에서 스타를 눌러주세요!"
+ },
+ "courseStatus": {
+ "vod": {
+ "completed": "출석",
+ "inProgress": "시청중",
+ "absent": "결석",
+ "pending": "미시청"
+ },
+ "assign": {
+ "completed": "제출완료",
+ "late": "늦은 제출",
+ "absent": "미제출 (마감)",
+ "pending": "미제출"
+ },
+ "quiz": {
+ "completed": "응시완료",
+ "absent": "미응시 (마감)",
+ "pending": "미응시"
+ },
+ "hidden": "숨김",
+ "currentWeek": "현재 주차",
+ "goToCurrentWeek": "현재 주차로 이동",
+ "bar": {
+ "loading": "정보 불러오는 중...",
+ "success": "정보 불러오기 완료",
+ "error": "정보를 불러오지 못했습니다"
+ }
+ }
+}
diff --git a/src/i18n/locales/ko/player.json b/src/i18n/locales/ko/player.json
new file mode 100644
index 0000000..a2783b5
--- /dev/null
+++ b/src/i18n/locales/ko/player.json
@@ -0,0 +1,13 @@
+{
+ "noVideoInfo": "영상 정보가 없습니다",
+ "startPrompt": "자동수강을 시작하려면 수강시작 버튼을 눌러주세요",
+ "stopWatching": "수강 종료",
+ "expectedEnd": "{{time}} 완료 예정",
+ "doneWatching": "수강 완료",
+ "startWatching": "수강 시작",
+ "lectureList": "강의 목록",
+ "noVideos": "수강할 영상이 없습니다",
+ "refreshPrompt": "이클래스 돋부기를 눌러 강의를 새로고침 해주세요",
+ "appIcon": "돋부기 아이콘",
+ "appName": "돋부기 🔎"
+}
diff --git a/src/i18n/locales/ko/popover.json b/src/i18n/locales/ko/popover.json
new file mode 100644
index 0000000..5339495
--- /dev/null
+++ b/src/i18n/locales/ko/popover.json
@@ -0,0 +1,13 @@
+{
+ "header": {
+ "vodList": "온라인 강의 목록",
+ "assignList": "과제 목록",
+ "quizList": "퀴즈 목록",
+ "setting": "설정"
+ },
+ "pending": {
+ "title": "요청 진행 중",
+ "message": "요청이 진행중입니다. 잠시만 기다려주세요.",
+ "autoClose": "완료되면 자동으로 창이 닫힙니다."
+ }
+}
diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json
new file mode 100644
index 0000000..f6eee61
--- /dev/null
+++ b/src/i18n/locales/zh/common.json
@@ -0,0 +1,133 @@
+{
+ "vod": "课程",
+ "assign": "作业",
+ "quiz": "测验",
+
+ "setting": "设置",
+ "dashboard": "仪表盘",
+ "close": "关闭",
+ "clearAll": "全部清除",
+ "search": "搜索",
+ "cancel": "取消",
+ "notFound": "404 Not Found",
+ "attendance": {
+ "attended": "出勤",
+ "absent": "缺勤"
+ },
+ "submit": {
+ "done": "已提交",
+ "needed": "未提交",
+ "doneSpaced": "已提交",
+ "neededSpaced": "未提交"
+ },
+ "status": {
+ "notStarted": "未学习",
+ "completed": "学习完成",
+ "inProgress": "学习中",
+ "checkManually": "手动确认",
+ "checkManuallyNoSpace": "手动确认"
+ },
+ "date": {
+ "noDeadline": "无截止日",
+ "noInfo": "无信息",
+ "expired": "已截止",
+ "daysLater": "{{days}}天后",
+ "hoursLater": "{{hours}}小时后",
+ "minutesLater": "{{minutes}}分钟后",
+ "remaining": "剩余{{days}}天{{hours}}小时{{minutes}}分钟",
+ "remainingHoursMinutes": "剩余{{hours}}小时{{minutes}}分钟",
+ "remainingMinutes": "剩余{{minutes}}分钟",
+ "justNow": "刚刚",
+ "daysAgo": "{{days}}天{{hours}}小时{{minutes}}分钟前",
+ "hoursAgo": "{{hours}}小时{{minutes}}分钟前",
+ "minutesAgo": "{{minutes}}分钟前",
+ "ago": "前",
+ "minutesAgoShort": "{{minutes}}分钟前",
+ "hoursAgoShort": "{{hours}}小时前",
+ "monthYear": "yyyy年 MM月",
+ "weekdays": ["日", "一", "二", "三", "四", "五", "六"]
+ },
+ "count": "{{count}} 个",
+ "countZero": "0 个",
+ "percentComplete": "{{percent}}% 完成",
+ "error": {
+ "occurred": "发生了错误。",
+ "refreshPage": "刷新页面"
+ },
+ "noRecords": "没有记录。",
+ "empty": {
+ "vod": "没有课程",
+ "assign": "没有作业",
+ "quiz": "没有测验"
+ },
+ "progress": "进度",
+ "attendancePeriod": "出勤认定期间",
+ "filterSet": "已设置筛选",
+ "language": "语言",
+ "contact": "联系我们",
+ "calendar": {
+ "bannerTitle": "Google Calendar 同步",
+ "bannerDesc": "通过Dotbugi在Google Calendar上管理课程、作业和测验日程",
+ "login": "登录",
+ "sync": "同步任务",
+ "syncShort": "同步",
+ "logout": "退出登录",
+ "syncing": "同步中...",
+ "syncSuccess": "已添加{{count}}个日程",
+ "syncNoNew": "已是最新状态",
+ "syncFailed": "{{failed}}个日程添加失败",
+ "syncPartial": "已添加{{added}}个,{{failed}}个失败",
+ "tokenExpired": "Google登录已过期",
+ "tokenExpiredAction": "重新登录"
+ },
+ "refresh": "刷新",
+ "refreshing": "刷新中...",
+ "refreshSuccess": "刷新完成",
+ "bubble": {
+ "tasks": "还剩{{count}}个哦!",
+ "allDone": "全部完成!",
+ "updatedAgo": "{{time}}基准"
+ },
+ "courses": {
+ "title": "课程管理",
+ "desc": "选择要追踪的课程",
+ "community": "社区"
+ },
+ "hide": {
+ "task": "隐藏此任务",
+ "title": "已隐藏的任务",
+ "empty": "没有隐藏的任务",
+ "unhide": "取消隐藏"
+ },
+ "github": {
+ "likeProject": "喜欢这个项目吗?",
+ "starOnGithub": "在GitHub上给我们加星!"
+ },
+ "courseStatus": {
+ "vod": {
+ "completed": "出勤",
+ "inProgress": "观看中",
+ "absent": "缺勤",
+ "pending": "未观看"
+ },
+ "assign": {
+ "completed": "已提交",
+ "late": "逾期提交",
+ "absent": "未提交 (截止)",
+ "pending": "未提交"
+ },
+ "quiz": {
+ "completed": "已参加",
+ "absent": "未参加 (截止)",
+ "pending": "未参加"
+ },
+ "hidden": "已隐藏",
+ "currentWeek": "本周",
+ "goToCurrentWeek": "跳转到本周",
+ "bar": {
+ "loading": "正在加载信息...",
+ "success": "信息加载完成",
+ "error": "信息加载失败"
+ }
+ }
+}
diff --git a/src/i18n/locales/zh/player.json b/src/i18n/locales/zh/player.json
new file mode 100644
index 0000000..3289a26
--- /dev/null
+++ b/src/i18n/locales/zh/player.json
@@ -0,0 +1,13 @@
+{
+ "noVideoInfo": "没有视频信息",
+ "startPrompt": "请按开始按钮开始自动播放",
+ "stopWatching": "停止学习",
+ "expectedEnd": "预计{{time}}完成",
+ "doneWatching": "学习完成",
+ "startWatching": "开始学习",
+ "lectureList": "课程列表",
+ "noVideos": "没有可观看的视频",
+ "refreshPrompt": "点击Dotbugi刷新课程",
+ "appIcon": "Dotbugi图标",
+ "appName": "Dotbugi 🔎"
+}
diff --git a/src/i18n/locales/zh/popover.json b/src/i18n/locales/zh/popover.json
new file mode 100644
index 0000000..6f63c7a
--- /dev/null
+++ b/src/i18n/locales/zh/popover.json
@@ -0,0 +1,13 @@
+{
+ "header": {
+ "vodList": "在线课程列表",
+ "assignList": "作业列表",
+ "quizList": "测验列表",
+ "setting": "设置"
+ },
+ "pending": {
+ "title": "请求处理中",
+ "message": "正在处理请求,请稍候。",
+ "autoClose": "完成后窗口将自动关闭。"
+ }
+}
diff --git a/src/lib/attendance.ts b/src/lib/attendance.ts
new file mode 100644
index 0000000..789f4bb
--- /dev/null
+++ b/src/lib/attendance.ts
@@ -0,0 +1,7 @@
+export function isAttended(value: string) {
+ return value.toLowerCase().trim() === 'o';
+}
+
+export function isAbsent(value: string) {
+ return value.toUpperCase().startsWith('X');
+}
diff --git a/src/lib/cache.ts b/src/lib/cache.ts
new file mode 100644
index 0000000..74f5233
--- /dev/null
+++ b/src/lib/cache.ts
@@ -0,0 +1,18 @@
+export const REFRESH_INTERVAL_MS = 60 * 1000;
+export const CACHE_TTL_MINUTES = 60; // 1시간
+export const CACHE_TTL_MS = CACHE_TTL_MINUTES * REFRESH_INTERVAL_MS;
+
+const CACHE_KEY = 'lastRequestTime';
+
+export function getLastRequestTime(): number | null {
+ const raw = localStorage.getItem(CACHE_KEY);
+ return raw ? parseInt(raw, 10) : null;
+}
+
+export function setLastRequestTime(time: number): void {
+ localStorage.setItem(CACHE_KEY, time.toString());
+}
+
+export function clearLastRequestTime(): void {
+ localStorage.removeItem(CACHE_KEY);
+}
diff --git a/src/lib/calendarUtils.ts b/src/lib/calendarUtils.ts
index b363825..2a3f20e 100644
--- a/src/lib/calendarUtils.ts
+++ b/src/lib/calendarUtils.ts
@@ -1,8 +1,9 @@
-import { CalendarEvent } from '@/hooks/useCalendarEvents';
+import { CalendarEvent } from '@/lib/transformCalendarEvents';
export type GoogleCalendarEvent = {
summary: string;
description?: string;
+ colorId?: string;
start: {
dateTime: string;
timeZone: string;
@@ -13,79 +14,190 @@ export type GoogleCalendarEvent = {
};
};
-export const getOAuthToken = async (): Promise => {
- return new Promise((resolve) => {
- chrome.identity.getAuthToken({ interactive: false }, (cachedToken) => {
- if (chrome.runtime.lastError || !cachedToken) {
- console.error('자동 로그인 실패:', chrome.runtime.lastError?.message);
- resolve(null);
- } else {
- resolve(cachedToken);
- }
- });
- });
+export type CalendarSyncResult = {
+ added: number;
+ failed: number;
+ tokenExpired: boolean;
+};
+
+const EVENT_COLOR_MAP: Record = {
+ vod: '1', // 라벤더
+ assign: '9', // 블루베리 (파랑)
+ quiz: '6', // 귤 (주황)
+};
+
+export const getOAuthToken = async (interactive = false): Promise => {
+ try {
+ const response = await chrome.runtime.sendMessage({ action: 'getAuthToken', interactive });
+ if (response?.token) {
+ return response.token;
+ }
+ console.error('OAuth 토큰 획득 실패:', response?.error);
+ } catch (e) {
+ console.error('OAuth 메시지 전송 실패:', e);
+ }
+ return null;
+};
+
+export const removeCachedAuthToken = async (token: string): Promise => {
+ try {
+ await chrome.runtime.sendMessage({ action: 'removeCachedAuthToken', token });
+ } catch (e) {
+ console.error('토큰 제거 메시지 전송 실패:', e);
+ }
};
/**
* 캘린더 API에 이벤트를 추가합니다.
- * @param event 캘린더에 추가할 이벤트 객체
- * @param token OAuth 토큰
+ * 401 반환 시 tokenExpired를 true로 반환합니다.
*/
-export async function addCalendarEvent(event: GoogleCalendarEvent, token: string): Promise {
+export async function addCalendarEvent(
+ event: GoogleCalendarEvent,
+ token: string,
+): Promise<{ ok: boolean; tokenExpired: boolean }> {
try {
- fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
+ const response = await fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(event),
- })
- .then((response) => response.json())
- .then(() => {})
- .catch((error) => console.error('이벤트 추가 실패:', error));
+ });
+ if (response.status === 401) {
+ return { ok: false, tokenExpired: true };
+ }
+ if (!response.ok) {
+ const errorBody = await response.json();
+ console.error('이벤트 추가 실패:', response.status, errorBody);
+ return { ok: false, tokenExpired: false };
+ }
+ return { ok: true, tokenExpired: false };
} catch (error) {
console.error('Error adding calendar event:', error);
+ return { ok: false, tokenExpired: false };
+ }
+}
+
+const BATCH_CONCURRENCY = 2;
+const BATCH_DELAY_MS = 500;
+
+async function runBatch(
+ items: T[],
+ fn: (item: T) => Promise,
+ concurrency: number,
+): Promise {
+ const results: R[] = new Array(items.length);
+
+ for (let i = 0; i < items.length; i += concurrency) {
+ const chunk = items.slice(i, i + concurrency);
+ const chunkResults = await Promise.all(chunk.map((item) => fn(item)));
+ chunkResults.forEach((r, j) => { results[i + j] = r; });
+ if (i + concurrency < items.length) {
+ await new Promise((resolve) => setTimeout(resolve, BATCH_DELAY_MS));
+ }
+ }
+
+ return results;
+}
+
+/**
+ * 이벤트를 동시 5건씩 전송하고 실패한 건을 1회 재시도합니다.
+ */
+export async function addCalendarEventsBatch(
+ events: GoogleCalendarEvent[],
+ token: string,
+): Promise {
+ const results = await runBatch(events, (event) => addCalendarEvent(event, token), BATCH_CONCURRENCY);
+
+ let added = 0;
+ const failed: GoogleCalendarEvent[] = [];
+ let tokenExpired = false;
+
+ results.forEach((result, i) => {
+ if (result.tokenExpired) {
+ tokenExpired = true;
+ } else if (result.ok) {
+ added++;
+ } else {
+ failed.push(events[i]);
+ }
+ });
+
+ if (tokenExpired) {
+ return { added, failed: events.length - added, tokenExpired: true };
}
+
+ // 실패한 건 1회 재시도
+ if (failed.length > 0) {
+ const retryResults = await runBatch(failed, (event) => addCalendarEvent(event, token), BATCH_CONCURRENCY);
+ let retryFailed = 0;
+ retryResults.forEach((result) => {
+ if (result.ok) {
+ added++;
+ } else if (result.tokenExpired) {
+ tokenExpired = true;
+ retryFailed++;
+ } else {
+ retryFailed++;
+ }
+ });
+ return { added, failed: retryFailed, tokenExpired };
+ }
+
+ return { added, failed: 0, tokenExpired: false };
}
/**
* 구글 캘린더 API를 사용해 현재 이벤트 목록을 가져옵니다.
- * @param token OAuth 토큰
- * @returns CalendarEvent 배열
*/
-export async function getCalendarEvents(token: string): Promise {
+export async function getCalendarEvents(
+ token: string,
+): Promise<{ events: GoogleCalendarEvent[]; tokenExpired: boolean }> {
try {
const pastDate = new Date();
- pastDate.setMonth(pastDate.getMonth() - 3);
- const response = await fetch(
- `https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=${pastDate.toISOString()}&orderBy=startTime&singleEvents=true`,
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
+ pastDate.setMonth(pastDate.getMonth() - 4);
+
+ let allEvents: GoogleCalendarEvent[] = [];
+ let pageToken: string | undefined;
+
+ do {
+ const url = new URL('https://www.googleapis.com/calendar/v3/calendars/primary/events');
+ url.searchParams.set('timeMin', pastDate.toISOString());
+ url.searchParams.set('orderBy', 'startTime');
+ url.searchParams.set('singleEvents', 'true');
+ url.searchParams.set('maxResults', '2500');
+ if (pageToken) url.searchParams.set('pageToken', pageToken);
+
+ const response = await fetch(url.toString(), {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+
+ if (response.status === 401) {
+ return { events: [], tokenExpired: true };
}
- );
- const data = await response.json();
- return data.items || [];
+
+ const data = await response.json();
+ allEvents = allEvents.concat(data.items || []);
+ pageToken = data.nextPageToken;
+ } while (pageToken);
+
+ return { events: allEvents, tokenExpired: false };
} catch (error) {
console.error('캘린더 이벤트 가져오기 실패:', error);
- return [];
+ return { events: [], tokenExpired: false };
}
}
/**
* CalendarEvent 배열을 받아 GoogleCalendarEvent 배열로 변환합니다.
- * 여기서는 title과 subject를 summary로 조합하는 예시입니다.
- * @param events CalendarEvent 배열
- * @returns GoogleCalendarEvent 배열
*/
export function convertCalendarEventsToGoogleEvents(events: CalendarEvent[]): GoogleCalendarEvent[] {
return events
.filter((event) => event.start !== null && event.end !== null)
.map((event) => ({
- summary: `${event.title}`,
- description: `${event.subject}`,
+ summary: `${event.title} - ${event.subject}`,
+ colorId: EVENT_COLOR_MAP[event.type],
start: {
dateTime: event.start!.toISOString(),
timeZone: 'Asia/Seoul',
diff --git a/src/lib/courseStatus/badge.ts b/src/lib/courseStatus/badge.ts
new file mode 100644
index 0000000..500b5ba
--- /dev/null
+++ b/src/lib/courseStatus/badge.ts
@@ -0,0 +1,184 @@
+import { Vod, Assign, Quiz } from '@/types';
+import { isAttended } from '@/lib/attendance';
+import { isCurrentDateByDate, parseDate, calculateDueDate, extractEndDate } from '@/lib/dateUtils';
+import { BASE_LINK } from '@/constants/links';
+import i18n from '@/i18n';
+
+// ── Types ───────────────────────────────────────────────────────────
+
+type StatusType = 'completed' | 'inProgress' | 'late' | 'absent' | 'urgent' | 'pending' | 'hidden';
+
+interface StatusInfo {
+ label: string;
+ type: StatusType;
+ progress?: number; // 0~100, inProgress 전용
+}
+
+// ── Constants ───────────────────────────────────────────────────────
+
+const BADGE_ATTR = 'data-dotbugi-status';
+
+const STATUS_COLORS: Record = {
+ completed: { bg: '#dcfce7', text: '#166534', border: '#86efac' },
+ inProgress: { bg: '#dbeafe', text: '#1e40af', border: '#93c5fd' },
+ late: { bg: '#450a0a', text: '#fecaca', border: '#7f1d1d' },
+ absent: { bg: '#450a0a', text: '#fecaca', border: '#7f1d1d' },
+ urgent: { bg: '#fee2e2', text: '#b91c1c', border: '#fca5a5' },
+ pending: { bg: '#fef3c7', text: '#92400e', border: '#fcd34d' },
+ hidden: { bg: '#f3f4f6', text: '#6b7280', border: '#d1d5db' },
+};
+
+const t = (key: string) => i18n.t(key, { ns: 'common' });
+
+// ── Helpers ─────────────────────────────────────────────────────────
+
+function parseTimeToSeconds(time: string): number {
+ const parts = time.split(':').map(Number);
+ if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
+ if (parts.length === 2) return parts[0] * 60 + parts[1];
+ return 0;
+}
+
+// ── Status determination ────────────────────────────────────────────
+
+function getVodStatus(vod: Vod): StatusInfo {
+ if (isAttended(vod.isAttendance)) {
+ return { label: t('courseStatus.vod.completed'), type: 'completed' };
+ }
+
+ const rangeEnd = vod.range?.split(' ~ ')[1];
+ if (rangeEnd && new Date() > parseDate(rangeEnd)) {
+ return { label: t('courseStatus.vod.absent'), type: 'absent' };
+ }
+
+ // 수강중: watchedTime > 0 이고 아직 출석 미완료
+ if (vod.watchedTime && vod.requiredTime) {
+ const watchedSec = parseTimeToSeconds(vod.watchedTime);
+ const requiredSec = parseTimeToSeconds(vod.requiredTime);
+ if (watchedSec > 0 && requiredSec > 0) {
+ const progress = Math.min(100, Math.round((watchedSec / requiredSec) * 100));
+ const endDate = extractEndDate(vod.range);
+ const remaining = endDate ? calculateDueDate(endDate) : null;
+ const suffix =
+ remaining && remaining.status !== 'expired' && remaining.status !== 'noInfo'
+ ? ` · ${remaining.message}`
+ : '';
+ const isUrgent = remaining?.status === 'urgent';
+ return {
+ label: `${t('courseStatus.vod.inProgress')}${suffix}`,
+ type: isUrgent ? 'urgent' : 'inProgress',
+ progress,
+ };
+ }
+ }
+
+ const endDate = extractEndDate(vod.range);
+ const remaining = endDate ? calculateDueDate(endDate) : null;
+ const label =
+ remaining && remaining.status !== 'expired' && remaining.status !== 'noInfo'
+ ? `${t('courseStatus.vod.pending')} · ${remaining.message}`
+ : t('courseStatus.vod.pending');
+ const type = remaining?.status === 'urgent' ? 'urgent' : 'pending';
+ return { label, type };
+}
+
+function getDueDateStatus(isSubmit: boolean, dueDate: string | null, labelKey: 'assign' | 'quiz'): StatusInfo {
+ if (isSubmit) {
+ return { label: t(`courseStatus.${labelKey}.completed`), type: 'completed' };
+ }
+ if (dueDate && !isCurrentDateByDate(dueDate)) {
+ return { label: t(`courseStatus.${labelKey}.absent`), type: 'absent' };
+ }
+ const remaining = dueDate ? calculateDueDate(dueDate) : null;
+ const label =
+ remaining && remaining.status !== 'expired' && remaining.status !== 'noInfo'
+ ? `${t(`courseStatus.${labelKey}.pending`)} · ${remaining.message}`
+ : t(`courseStatus.${labelKey}.pending`);
+ const type = remaining?.status === 'urgent' ? 'urgent' : 'pending';
+ return { label, type };
+}
+
+// ── Badge creation ──────────────────────────────────────────────────
+
+function createBadge({ label, type, progress }: StatusInfo): HTMLElement {
+ const { bg, text, border } = STATUS_COLORS[type];
+ const badge = document.createElement('span');
+ badge.setAttribute(BADGE_ATTR, '');
+ badge.textContent = label;
+
+ let background = bg;
+ if (progress !== undefined) {
+ // 프로그레스 시각화: 왼쪽에서 오른쪽으로 채움색 적용
+ const fillColor = type === 'urgent' ? '#fca5a5' : '#93c5fd';
+ background = `linear-gradient(to right, ${fillColor} 0%, ${fillColor} ${progress}%, ${bg} ${progress}%, ${bg} 100%)`;
+ }
+
+ badge.style.cssText = `
+ display:inline-flex;align-items:center;padding:2px 8px;margin-left:8px;
+ border-radius:9999px;font-size:11px;font-weight:600;line-height:1.4;
+ background:${background};color:${text};border:1px solid ${border};
+ white-space:nowrap;vertical-align:middle;
+ `;
+ return badge;
+}
+
+function normalizeUrl(url: string): string {
+ if (url.startsWith('http')) return url;
+ return BASE_LINK + (url.startsWith('/') ? '' : '/') + url;
+}
+
+// ── Badge injection ─────────────────────────────────────────────────
+
+function injectBadgesForSelector(
+ selector: string,
+ dataMap: Map,
+ getStatus: (item: T) => StatusInfo,
+ hiddenUrls: Set,
+ hiddenStatus: StatusInfo,
+) {
+ document.querySelectorAll(selector).forEach((activity) => {
+ const link = activity.querySelector('a') as HTMLAnchorElement | null;
+ const nameEl = activity.querySelector('.instancename');
+ if (!link || !nameEl) return;
+
+ const url = normalizeUrl(link.href);
+
+ if (hiddenUrls.has(url)) {
+ nameEl.appendChild(createBadge(hiddenStatus));
+ return;
+ }
+
+ const item = dataMap.get(url);
+ if (item) nameEl.appendChild(createBadge(getStatus(item)));
+ });
+}
+
+export function injectBadgesIntoDOM(vods: Vod[], assigns: Assign[], quizzes: Quiz[], hiddenUrls: Set) {
+ document.querySelectorAll(`[${BADGE_ATTR}]`).forEach((el) => el.remove());
+
+ const hidden: StatusInfo = { label: t('courseStatus.hidden'), type: 'hidden' };
+
+ injectBadgesForSelector(
+ 'li.modtype_vod:not(.dimmed) .activityinstance',
+ new Map(vods.map((v) => [v.url, v])),
+ getVodStatus,
+ hiddenUrls,
+ hidden,
+ );
+
+ injectBadgesForSelector(
+ 'li.modtype_assign:not(.dimmed) .activityinstance',
+ new Map(assigns.map((a) => [a.url, a])),
+ (a) => getDueDateStatus(a.isSubmit, a.dueDate, 'assign'),
+ hiddenUrls,
+ hidden,
+ );
+
+ injectBadgesForSelector(
+ 'li.modtype_quiz:not(.dimmed) .activityinstance',
+ new Map(quizzes.map((q) => [q.url, q])),
+ (q) => getDueDateStatus(q.isSubmit, q.dueDate, 'quiz'),
+ hiddenUrls,
+ hidden,
+ );
+}
diff --git a/src/lib/courseStatus/currentWeek.ts b/src/lib/courseStatus/currentWeek.ts
new file mode 100644
index 0000000..2129039
--- /dev/null
+++ b/src/lib/courseStatus/currentWeek.ts
@@ -0,0 +1,128 @@
+import i18n from '@/i18n';
+
+const CURRENT_WEEK_ATTR = 'data-dotbugi-current-week';
+const SCROLL_BTN_ID = 'dotbugi-scroll-current-week';
+
+const MONTH_NAMES: Record = {
+ january: 1, february: 2, march: 3, april: 4, may: 5, june: 6,
+ july: 7, august: 8, september: 9, october: 10, november: 11, december: 12,
+};
+
+const t = (key: string) => i18n.t(key, { ns: 'common' });
+
+// ── Date parsing ────────────────────────────────────────────────────
+
+function inferYearFromSection(section: Element): number | null {
+ const m = section.querySelector('.text-ubstrap')?.textContent?.trim()?.match(/(\d{4})-/);
+ return m ? parseInt(m[1]) : null;
+}
+
+function inferYear(sections: NodeListOf): number {
+ for (const s of sections) {
+ const y = inferYearFromSection(s);
+ if (y) return y;
+ }
+ return new Date().getFullYear();
+}
+
+function parseLocalizedDate(str: string, year: number): Date | null {
+ let m: RegExpMatchArray | null;
+
+ // Korean: 3월18일 / Chinese: 03月04日
+ m = str.match(/(\d{1,2})월\s*(\d{1,2})일/) ?? str.match(/(\d{1,2})月(\d{1,2})日/);
+ if (m) return new Date(year, parseInt(m[1]) - 1, parseInt(m[2]));
+
+ // Japanese: 03/04
+ m = str.match(/^(\d{1,2})\/(\d{1,2})$/);
+ if (m) return new Date(year, parseInt(m[1]) - 1, parseInt(m[2]));
+
+ // English: 04 March
+ m = str.match(/(\d{1,2})\s+([A-Za-z]+)/);
+ if (m && MONTH_NAMES[m[2].toLowerCase()]) {
+ return new Date(year, MONTH_NAMES[m[2].toLowerCase()] - 1, parseInt(m[1]));
+ }
+
+ return null;
+}
+
+function parseSectionDateRange(text: string, year: number): { start: Date; end: Date } | null {
+ const match = text.match(/\[([^\]]+)\]/);
+ if (!match) return null;
+
+ const [startStr, endStr] = match[1].split(/\s*-\s*/);
+ if (!startStr || !endStr) return null;
+
+ const start = parseLocalizedDate(startStr.trim(), year);
+ const end = parseLocalizedDate(endStr.trim(), year);
+ if (!start || !end) return null;
+
+ end.setHours(23, 59, 59);
+ return { start, end };
+}
+
+// ── Highlight & scroll button ───────────────────────────────────────
+
+export function highlightCurrentWeek() {
+ const now = new Date();
+ const sections = document.querySelectorAll('li[id^="section-"]');
+ const fallbackYear = inferYear(sections);
+ let currentWeekTarget: Element | null = null;
+
+ for (const section of sections) {
+ if (section.querySelector(`[${CURRENT_WEEK_ATTR}]`)) continue;
+
+ const year = inferYearFromSection(section) ?? fallbackYear;
+ const name = section.querySelector('.sectionname')?.textContent?.trim();
+ if (!name) continue;
+
+ const range = parseSectionDateRange(name, year);
+ if (!range || now < range.start || now > range.end) continue;
+
+ currentWeekTarget = section;
+
+ const content = section.querySelector('.content') as HTMLElement | null;
+ if (!content) continue;
+
+ content.style.borderLeft = '4px solid #3b82f6';
+ content.style.paddingLeft = '12px';
+ content.style.borderRadius = '4px';
+
+ const marker = document.createElement('span');
+ marker.setAttribute(CURRENT_WEEK_ATTR, '');
+ marker.textContent = t('courseStatus.currentWeek');
+ marker.style.cssText = `
+ display:inline-flex;align-items:center;padding:2px 8px;margin-left:8px;
+ border-radius:9999px;font-size:11px;font-weight:600;line-height:1.4;
+ background:#eff6ff;color:#1d4ed8;border:1px solid #bfdbfe;
+ white-space:nowrap;vertical-align:middle;
+ `;
+
+ section.querySelector('.sectionname span')?.appendChild(marker);
+ }
+
+ if (currentWeekTarget && !document.getElementById(SCROLL_BTN_ID)) {
+ const target = currentWeekTarget;
+ const btn = document.createElement('button');
+ btn.id = SCROLL_BTN_ID;
+ btn.textContent = t('courseStatus.goToCurrentWeek');
+ btn.style.cssText = `
+ position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:9999;
+ display:inline-flex;align-items:center;gap:6px;padding:10px 18px;
+ border-radius:24px;font-size:13px;font-weight:600;color:#fff;
+ background:#3b82f6;border:none;cursor:pointer;transition:all 0.3s;
+ box-shadow:0 4px 12px rgba(59,130,246,0.4);
+ `;
+ btn.addEventListener('mouseenter', () => {
+ btn.style.background = '#2563eb';
+ btn.style.boxShadow = '0 6px 16px rgba(59,130,246,0.5)';
+ });
+ btn.addEventListener('mouseleave', () => {
+ btn.style.background = '#3b82f6';
+ btn.style.boxShadow = '0 4px 12px rgba(59,130,246,0.4)';
+ });
+ btn.addEventListener('click', () => {
+ target.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ });
+ document.body.appendChild(btn);
+ }
+}
diff --git a/src/lib/courseStatus/index.ts b/src/lib/courseStatus/index.ts
new file mode 100644
index 0000000..f5f3847
--- /dev/null
+++ b/src/lib/courseStatus/index.ts
@@ -0,0 +1,95 @@
+import { CourseBase, Quiz } from '@/types';
+import { scrapeCourseData } from '@/lib/fetchCourseData';
+import { mergeVodWithAttendance, mergeDueDateItems } from '@/lib/transformCourseData';
+
+import {
+ load,
+ loadCourseData,
+ loadAllData,
+ saveCourseData,
+ cleanupExpiredTempCourses,
+ registerTempCourse,
+ isFetchCacheValid,
+ setFetchCache,
+} from './storage';
+import { injectBadgesIntoDOM } from './badge';
+import { highlightCurrentWeek } from './currentWeek';
+import { showStatusBar } from './statusBar';
+
+// Course info from DOM
+
+function parseCourseInfoFromDOM(courseId: string): CourseBase {
+ const prof = document.querySelector('.course-header .media-heading')?.textContent?.trim() ?? '';
+ const courseTitle =
+ document.querySelector('.coursename h1 a')?.textContent?.trim() ??
+ document.querySelector('.page-header-headings h1')?.textContent?.trim() ??
+ '';
+ return { courseId, courseTitle, prof };
+}
+
+// Fetch & inject
+
+async function fetchAndInject(courseId: string, course: CourseBase, isTracked: boolean, isCommunity: boolean) {
+ showStatusBar('loading');
+
+ const [hiddenUrls, cachedQuizzes] = await Promise.all([
+ load('hiddenTaskUrls'),
+ load('quiz').then((all) => (all ?? []).filter((q) => q.courseId === courseId)),
+ ]);
+
+ try {
+ const scraped = await scrapeCourseData(courseId, cachedQuizzes, isCommunity);
+ const newVods = mergeVodWithAttendance(course, scraped.vodDataArray, scraped.vodAttendanceArray);
+ const newAssigns = mergeDueDateItems(course, scraped.assignDataArray);
+ const newQuizzes = mergeDueDateItems(course, scraped.quizDataArray);
+
+ const all = await loadAllData();
+ saveCourseData(courseId, newVods, newAssigns, newQuizzes, all);
+
+ if (!isTracked) await registerTempCourse(courseId);
+
+ injectBadgesIntoDOM(newVods, newAssigns, newQuizzes, new Set(hiddenUrls ?? []));
+ setFetchCache(courseId);
+ showStatusBar('success');
+ } catch (error) {
+ console.error('[Dotbugi] 강의 데이터 로드 오류:', error);
+ showStatusBar('error');
+ }
+}
+
+// Main entry point
+
+export async function injectCourseStatus() {
+ const match = window.location.href.match(/\/course\/view\.php\?id=(\d+)/);
+ if (!match) return;
+
+ const courseId = match[1];
+
+ highlightCurrentWeek();
+ await cleanupExpiredTempCourses();
+
+ const [trackedIds, communityIds] = await Promise.all([
+ load('trackedCourseIds'),
+ load('communityIds'),
+ ]);
+ const isTracked = trackedIds?.includes(courseId) ?? false;
+ const isCommunity = communityIds?.includes(courseId) ?? false;
+
+ // 뒤로가기/앞으로가기 → 캐시 사용
+ if (isFetchCacheValid(courseId)) {
+ const data = await loadCourseData(courseId);
+ injectBadgesIntoDOM(data.vods, data.assigns, data.quizzes, data.hiddenUrls);
+ return;
+ }
+
+ // 트래킹 강의 → 저장된 데이터로 빠른 초기 렌더링
+ if (isTracked) {
+ const data = await loadCourseData(courseId);
+ if (data.vods.length || data.assigns.length || data.quizzes.length) {
+ injectBadgesIntoDOM(data.vods, data.assigns, data.quizzes, data.hiddenUrls);
+ }
+ }
+
+ // 최신 데이터 fetch
+ fetchAndInject(courseId, parseCourseInfoFromDOM(courseId), isTracked, isCommunity);
+}
diff --git a/src/lib/courseStatus/statusBar.ts b/src/lib/courseStatus/statusBar.ts
new file mode 100644
index 0000000..785701c
--- /dev/null
+++ b/src/lib/courseStatus/statusBar.ts
@@ -0,0 +1,49 @@
+import i18n from '@/i18n';
+
+const STATUS_BAR_ID = 'dotbugi-status-bar';
+const STATUS_BAR_HEIGHT = 36;
+
+const t = (key: string) => i18n.t(key, { ns: 'common' });
+
+const STATUS_BAR_STYLES: Record = {
+ loading: { bg: '#eff6ff', text: '#1d4ed8' },
+ success: { bg: '#f0fdf4', text: '#166534' },
+ error: { bg: '#fef2f2', text: '#991b1b' },
+};
+
+export function showStatusBar(state: 'loading' | 'success' | 'error') {
+ let bar = document.getElementById(STATUS_BAR_ID);
+ if (!bar) {
+ bar = document.createElement('div');
+ bar.id = STATUS_BAR_ID;
+ bar.style.cssText = `
+ position:fixed;top:0;left:0;right:0;z-index:99999;
+ height:0;opacity:0;overflow:hidden;
+ display:flex;align-items:center;justify-content:center;
+ gap:8px;font-size:13px;font-weight:500;
+ transition:height 0.3s ease,opacity 0.3s ease;
+ `;
+ document.body.appendChild(bar);
+ }
+
+ const { bg, text } = STATUS_BAR_STYLES[state];
+ bar.style.background = bg;
+ bar.style.color = text;
+ bar.style.height = `${STATUS_BAR_HEIGHT}px`;
+ bar.style.opacity = '1';
+ bar.textContent = t(`courseStatus.bar.${state}`);
+
+ const navbar = document.querySelector('.navbar-fixed-top');
+ if (navbar) {
+ navbar.style.transition = 'top 0.3s ease';
+ navbar.style.top = `${STATUS_BAR_HEIGHT}px`;
+ }
+
+ if (state !== 'loading') {
+ setTimeout(() => {
+ bar!.style.height = '0';
+ bar!.style.opacity = '0';
+ if (navbar) navbar.style.top = '0';
+ }, 2000);
+ }
+}
diff --git a/src/lib/courseStatus/storage.ts b/src/lib/courseStatus/storage.ts
new file mode 100644
index 0000000..94ccccc
--- /dev/null
+++ b/src/lib/courseStatus/storage.ts
@@ -0,0 +1,107 @@
+import { Vod, Assign, Quiz } from '@/types';
+import { loadDataFromStorage, saveDataToStorage } from '@/lib/storage';
+
+const TEMP_COURSES_KEY = 'tempCourseFetchTimes';
+const TEMP_COURSE_TTL_MS = 60 * 1000;
+const FETCH_CACHE_TTL_MS = 2 * 60 * 1000;
+
+type TempCourseFetchTimes = Record;
+
+// ── Generic loader ──────────────────────────────────────────────────
+
+export function load(key: string): Promise {
+ return new Promise((resolve) => loadDataFromStorage(key, resolve));
+}
+
+// ── Course data ─────────────────────────────────────────────────────
+
+export async function loadCourseData(courseId: string) {
+ const [vods, assigns, quizzes, hidden] = await Promise.all([
+ load('vod'),
+ load('assign'),
+ load('quiz'),
+ load('hiddenTaskUrls'),
+ ]);
+
+ const byCourse = (items: T[] | null) =>
+ (items ?? []).filter((item) => item.courseId === courseId);
+
+ return {
+ vods: byCourse(vods),
+ assigns: byCourse(assigns),
+ quizzes: byCourse(quizzes),
+ hiddenUrls: new Set(hidden ?? []),
+ };
+}
+
+export async function loadAllData() {
+ const [vods, assigns, quizzes] = await Promise.all([
+ load('vod'),
+ load('assign'),
+ load('quiz'),
+ ]);
+ return { vods: vods ?? [], assigns: assigns ?? [], quizzes: quizzes ?? [] };
+}
+
+export function saveCourseData(
+ courseId: string,
+ newVods: Vod[],
+ newAssigns: Assign[],
+ newQuizzes: Quiz[],
+ all: { vods: Vod[]; assigns: Assign[]; quizzes: Quiz[] },
+) {
+ const exclude = (items: T[]) =>
+ items.filter((item) => item.courseId !== courseId);
+
+ saveDataToStorage('vod', [...exclude(all.vods), ...newVods]);
+ saveDataToStorage('assign', [...exclude(all.assigns), ...newAssigns]);
+ saveDataToStorage('quiz', [...exclude(all.quizzes), ...newQuizzes]);
+}
+
+// ── Temp course cleanup ─────────────────────────────────────────────
+
+export async function cleanupExpiredTempCourses() {
+ const times = (await load(TEMP_COURSES_KEY)) ?? {};
+ const now = Date.now();
+ const expired = Object.entries(times)
+ .filter(([, at]) => now - at >= TEMP_COURSE_TTL_MS)
+ .map(([id]) => id);
+
+ if (expired.length === 0) return;
+
+ const expiredSet = new Set(expired);
+ const all = await loadAllData();
+
+ const notExpired = (items: T[]) =>
+ items.filter((item) => !expiredSet.has(item.courseId));
+
+ saveDataToStorage('vod', notExpired(all.vods));
+ saveDataToStorage('assign', notExpired(all.assigns));
+ saveDataToStorage('quiz', notExpired(all.quizzes));
+
+ for (const id of expired) delete times[id];
+ saveDataToStorage(TEMP_COURSES_KEY, times);
+}
+
+export async function registerTempCourse(courseId: string) {
+ const times = (await load(TEMP_COURSES_KEY)) ?? {};
+ times[courseId] = Date.now();
+ saveDataToStorage(TEMP_COURSES_KEY, times);
+}
+
+// ── Fetch cache (sessionStorage) ────────────────────────────────────
+
+function isBackForwardNavigation(): boolean {
+ const [entry] = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
+ return entry?.type === 'back_forward';
+}
+
+export function isFetchCacheValid(courseId: string): boolean {
+ if (!isBackForwardNavigation()) return false;
+ const raw = sessionStorage.getItem(`dotbugi_fetch_${courseId}`);
+ return !!raw && Date.now() - parseInt(raw, 10) < FETCH_CACHE_TTL_MS;
+}
+
+export function setFetchCache(courseId: string) {
+ sessionStorage.setItem(`dotbugi_fetch_${courseId}`, Date.now().toString());
+}
diff --git a/src/lib/dateUtils.ts b/src/lib/dateUtils.ts
new file mode 100644
index 0000000..02d8753
--- /dev/null
+++ b/src/lib/dateUtils.ts
@@ -0,0 +1,121 @@
+import i18n from '@/i18n';
+import { TimeDifferenceResult } from '@/types';
+
+const MS_PER_MINUTE = 1000 * 60;
+const MS_PER_HOUR = MS_PER_MINUTE * 60;
+const MS_PER_DAY = MS_PER_HOUR * 24;
+
+const t = (key: string, options?: Record) => i18n.t(key, { ns: 'common', ...options });
+
+/** 하이픈 구분 날짜 문자열을 Date로 변환 (Safari 호환) */
+export function parseDate(str: string): Date {
+ return new Date(str.replace(/-/g, '/'));
+}
+
+export function isCurrentDateInRange(dateRange: string | null) {
+ if (!dateRange || !dateRange.includes(' ~ ')) return false;
+
+ const [startStr, endStr] = dateRange.split(' ~ ');
+ if (!startStr || !endStr) return false;
+
+ const startDate = parseDate(startStr);
+ const endDate = parseDate(endStr);
+ const now = new Date();
+
+ return now >= startDate && now <= endDate;
+}
+
+export function isCurrentDateByDate(date: string | null) {
+ if (!date || date.length <= 1) return false;
+ return new Date() <= new Date(date);
+}
+
+export function isWithinSevenDays(date: string) {
+ const diffDays = (new Date(date).getTime() - Date.now()) / MS_PER_DAY;
+ return diffDays <= 7 && diffDays >= 0;
+}
+
+export function extractEndDate(range: string | null): string | null {
+ if (!range) return null;
+ return range.split(' ~ ')[1] ?? null;
+}
+
+export function calculateDueDate(dueDate: string | null): TimeDifferenceResult {
+ if (!dueDate) {
+ return {
+ message: t('date.noInfo'),
+ status: 'noInfo',
+ borderColor: 'border-amber-500',
+ borderLeftColor: 'border-l-amber-500',
+ textColor: 'text-amber-500',
+ };
+ }
+
+ const now = new Date();
+ const endDate = new Date(dueDate);
+
+ if (isNaN(endDate.getTime())) {
+ return { message: 'Invalid date format', status: 'invalid', borderColor: 'gray', borderLeftColor: 'gray', textColor: 'black' };
+ }
+
+ if (now >= endDate) {
+ return {
+ message: t('date.expired'),
+ status: 'expired',
+ borderColor: 'border-red-950',
+ borderLeftColor: 'border-l-red-950',
+ textColor: 'text-red-950',
+ };
+ }
+
+ const timeDiff = endDate.getTime() - now.getTime();
+ const days = Math.floor(timeDiff / MS_PER_DAY);
+
+ if (days >= 1) {
+ return {
+ message: t('date.daysLater', { days }),
+ status: 'daysLeft',
+ borderColor: 'border-amber-500',
+ borderLeftColor: 'border-l-amber-500',
+ textColor: 'text-amber-500',
+ };
+ }
+
+ const hours = Math.floor(timeDiff / MS_PER_HOUR);
+ const minutes = Math.floor(timeDiff / MS_PER_MINUTE);
+ return {
+ message: hours !== 0 ? t('date.hoursLater', { hours }) : t('date.minutesLater', { minutes }),
+ status: 'urgent',
+ borderColor: 'border-red-700',
+ borderLeftColor: 'border-l-red-700',
+ textColor: 'text-red-700',
+ };
+}
+
+export function calculateRemainingTime(endTime: string | null) {
+ if (!endTime) return t('date.noInfo');
+
+ const timeDiff = new Date(endTime).getTime() - Date.now();
+ const daysLeft = Math.floor(timeDiff / MS_PER_DAY);
+ const hoursLeft = Math.floor((timeDiff % MS_PER_DAY) / MS_PER_HOUR);
+ const minutesLeft = Math.floor((timeDiff % MS_PER_HOUR) / MS_PER_MINUTE);
+
+ if (daysLeft < 0 || hoursLeft < 0 || minutesLeft < 0) return t('date.expired');
+
+ if (daysLeft > 0) return t('date.remaining', { days: daysLeft, hours: hoursLeft, minutes: minutesLeft });
+ if (hoursLeft > 0) return t('date.remainingHoursMinutes', { hours: hoursLeft, minutes: minutesLeft });
+ return t('date.remainingMinutes', { minutes: minutesLeft });
+}
+
+export function timeAgo(givenTimestamp: number) {
+ const diffMs = Date.now() - givenTimestamp;
+
+ const days = Math.floor(diffMs / MS_PER_DAY);
+ const hours = Math.floor((diffMs % MS_PER_DAY) / MS_PER_HOUR);
+ const minutes = Math.floor((diffMs % MS_PER_HOUR) / MS_PER_MINUTE);
+
+ if (days === 0 && hours === 0 && minutes === 0) return t('date.justNow');
+ if (days > 0) return t('date.daysAgo', { days, hours, minutes });
+ if (hours > 0) return t('date.hoursAgo', { hours, minutes });
+ return t('date.minutesAgo', { minutes });
+}
diff --git a/src/lib/deduplicateInto.ts b/src/lib/deduplicateInto.ts
new file mode 100644
index 0000000..8678e4a
--- /dev/null
+++ b/src/lib/deduplicateInto.ts
@@ -0,0 +1,17 @@
+/**
+ * 키 생성 함수 기반으로 중복 제거하면서 배열에 추가
+ */
+export function deduplicateInto(
+ target: T[],
+ source: T[],
+ seen: Set,
+ getKey: (item: T) => string,
+): void {
+ for (const item of source) {
+ const key = getKey(item);
+ if (!seen.has(key)) {
+ seen.add(key);
+ target.push(item);
+ }
+ }
+}
diff --git a/src/lib/fetchAssign.ts b/src/lib/fetchAssign.ts
index d3f902a..7909501 100644
--- a/src/lib/fetchAssign.ts
+++ b/src/lib/fetchAssign.ts
@@ -1,55 +1,39 @@
-export const fetchAssign = async (link: string) => {
- try {
- const response = await fetch(link, {
- method: 'GET',
- credentials: 'include',
- });
+import { fetchHtml, getText, getHref } from './fetchHtml';
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
+// 과제 테이블 컬럼 (generaltable)
+// [주(c0)] [과제(c1)] [종료 일시(c2)] [제출(c3)] [성적(c4)]
+const COL = {
+ WEEK: '.cell.c0',
+ TITLE_LINK: '.cell.c1 a',
+ DUE_DATE: '.cell.c2',
+ SUBMIT_STATUS: '.cell.c3',
+} as const;
- const html = await response.text();
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
+import { NOT_SUBMITTED, normalizeLmsDate } from './lmsKeywords';
- const headerMap: Record = {};
- const headers = Array.from(doc.querySelectorAll('table.generaltable thead tr th'));
- headers.forEach((header) => {
- const text = header.textContent?.trim();
- const className = header.className.match(/c\d+/)?.[0];
- if (text && className) {
- if (text.includes('주제')) headerMap['subject'] = '.cell.' + className;
- else if (text.includes('과제')) {
- headerMap['title'] = '.cell.' + className;
- headerMap['url'] = headerMap['title'] + ' a';
- } else if (text.includes('종료 일시')) headerMap['dueDate'] = '.cell.' + className;
- else if (text.includes('제출')) headerMap['isSubmit'] = '.cell.' + className;
- }
- });
+export const fetchAssign = async (link: string) => {
+ try {
+ const doc = await fetchHtml(link);
+ const rows = doc.querySelectorAll('table.generaltable tbody tr');
- let subject: string;
- const rows = Array.from(doc.querySelectorAll('table.generaltable tbody tr'));
- const assignments = rows
- .map((row) => {
- const title =
- row.querySelector(headerMap.title)?.textContent?.trim() ||
- row.querySelector(headerMap.assign)?.textContent?.trim() ||
- null;
- const sbj = row.querySelector(headerMap.subject)?.textContent?.trim() || '';
- const url = (row.querySelector(headerMap.url) as HTMLAnchorElement)?.href || null;
- const dueDate = row.querySelector(headerMap.dueDate)?.textContent?.trim() || null;
- const isSubmit = row.querySelector(headerMap.isSubmit)?.textContent?.trim() === '미제출' ? false : true;
+ let lastWeekLabel = '';
- if (sbj.length !== 0) subject = sbj;
- if (!title || !url || !dueDate) return null;
- return { subject, title, url, dueDate, isSubmit };
- })
- .filter((assign) => assign !== null);
+ return Array.from(rows).flatMap((row) => {
+ const weekLabel = getText(row, COL.WEEK);
+ if (weekLabel) lastWeekLabel = weekLabel;
- return assignments;
+ const title = getText(row, COL.TITLE_LINK);
+ const url = getHref(row, COL.TITLE_LINK);
+ const rawDueDate = getText(row, COL.DUE_DATE)?.trim();
+ if (!title || !url) return [];
+ const dueDate = rawDueDate && rawDueDate !== '-' ? normalizeLmsDate(rawDueDate) ?? null : null;
+
+ const submitText = getText(row, COL.SUBMIT_STATUS) ?? '';
+ const isSubmit = !NOT_SUBMITTED.some((keyword) => submitText.includes(keyword));
+ return { subject: lastWeekLabel, title, url, dueDate, isSubmit };
+ });
} catch (error) {
- console.error('Fetch error:', error);
+ console.error('[Dotbugi] 과제 조회 오류:', error);
throw error;
}
};
diff --git a/src/lib/fetchCourseData.ts b/src/lib/fetchCourseData.ts
index aa1e1b8..34490d7 100644
--- a/src/lib/fetchCourseData.ts
+++ b/src/lib/fetchCourseData.ts
@@ -1,27 +1,72 @@
+import { VodAttendanceData } from '@/types';
import { fetchVodAttendance } from './fetchVodAttendance';
-import { fetchIndexPage } from './fetchIndexPage';
-import { getAssignPageLink, getIndexPageLink, getQuizPageLink, getVodPageLink } from '@/constants/constant';
+import { fetchVodProgress } from './fetchVodProgress';
+import { fetchVodList } from './fetchVodList';
+import { getAssignPageLink, getVodProgressPageLink, getIndexPageLink, getQuizPageLink, getVodPageLink } from '@/constants/links';
import { fetchAssign } from './fetchAssign';
import { fetchQuiz } from './fetchQuiz';
+import { Quiz } from '@/types';
-export const requestData = async (id: string) => {
- const VOD_LINK = getVodPageLink(id);
- const INDEX_LINK = getIndexPageLink(id);
- const ASSIGN_LINK = getAssignPageLink(id);
- const QUIZ_LINK = getQuizPageLink(id);
+/**
+ * 일반 강좌: user_progress_a.php 출석 데이터에 user_progress.php 시간 데이터를 병합
+ * (일괄출석인정 등 기존 출석 판정 유지 + 시청중 배지용 시간 데이터 추가)
+ */
+function mergeTimeData(
+ attendance: VodAttendanceData[],
+ progress: VodAttendanceData[],
+): VodAttendanceData[] {
+ const progressByKey = new Map();
+ for (const p of progress) {
+ progressByKey.set(`${p.title}-${p.week}`, p);
+ }
+
+ return attendance.map((att) => {
+ const prog = progressByKey.get(`${att.title}-${att.week}`);
+ if (!prog) return att;
+ return {
+ ...att,
+ requiredTime: prog.requiredTime,
+ watchedTime: prog.watchedTime,
+ };
+ });
+}
+
+export const scrapeCourseData = async (
+ courseId: string,
+ cachedQuizzes?: Quiz[],
+ isCommunity?: boolean,
+) => {
+ // 캐시된 퀴즈의 제출 상태 맵 생성 (url → isSubmit)
+ const cachedSubmitMap = cachedQuizzes
+ ? new Map(cachedQuizzes.map((q) => [q.url, q.isSubmit]))
+ : undefined;
try {
- const [vodAttendanceArray, vodDataArray, assignDataArray, quizDataArray] = await Promise.all([
- fetchVodAttendance(VOD_LINK),
- fetchIndexPage(INDEX_LINK),
- fetchAssign(ASSIGN_LINK),
- fetchQuiz(QUIZ_LINK),
+ // 커뮤니티: user_progress.php만 사용
+ // 일반: user_progress_a.php(출석) + user_progress.php(시간 데이터) 병합
+ const progressLink = getVodProgressPageLink(courseId);
+
+ const [vodAttendanceRaw, vodProgress, vodList, assignList, quizList] = await Promise.all([
+ isCommunity ? Promise.resolve(null) : fetchVodAttendance(getVodPageLink(courseId)),
+ fetchVodProgress(progressLink),
+ fetchVodList(getIndexPageLink(courseId)),
+ fetchAssign(getAssignPageLink(courseId)),
+ fetchQuiz(getQuizPageLink(courseId), cachedSubmitMap),
]);
- console.info('[Dotbugi]', vodAttendanceArray, vodDataArray, assignDataArray, quizDataArray);
- return { vodAttendanceArray, vodDataArray, assignDataArray, quizDataArray };
+ const vodAttendance = isCommunity || !vodAttendanceRaw
+ ? vodProgress
+ : mergeTimeData(vodAttendanceRaw, vodProgress);
+
+ console.info('[Dotbugi]', vodAttendance, vodList, assignList, quizList);
+ return {
+ vodAttendanceArray: vodAttendance,
+ vodDataArray: vodList,
+ assignDataArray: assignList,
+ quizDataArray: quizList,
+ };
} catch (error) {
- console.error('Error while fetching data:', error);
+ console.error('[Dotbugi] 강의 데이터 스크래핑 오류:', error);
throw error;
}
};
diff --git a/src/lib/fetchHtml.ts b/src/lib/fetchHtml.ts
new file mode 100644
index 0000000..c4e7558
--- /dev/null
+++ b/src/lib/fetchHtml.ts
@@ -0,0 +1,26 @@
+/**
+ * LMS 페이지를 fetch하고 DOM으로 파싱하여 반환
+ */
+export async function fetchHtml(link: string): Promise {
+ const response = await fetch(link, {
+ method: 'GET',
+ credentials: 'include',
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${link}`);
+ }
+
+ const html = await response.text();
+ return new DOMParser().parseFromString(html, 'text/html');
+}
+
+/** 셀렉터로 요소의 텍스트 추출. 없으면 null */
+export function getText(parent: Element, selector: string): string | null {
+ return parent.querySelector(selector)?.textContent?.trim() || null;
+}
+
+/** 셀렉터로 a 태그의 href 추출. 없으면 null */
+export function getHref(parent: Element, selector: string): string | null {
+ return parent.querySelector(selector)?.href || null;
+}
diff --git a/src/lib/fetchIndexPage.ts b/src/lib/fetchIndexPage.ts
deleted file mode 100644
index 63a039b..0000000
--- a/src/lib/fetchIndexPage.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-export const fetchIndexPage = async (link: string) => {
- try {
- const response = await fetch(link, {
- method: 'GET',
- credentials: 'include',
- });
-
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
-
- const html = await response.text();
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
-
- const weeks = Array.from(doc.querySelectorAll('#region-main > div > div > div.total_sections > div > ul > li'));
-
- const vods = weeks
- .map((week, index) => {
- const subject = week.querySelector('.content .sectionname')?.textContent?.trim() || index + '주차';
- const contents = Array.from(week.querySelectorAll('.content .vod .activityinstance'));
- const vodsWeekly = contents
- .filter((item) => !item.closest('.dimmed'))
- .map((item) => {
- const week = index + 1;
- const instancename = item.querySelector('.instancename');
- instancename?.querySelector('.accesshide')?.remove();
- const title = instancename?.textContent?.trim() || null;
- const url = item.querySelector('a')?.getAttribute('href') || null;
- const range = item.querySelector('.text-ubstrap')?.textContent?.trim() || '';
- const length = item.querySelector('.text-info')?.textContent?.replace(',', '').trim() || '';
-
- if (!title || !url || !range) return null;
- return { week, subject, title, url, range, length };
- })
- .filter((item) => item !== null);
-
- if (!vodsWeekly || vodsWeekly.length === 0) return null;
- return vodsWeekly;
- })
- .filter((week) => week !== null)
- .flat();
-
- return vods;
- } catch (error) {
- console.error('Fetch error:', error);
- throw error;
- }
-};
diff --git a/src/lib/fetchQuiz.ts b/src/lib/fetchQuiz.ts
index e5107ce..65feaea 100644
--- a/src/lib/fetchQuiz.ts
+++ b/src/lib/fetchQuiz.ts
@@ -1,58 +1,96 @@
-export const fetchQuiz = async (link: string) => {
+import { fetchHtml, getText } from './fetchHtml';
+import { BASE_LINK } from '@/constants/links';
+import { normalizeLmsDate } from './lmsKeywords';
+import { isCurrentDateByDate } from './dateUtils';
+
+// 퀴즈 테이블 컬럼 (generaltable)
+// [주(c0)] [제목(c1)] [종료 일시(c2)] [성적(c3)]
+const COL = {
+ WEEK: '.cell.c0',
+ TITLE_LINK: '.cell.c1 a',
+ DUE_DATE: '.cell.c2',
+} as const;
+
+/**
+ * 상대/절대 경로를 퀴즈 모듈 절대 URL로 변환
+ * view.php?id=123 → https://learn.hansung.ac.kr/mod/quiz/view.php?id=123
+ */
+function toQuizUrl(rawHref: string): string {
+ if (rawHref.startsWith('http')) return rawHref;
+ return `${BASE_LINK}/mod/quiz/${rawHref}`;
+}
+
+/**
+ * 퀴즈 상세 페이지에서 제출 여부를 확인
+ * quizattemptsummary 테이블에 행이 있으면 제출된 것으로 판단
+ */
+async function fetchQuizSubmitStatus(quizUrl: string): Promise {
try {
- const response = await fetch(link, {
- method: 'GET',
- credentials: 'include',
- });
-
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
-
- const html = await response.text();
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
-
- const headerMap: Record = {};
- const headers = Array.from(doc.querySelectorAll('table.generaltable thead tr th'));
- headers.forEach((header) => {
- const text = header.textContent?.trim();
- const className = header.className.match(/c\d+/)?.[0];
- if (text && className) {
- if (text.includes('주제')) headerMap['subject'] = '.cell.' + className;
- else if (text.includes('제목')) {
- headerMap['title'] = '.cell.' + className;
- headerMap['url'] = headerMap['title'] + ' a';
- } else if (text.includes('종료 일시')) headerMap['dueDate'] = '.cell.' + className;
- }
- });
-
- let subject: string;
- const rows = Array.from(doc.querySelectorAll('table.generaltable tbody tr'));
- const quizzes = rows
- .map((row) => {
- const sbj = row.querySelector(headerMap.subject)?.textContent?.trim() || '';
- const title = row.querySelector(headerMap.title)?.textContent?.trim() || null;
- let url = (row.querySelector(headerMap.url) as HTMLAnchorElement)?.href || null;
- const dueDate = row.querySelector(headerMap.dueDate)?.textContent?.trim() || null;
-
- if (sbj.length !== 0) subject = sbj;
-
- if (url) {
- const index = url.indexOf('view');
- url = url.slice(0, index) + 'mod/quiz/' + url.slice(index);
- }
+ const doc = await fetchHtml(quizUrl);
+ const rows = doc.querySelectorAll('table.quizattemptsummary tbody tr');
+ return rows.length > 0;
+ } catch {
+ return false;
+ }
+}
+
+type QuizItem = {
+ title: string;
+ subject: string;
+ url: string;
+ dueDate: string | null;
+ isSubmit: boolean;
+};
+
+/**
+ * 퀴즈 목록 + 제출 여부 조회
+ * @param cachedSubmitMap 이전에 저장된 퀴즈별 제출 상태 (key: url)
+ * 마감된 퀴즈는 상세 페이지를 다시 fetch하지 않고 캐시된 값을 사용
+ */
+export const fetchQuiz = async (
+ link: string,
+ cachedSubmitMap?: Map,
+) => {
+ try {
+ const doc = await fetchHtml(link);
+ const rows = doc.querySelectorAll('table.generaltable tbody tr');
+
+ let lastWeekLabel = '';
+
+ const quizItems: Omit[] = Array.from(rows)
+ .flatMap((row) => {
+ const weekLabel = getText(row, COL.WEEK);
+ if (weekLabel) lastWeekLabel = weekLabel;
+
+ const titleLink = row.querySelector(COL.TITLE_LINK);
+ const rawDueDate = getText(row, COL.DUE_DATE)?.trim();
+ if (!titleLink) return [];
+ const dueDate = rawDueDate && rawDueDate !== '-' ? normalizeLmsDate(rawDueDate) ?? null : null;
- if (title && url && dueDate) {
- return { title, subject, url, dueDate };
+ const title = titleLink.textContent?.trim();
+ const rawHref = titleLink.getAttribute('href');
+ if (!title || !rawHref) return [];
+
+ return { title, subject: lastWeekLabel, url: toQuizUrl(rawHref), dueDate };
+ });
+
+ // 마감 전 퀴즈만 상세 페이지 fetch, 마감된 퀴즈는 캐시 사용
+ const results: QuizItem[] = await Promise.all(
+ quizItems.map(async (item) => {
+ const isExpired = item.dueDate ? !isCurrentDateByDate(item.dueDate) : false;
+
+ if (isExpired && cachedSubmitMap?.has(item.url)) {
+ return { ...item, isSubmit: cachedSubmitMap.get(item.url)! };
}
- return null;
- })
- .filter((quiz) => quiz !== null);
- return quizzes;
+ const isSubmit = await fetchQuizSubmitStatus(item.url);
+ return { ...item, isSubmit };
+ }),
+ );
+
+ return results;
} catch (error) {
- console.error('Fetch error:', error);
+ console.error('[Dotbugi] 퀴즈 조회 오류:', error);
throw error;
}
};
diff --git a/src/lib/fetchVodAttendance.ts b/src/lib/fetchVodAttendance.ts
index 601bdbb..2f45c0b 100644
--- a/src/lib/fetchVodAttendance.ts
+++ b/src/lib/fetchVodAttendance.ts
@@ -1,129 +1,114 @@
-import { VodAttendanceData } from '@/content/types';
+import { VodAttendanceData } from '@/types';
+import { fetchHtml } from './fetchHtml';
+
+// 출석부 테이블 컬럼 인덱스 (user_progress_table)
+// [주차(0)] [강의 자료(1)] [출석인정 요구시간(2)] [총 학습시간(3)] [출석(4)] [주차 출석(5)]
+const COL = {
+ WEEK: 0,
+ TITLE: 1,
+ ATTENDANCE: 4,
+ WEEKLY_ATTENDANCE: 5,
+} as const;
+
+import { BULK_APPROVED } from './lmsKeywords';
/**
- * 온라인 출석부 정보 가져오기
- *
- * @param link 강의 링크
- * @returns [{온라인 강의 제목, 출석 여부, 주차정보}...]
+ * rowspan/colspan이 있는 테이블 행을 평탄화된 셀 값 배열로 변환
*/
-export const fetchVodAttendance = async (link: string) => {
- try {
- const response = await fetch(link, {
- method: 'GET',
- credentials: 'include',
- });
-
- if (!response.ok) {
- throw new Error('Network response was not ok');
+function flattenRow(
+ row: Element,
+ pendingSpans: (number | undefined)[],
+ spanValues: (string | null)[],
+): (string | null)[] {
+ const cells = Array.from(row.querySelectorAll('td'));
+ const rowData: (string | null)[] = [];
+ let col = 0;
+
+ const consumePendingSpans = () => {
+ while (pendingSpans[col] && pendingSpans[col]! > 0) {
+ rowData.push(spanValues[col] || null);
+ pendingSpans[col]! -= 1;
+ col++;
}
+ };
- const html = await response.text();
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
+ consumePendingSpans();
- const headerMap: Record = {};
+ for (const cell of cells) {
+ consumePendingSpans();
- const headers = Array.from(doc.querySelectorAll('.user_progress_table > thead > tr > th'));
+ const rowspan = parseInt(cell.getAttribute('rowspan') || '1', 10);
+ const colspan = parseInt(cell.getAttribute('colspan') || '1', 10);
+ const content = cell.textContent?.trim() || '';
- headers.forEach((header, index) => {
- const text = header.textContent?.trim() || '';
- if (text === '강의 자료') headerMap['title'] = index;
- else if (text === '출석') headerMap['isAttendance'] = index;
- else if (text === '주차 출석') headerMap['weeklyAttendance'] = index;
- });
+ for (let i = 0; i < colspan; i++) {
+ rowData.push(content);
+ if (rowspan > 1) {
+ pendingSpans[col] = rowspan - 1;
+ spanValues[col] = content;
+ } else {
+ spanValues[col] = null;
+ }
+ col++;
+ }
+ }
+
+ while (col < pendingSpans.length) {
+ if (pendingSpans[col] && pendingSpans[col]! > 0) {
+ rowData.push(spanValues[col] || null);
+ pendingSpans[col]! -= 1;
+ } else {
+ rowData.push(null);
+ }
+ col++;
+ }
- const rows = Array.from(doc.querySelectorAll('.user_progress_table > tbody > tr'));
+ return rowData;
+}
- const spanTable: (number | undefined)[] = [];
- const cellValues: (string | null)[] = [];
- const vods: VodAttendanceData[] = [];
- let idx = 0;
+/**
+ * 온라인 출석부 정보 가져오기
+ */
+export const fetchVodAttendance = async (link: string) => {
+ try {
+ const doc = await fetchHtml(link);
+ const rows = doc.querySelectorAll('.user_progress_table > tbody > tr');
+
+ const pendingSpans: (number | undefined)[] = [];
+ const spanValues: (string | null)[] = [];
+ const results: VodAttendanceData[] = [];
+ let currentWeek = 0;
let lastWeeklyAttendance = '';
+
rows.forEach((row) => {
try {
- const cells = Array.from(row.querySelectorAll('td'));
- let colIndex = 0;
- const rowData: (string | null)[] = [];
-
- while (spanTable[colIndex] && spanTable[colIndex]! > 0) {
- rowData.push(cellValues[colIndex] || null);
- spanTable[colIndex]! -= 1;
- colIndex++;
- }
+ const cells = flattenRow(row, pendingSpans, spanValues);
- cells.forEach((cell) => {
- while (spanTable[colIndex] && spanTable[colIndex]! > 0) {
- rowData.push(cellValues[colIndex] || null);
- spanTable[colIndex]! -= 1;
- colIndex++;
- }
-
- const rowspan = parseInt(cell.getAttribute('rowspan') || '1', 10);
- const colspan = parseInt(cell.getAttribute('colspan') || '1', 10);
- const cellContent = cell.textContent?.trim() || '';
-
- for (let i = 0; i < colspan; i++) {
- rowData.push(cellContent);
-
- if (rowspan > 1) {
- spanTable[colIndex] = rowspan - 1;
- cellValues[colIndex] = cellContent;
- } else {
- cellValues[colIndex] = null;
- }
- colIndex++;
- }
- });
-
- while (colIndex < spanTable.length) {
- if (spanTable[colIndex] && spanTable[colIndex]! > 0) {
- rowData.push(cellValues[colIndex] || null);
- spanTable[colIndex]! -= 1;
- } else {
- rowData.push(null);
- }
- colIndex++;
- }
+ const title = cells[COL.TITLE] || '';
+ const isAttendance = cells[COL.ATTENDANCE] || '';
- let weeklyAttendance =
- headerMap['weeklyAttendance'] !== undefined ? rowData[headerMap['weeklyAttendance']] || '' : '';
+ let weeklyAttendance = cells[COL.WEEKLY_ATTENDANCE] || '';
if (weeklyAttendance) {
lastWeeklyAttendance = weeklyAttendance;
} else {
weeklyAttendance = lastWeeklyAttendance;
}
+ if (BULK_APPROVED.some((keyword) => weeklyAttendance.includes(keyword))) weeklyAttendance = 'o';
- if (weeklyAttendance.includes('일괄출석인정')) weeklyAttendance = 'o';
-
- const title = headerMap['title'] !== undefined ? rowData[headerMap['title']] || '' : '';
- const isAttendance = headerMap['isAttendance'] !== undefined ? rowData[headerMap['isAttendance']] || '' : '';
+ const parsedWeek = parseInt(cells[COL.WEEK] || '');
+ if (!isNaN(parsedWeek)) currentWeek = parsedWeek;
- let weekStr = rowData[0] || '';
- if (weekStr !== '' && !isNaN(parseInt(weekStr))) {
- idx = parseInt(weekStr);
- } else {
- weekStr = idx.toString();
- }
+ if (!title || !isAttendance) return;
- if (!title || !isAttendance) {
- return;
- }
- const week = parseInt(weekStr);
-
- vods.push({
- title,
- isAttendance,
- weeklyAttendance,
- week,
- });
+ results.push({ title, isAttendance, weeklyAttendance, week: currentWeek });
} catch (error) {
- console.error(`[Dotbugi] 영상 강의 조회 오류: ${link} ${row}`, error);
+ console.error(`[Dotbugi] 출석부 행 파싱 오류: ${link}`, error);
}
});
- return vods;
+ return results;
} catch (error) {
- console.error('Fetch error:', error);
+ console.error('[Dotbugi] 출석부 조회 오류:', error);
throw error;
}
};
diff --git a/src/lib/fetchVodList.ts b/src/lib/fetchVodList.ts
new file mode 100644
index 0000000..1176f7b
--- /dev/null
+++ b/src/lib/fetchVodList.ts
@@ -0,0 +1,42 @@
+import { fetchHtml, getText, getHref } from './fetchHtml';
+import { normalizeLmsRange } from './lmsKeywords';
+
+function parseWeekNumber(section: Element): number {
+ const match = section.id?.match(/section-(\d+)/);
+ return match ? parseInt(match[1], 10) : 0;
+}
+
+function parseVodTitle(activity: Element): string | undefined {
+ const nameEl = activity.querySelector('.instancename');
+ if (!nameEl) return;
+ const clone = nameEl.cloneNode(true) as Element;
+ clone.querySelector('.accesshide')?.remove();
+ return clone.textContent?.trim();
+}
+
+export const fetchVodList = async (link: string) => {
+ try {
+ const doc = await fetchHtml(link);
+ const sections = doc.querySelectorAll('li[id^="section-"]');
+
+ return Array.from(sections).flatMap((section) => {
+ const week = parseWeekNumber(section);
+ const subject = getText(section, '.sectionname') || `${week}`;
+ const vodActivities = section.querySelectorAll('li.modtype_vod:not(.dimmed) .activityinstance');
+
+ return Array.from(vodActivities).flatMap((activity) => {
+ const title = parseVodTitle(activity);
+ const url = getHref(activity, 'a');
+ const rawRange = getText(activity, '.text-ubstrap');
+ if (!title || !url || !rawRange) return [];
+ const range = normalizeLmsRange(rawRange)!;
+
+ const length = getText(activity, '.text-info')?.replace(',', '') ?? '';
+ return { week, subject, title, url, range, length };
+ });
+ });
+ } catch (error) {
+ console.error('[Dotbugi] VOD 목록 조회 오류:', error);
+ throw error;
+ }
+};
diff --git a/src/lib/fetchVodProgress.ts b/src/lib/fetchVodProgress.ts
new file mode 100644
index 0000000..4d08d6a
--- /dev/null
+++ b/src/lib/fetchVodProgress.ts
@@ -0,0 +1,117 @@
+import { VodAttendanceData } from '@/types';
+import { fetchHtml } from './fetchHtml';
+
+function parseTimeToSeconds(time: string): number {
+ const parts = time.split(':').map(Number);
+ if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
+ if (parts.length === 2) return parts[0] * 60 + parts[1];
+ return 0;
+}
+
+/**
+ * VOD 학습 진도 정보 가져오기 (user_progress.php)
+ *
+ * `.user_progress` 테이블에서 출석인정 요구시간과 총 학습시간을 추출.
+ * - 일반 강좌: 시청중 배지용 시간 데이터 제공 (출석 판정은 user_progress_a.php 사용)
+ * - 커뮤니티 강좌: 출석 판정 + 시간 데이터 모두 담당 (user_progress_a.php 접근 불가)
+ */
+export const fetchVodProgress = async (link: string) => {
+ try {
+ const doc = await fetchHtml(link);
+ const rows = doc.querySelectorAll('.user_progress > tbody > tr');
+
+ const results: VodAttendanceData[] = [];
+ let currentWeek = 0;
+
+ rows.forEach((row) => {
+ try {
+ const cells = Array.from(row.querySelectorAll('td'));
+ if (cells.length === 0) return;
+
+ // 주차 파싱: sectiontitle이 있는 셀 (rowspan이 있을 수 있음)
+ const sectionTitle = row.querySelector('.sectiontitle');
+ if (sectionTitle) {
+ const parsedWeek = parseInt(sectionTitle.textContent?.trim() || '', 10);
+ if (!isNaN(parsedWeek)) currentWeek = parsedWeek;
+ }
+
+ // VOD 아이콘이 있는 행만 처리 (빈 주차 건너뛰기)
+ const titleCell = cells.find((cell) => cell.querySelector('img[src*="vod"]'));
+ if (!titleCell) return;
+
+ // 제목 추출: 이미지 태그 뒤의 텍스트
+ const title = titleCell.textContent?.trim() || '';
+ if (!title) return;
+
+ // 시간 데이터 추출: 출석인정 요구시간, 총 학습시간
+ const timeCells = cells.filter((cell) => {
+ const text = cell.textContent?.trim() || '';
+ return /^\d{1,2}:\d{2}(:\d{2})?$/.test(text);
+ });
+
+ // 상세보기 버튼에서 추가 정보 추출
+ const trackBtn = row.querySelector('.track_detail');
+
+ let requiredTime = '';
+ let watchedTime = '';
+
+ if (timeCells.length >= 1) {
+ requiredTime = timeCells[0].textContent?.trim() || '';
+ }
+
+ // 총 학습시간은 상세보기 버튼이 있는 셀에서 추출 (시간 텍스트 + 버튼이 공존)
+ if (trackBtn) {
+ const parentCell = trackBtn.closest('td');
+ if (parentCell) {
+ const textNodes = Array.from(parentCell.childNodes)
+ .filter((node) => node.nodeType === 3)
+ .map((node) => node.textContent?.trim() || '')
+ .filter(Boolean);
+ const timeText = textNodes.find((t) => /^\d{1,2}:\d{2}(:\d{2})?$/.test(t));
+ if (timeText) watchedTime = timeText;
+ }
+ } else if (timeCells.length >= 2) {
+ watchedTime = timeCells[1].textContent?.trim() || '';
+ }
+
+ // 출석 판단: 총 학습시간 >= 출석인정 요구시간
+ const isAttendance =
+ requiredTime && watchedTime && parseTimeToSeconds(watchedTime) >= parseTimeToSeconds(requiredTime)
+ ? 'o'
+ : 'x';
+
+ results.push({
+ title,
+ isAttendance,
+ weeklyAttendance: '', // 후처리에서 계산
+ week: currentWeek,
+ requiredTime,
+ watchedTime,
+ });
+ } catch (error) {
+ console.error(`[Dotbugi] 진도 행 파싱 오류: ${link}`, error);
+ }
+ });
+
+ // 주차별 출석 계산: 해당 주차의 모든 VOD가 출석이면 'o'
+ const weekGroups = new Map();
+ for (const item of results) {
+ const group = weekGroups.get(item.week) ?? [];
+ group.push(item);
+ weekGroups.set(item.week, group);
+ }
+
+ for (const [, group] of weekGroups) {
+ const allAttended = group.every((item) => item.isAttendance === 'o');
+ const weeklyAttendance = allAttended ? 'o' : 'x';
+ for (const item of group) {
+ item.weeklyAttendance = weeklyAttendance;
+ }
+ }
+
+ return results;
+ } catch (error) {
+ console.error('[Dotbugi] VOD 진도 조회 오류:', error);
+ throw error;
+ }
+};
diff --git a/src/lib/filterData.tsx b/src/lib/filterData.tsx
index 0c789e3..962797b 100644
--- a/src/lib/filterData.tsx
+++ b/src/lib/filterData.tsx
@@ -1,34 +1,39 @@
-import { Vod, Assign, Quiz, Filters } from '@/content/types';
+import { Vod, Assign, Quiz, CourseBase, Filters } from '@/types';
+import { isAttended } from './utils';
+import i18n from '@/i18n';
+
+function matchesBase(item: CourseBase & { title: string }, courseTitles: string[], term: string): boolean {
+ if (courseTitles.length > 0 && !courseTitles.includes(item.courseTitle)) return false;
+ if (
+ term &&
+ !item.courseTitle.toLowerCase().includes(term) &&
+ !item.title.toLowerCase().includes(term) &&
+ !item.prof.toLowerCase().includes(term)
+ )
+ return false;
+ return true;
+}
// 필터 적용 for VODs
export function filterVods(vods: Vod[], filters: Filters, searchTerm: string, sortBy: keyof Vod): Vod[] {
- let data = vods;
-
const { courseTitles, attendanceStatuses } = filters;
+ const term = searchTerm.toLowerCase();
- if (courseTitles.length > 0) {
- data = data.filter((vod) => courseTitles.includes(vod.courseTitle));
- }
-
- if (attendanceStatuses && attendanceStatuses.length > 0) {
- data = data.filter((vod) => {
- const status = vod.isAttendance.toLowerCase().trim() === 'o' ? '출석' : '결석';
- return attendanceStatuses.includes(status);
- });
- }
+ const attendedLabel = i18n.t('attendance.attended', { ns: 'common' });
+ const absentLabel = i18n.t('attendance.absent', { ns: 'common' });
- if (searchTerm !== '') {
- data = data.filter(
- (item) =>
- item.courseTitle.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.prof.toLowerCase().includes(searchTerm.toLowerCase())
- );
- }
+ const data = vods.filter((vod) => {
+ if (!matchesBase(vod, courseTitles, term)) return false;
+ if (attendanceStatuses && attendanceStatuses.length > 0) {
+ const status = isAttended(vod.isAttendance) ? attendedLabel : absentLabel;
+ if (!attendanceStatuses.includes(status)) return false;
+ }
+ return true;
+ });
return data.sort((a, b) => {
- const attendanceA = a.isAttendance.toLowerCase().trim() === 'o';
- const attendanceB = b.isAttendance.toLowerCase().trim() === 'o';
+ const attendanceA = isAttended(a.isAttendance);
+ const attendanceB = isAttended(b.isAttendance);
if (attendanceA !== attendanceB) {
return attendanceA ? -1 : 1;
@@ -48,26 +53,14 @@ export function filterVods(vods: Vod[], filters: Filters, searchTerm: string, so
// 필터 적용 for Assigns
export function filterAssigns(assigns: Assign[], filters: Filters, searchTerm: string, sortBy: keyof Assign): Assign[] {
- let data = assigns;
-
const { courseTitles, submitStatuses } = filters;
+ const term = searchTerm.toLowerCase();
- if (courseTitles.length > 0) {
- data = data.filter((assign) => courseTitles.includes(assign.courseTitle));
- }
-
- if (submitStatuses && submitStatuses.length > 0) {
- data = data.filter((assign) => submitStatuses.includes(assign.isSubmit));
- }
-
- if (searchTerm !== '') {
- data = data.filter(
- (item) =>
- item.courseTitle.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.prof.toLowerCase().includes(searchTerm.toLowerCase())
- );
- }
+ const data = assigns.filter((assign) => {
+ if (!matchesBase(assign, courseTitles, term)) return false;
+ if (submitStatuses && submitStatuses.length > 0 && !submitStatuses.includes(assign.isSubmit)) return false;
+ return true;
+ });
return data.sort((a, b) => {
// 미제출 우선 배치
@@ -87,34 +80,31 @@ export function filterAssigns(assigns: Assign[], filters: Filters, searchTerm: s
});
}
-// 필터 적용 for Quizes
-export function filterQuizes(quizes: Quiz[], filters: Filters, searchTerm: string, sortBy: keyof Quiz): Quiz[] {
- let data = quizes;
-
- const { courseTitles } = filters;
-
- if (courseTitles.length > 0) {
- data = data.filter((quiz) => courseTitles.includes(quiz.courseTitle));
- }
+// 필터 적용 for Quizzes
+export function filterQuizzes(quizzes: Quiz[], filters: Filters, searchTerm: string, sortBy: keyof Quiz): Quiz[] {
+ const { courseTitles, submitStatuses } = filters;
+ const term = searchTerm.toLowerCase();
- if (searchTerm !== '') {
- data = data.filter(
- (item) =>
- item.courseTitle.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
- item.prof.toLowerCase().includes(searchTerm.toLowerCase())
- );
- }
+ const data = quizzes.filter((quiz) => {
+ if (!matchesBase(quiz, courseTitles, term)) return false;
+ if (submitStatuses && submitStatuses.length > 0 && !submitStatuses.includes(quiz.isSubmit)) return false;
+ return true;
+ });
return data.sort((a, b) => {
+ // 미제출 우선 배치
+ if (!a.isSubmit && b.isSubmit) return -1;
+ if (a.isSubmit && !b.isSubmit) return 1;
+
switch (sortBy) {
case 'title':
return a.title.localeCompare(b.title);
- default:
- if (a.dueDate === null && b.dueDate !== null) return 1;
- if (a.dueDate !== null && b.dueDate === null) return -1;
- if (a.dueDate === null && b.dueDate === null) return 0;
- return (a.dueDate ?? '').localeCompare(b.dueDate ?? '');
+ default: {
+ const dateA = a.dueDate === null ? Number.MAX_SAFE_INTEGER : new Date(a.dueDate).getTime();
+ const dateB = b.dueDate === null ? Number.MAX_SAFE_INTEGER : new Date(b.dueDate).getTime();
+ if (dateA !== dateB) return dateA - dateB;
+ return 0;
+ }
}
});
}
diff --git a/src/lib/generateKey.ts b/src/lib/generateKey.ts
new file mode 100644
index 0000000..e10b26e
--- /dev/null
+++ b/src/lib/generateKey.ts
@@ -0,0 +1,4 @@
+export const makeVodKey = (courseId: string, title: string, week: number) => `${courseId}-${title}-${week}`;
+export const makeItemKey = (courseId: string, title: string, dueDate: string) => `${courseId}-${title}-${dueDate}`;
+export const makeVodGroupKey = (courseId: string, subject: string, range: string | null) =>
+ `${courseId}-${subject}-${range}`;
diff --git a/src/lib/injectCourseToggles.ts b/src/lib/injectCourseToggles.ts
new file mode 100644
index 0000000..1b4a065
--- /dev/null
+++ b/src/lib/injectCourseToggles.ts
@@ -0,0 +1,107 @@
+import { loadDataFromStorage, saveDataToStorage } from '@/lib/storage';
+import { parseCoursesFromDOM } from '@/lib/parseCourses';
+
+const TOGGLE_ATTR = 'data-dotbugi-toggle';
+
+export function injectCourseToggles() {
+ const allCourses = parseCoursesFromDOM();
+ if (allCourses.length === 0) return;
+
+ // 커뮤니티 강좌 ID 저장 (강의 페이지 배지 주입 시 참조)
+ const communityIds = allCourses.filter((c) => c.isCommunity).map((c) => c.courseId);
+ saveDataToStorage('communityIds', communityIds);
+
+ loadDataFromStorage('trackedCourseIds', (savedIds) => {
+ const trackedIds = savedIds ?? allCourses.filter((c) => !c.isCommunity).map((c) => c.courseId);
+
+ if (!savedIds) {
+ saveDataToStorage('trackedCourseIds', trackedIds);
+ }
+
+ const trackedSet = new Set(trackedIds);
+ const listItems = document.querySelectorAll('.my-course-lists > li');
+
+ listItems.forEach((li) => {
+ if (li.querySelector(`[${TOGGLE_ATTR}]`)) return;
+
+ const link = li.querySelector('a.course_link') as HTMLAnchorElement | null;
+ if (!link) return;
+
+ const courseId = new URL(link.href).searchParams.get('id');
+ if (!courseId) return;
+
+ const isTracked = trackedSet.has(courseId);
+ const toggle = createToggleElement(isTracked);
+ toggle.setAttribute(TOGGLE_ATTR, courseId);
+
+ toggle.addEventListener('click', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ handleToggle(toggle, courseId);
+ });
+
+ const courseBox = li.querySelector('.course_box');
+ if (courseBox) {
+ (courseBox as HTMLElement).style.position = 'relative';
+ courseBox.appendChild(toggle);
+ }
+ });
+ });
+}
+
+function createToggleElement(isTracked: boolean): HTMLElement {
+ const btn = document.createElement('div');
+ btn.style.cssText = `
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ z-index: 10;
+ transition: all 0.2s ease;
+ background: ${isTracked ? '#3b82f6' : 'rgba(0, 0, 0, 0.4)'};
+ border: 2px solid ${isTracked ? '#3b82f6' : 'rgba(255, 255, 255, 0.6)'};
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ `;
+
+ btn.innerHTML = getCheckSvg(isTracked);
+ btn.title = isTracked ? 'Tracking' : 'Not tracking';
+ return btn;
+}
+
+function getCheckSvg(isTracked: boolean): string {
+ if (isTracked) {
+ return ` `;
+ }
+ return ` `;
+}
+
+function updateToggleVisual(toggle: HTMLElement, isTracked: boolean) {
+ toggle.style.background = isTracked ? '#3b82f6' : 'rgba(0, 0, 0, 0.4)';
+ toggle.style.borderColor = isTracked ? '#3b82f6' : 'rgba(255, 255, 255, 0.6)';
+ toggle.innerHTML = getCheckSvg(isTracked);
+ toggle.title = isTracked ? 'Tracking' : 'Not tracking';
+}
+
+function handleToggle(toggle: HTMLElement, courseId: string) {
+ loadDataFromStorage('trackedCourseIds', (currentIds) => {
+ if (!currentIds) return;
+
+ const isCurrentlyTracked = currentIds.includes(courseId);
+ let newIds: string[];
+
+ if (isCurrentlyTracked) {
+ newIds = currentIds.filter((id) => id !== courseId);
+ } else {
+ newIds = [...currentIds, courseId];
+ }
+
+ saveDataToStorage('trackedCourseIds', newIds);
+ updateToggleVisual(toggle, !isCurrentlyTracked);
+ });
+}
diff --git a/src/lib/lmsKeywords.ts b/src/lib/lmsKeywords.ts
new file mode 100644
index 0000000..9d9a575
--- /dev/null
+++ b/src/lib/lmsKeywords.ts
@@ -0,0 +1,61 @@
+/**
+ * LMS HTML 파싱에 사용되는 키워드 목록
+ *
+ * UI 언어 설정과 무관하게, LMS 자체 언어 설정에 따라 HTML에 표시되는 문자열이 달라집니다.
+ * 각 키워드 배열에 LMS가 지원하는 모든 언어의 문자열을 추가하면 어떤 언어 설정이든 파싱이 동작합니다.
+ *
+ * 새 언어를 추가하려면 배열에 해당 언어의 문자열을 추가하면 됩니다.
+ */
+
+/** 과제 미제출 상태 텍스트 */
+export const NOT_SUBMITTED = [
+ '미제출', // ko
+ 'Not submitted', // en
+ '提出なし', // 'ja',
+ '没有作业', // 'zh',
+];
+
+/** 일괄출석인정 텍스트 (주차 출석 컬럼에 포함되면 출석 처리) */
+export const BULK_APPROVED = [
+ '일괄출석인정', // ko
+ 'Batch attendance', // en
+ // 'ja',
+ // 'zh',
+];
+
+/**
+ * LMS 날짜 문자열을 JS Date가 파싱 가능한 형식으로 정규화
+ *
+ * 지원 형식:
+ * ko/en: "2023-04-24 10:50"
+ * zh: "2023年04月24日 星期一 10:50"
+ * ja: "2023年 04月 24日(月曜日) 10:50"
+ *
+ * 결과: "2023-04-24 10:50" (통일)
+ */
+export function normalizeLmsDate(raw: string | null): string | null {
+ if (!raw) return null;
+ const str = raw.trim();
+
+ // 이미 ISO-like 형식이면 그대로 반환 (ko/en)
+ if (/^\d{4}-\d{2}-\d{2}/.test(str)) return str;
+
+ // ja/zh: "2023年04月24日..." 또는 "2023年 04月 24日..."
+ const match = str.match(/(\d{4})年\s*(\d{2})月\s*(\d{2})日.*?(\d{2}:\d{2})/);
+ if (match) {
+ return `${match[1]}-${match[2]}-${match[3]} ${match[4]}`;
+ }
+
+ return str;
+}
+
+/**
+ * LMS VOD range 문자열 정규화 ("시작 ~ 종료" 형태)
+ * 각 날짜를 개별적으로 정규화하여 반환
+ */
+export function normalizeLmsRange(raw: string | null): string | null {
+ if (!raw) return null;
+ if (!raw.includes('~')) return normalizeLmsDate(raw);
+ const parts = raw.split('~').map((s) => normalizeLmsDate(s.trim()));
+ return parts.join(' ~ ');
+}
diff --git a/src/lib/parseCourses.ts b/src/lib/parseCourses.ts
new file mode 100644
index 0000000..8f2ced0
--- /dev/null
+++ b/src/lib/parseCourses.ts
@@ -0,0 +1,26 @@
+import { CourseBase } from '@/types';
+import { removeSquareBrackets } from '@/lib/utils';
+
+// course_label_ec = 커뮤니티(비교과), 클래스 기반 필터링으로 다국어 대응
+const COMMUNITY_CLASS = 'course_label_ec';
+
+function parseCourseFromElement(li: Element): CourseBase | null {
+ const link = li.querySelector('a.course_link') as HTMLAnchorElement | null;
+ if (!link) return null;
+
+ const courseId = new URL(link.href).searchParams.get('id');
+ const titleEl = link.querySelector('.course-title');
+ const prof = titleEl?.querySelector('p')?.textContent?.trim();
+ const rawTitle = titleEl?.querySelector('h1, h2, h3')?.textContent?.replace(/new/i, '').trim();
+ const courseTitle = rawTitle ? removeSquareBrackets(rawTitle) : '';
+
+ if (!courseId || !courseTitle || !prof) return null;
+
+ const isCommunity = li.classList.contains(COMMUNITY_CLASS);
+ return { courseId, courseTitle, prof, isCommunity };
+}
+
+export function parseCoursesFromDOM(): CourseBase[] {
+ return Array.from(document.querySelectorAll('.my-course-lists > li'))
+ .flatMap((li) => parseCourseFromElement(li) ?? []);
+}
diff --git a/src/lib/storage.ts b/src/lib/storage.ts
index 595f1ba..99a36bf 100644
--- a/src/lib/storage.ts
+++ b/src/lib/storage.ts
@@ -21,3 +21,30 @@ export function loadDataFromStorage(key: string, callback: (data: T | null) =
}
});
}
+
+/**
+ * storage에서 데이터를 로드하고 JSON 파싱 후 변환 함수를 적용하는 유틸
+ */
+export function loadAndTransform(
+ storageKey: string,
+ transform: (data: T[]) => R,
+ callback: (result: R) => void
+): void {
+ loadDataFromStorage(storageKey, (data: string | null) => {
+ if (!data) return;
+
+ let parsedData: T[];
+ if (typeof data === 'string') {
+ try {
+ parsedData = JSON.parse(data);
+ } catch (error) {
+ console.error(`JSON 파싱 에러 (${storageKey}):`, error);
+ return;
+ }
+ } else {
+ parsedData = data;
+ }
+
+ callback(transform(parsedData));
+ });
+}
diff --git a/src/lib/stringUtils.ts b/src/lib/stringUtils.ts
new file mode 100644
index 0000000..5dff7a5
--- /dev/null
+++ b/src/lib/stringUtils.ts
@@ -0,0 +1,16 @@
+import i18n from '@/i18n';
+
+export function removeSquareBrackets(str: string) {
+ return str.replace(/\[[^\]]*\]/g, '');
+}
+
+export function formatDateString(input: string | null) {
+ if (!input) return i18n.t('date.noDeadline', { ns: 'common' });
+ const regex = /(\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}):\d{2} ~ (\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}):\d{2}/;
+
+ return input.replace(
+ regex,
+ (_, y1, m1, d1, t1, y2, m2, d2, t2) =>
+ `${y1.slice(2)}.${m1}.${d1} ${t1} ~ ${y2.slice(2)}.${m2}.${d2} ${t2}`
+ );
+}
diff --git a/src/lib/summarizeCourseData.ts b/src/lib/summarizeCourseData.ts
new file mode 100644
index 0000000..de59461
--- /dev/null
+++ b/src/lib/summarizeCourseData.ts
@@ -0,0 +1,19 @@
+import { Vod } from '@/types';
+import { makeVodGroupKey } from '@/lib/generateKey';
+import { isAttended } from '@/lib/utils';
+
+export type CardData = {
+ done: number;
+ total: number;
+};
+
+export function summarizeVods(vods: Vod[]): CardData {
+ const groups: Record = {};
+ for (const vod of vods) {
+ const key = makeVodGroupKey(vod.courseId, vod.subject, vod.range);
+ (groups[key] ??= []).push(vod);
+ }
+ const groupList = Object.values(groups);
+ const done = groupList.filter((g) => isAttended(g[0].weeklyAttendance)).length;
+ return { done, total: groupList.length };
+}
diff --git a/src/lib/transformCalendarEvents.ts b/src/lib/transformCalendarEvents.ts
new file mode 100644
index 0000000..5f43205
--- /dev/null
+++ b/src/lib/transformCalendarEvents.ts
@@ -0,0 +1,50 @@
+import { startOfDay } from 'date-fns';
+import { Vod } from '@/types';
+import { removeSquareBrackets } from '@/lib/utils';
+import { parseDate } from '@/lib/dateUtils';
+import { makeVodGroupKey } from '@/lib/generateKey';
+
+export type CalendarEvent = {
+ id: string;
+ type: 'vod' | 'assign' | 'quiz';
+ title: string;
+ subject: string;
+ start: Date | null;
+ end: Date | null;
+};
+
+export function vodGroupsToEvents(vods: Vod[]): CalendarEvent[] {
+ const grouped: Record = {};
+ for (const vod of vods) {
+ const key = makeVodGroupKey(vod.courseId, vod.subject, vod.range);
+ (grouped[key] ??= []).push(vod);
+ }
+
+ return Object.entries(grouped).map(([key, items]) => {
+ const range = items[0].range;
+ const [startStr, endStr] = range ? range.split(' ~ ') : [null, null];
+ return {
+ id: key,
+ type: 'vod' as const,
+ start: startStr ? parseDate(startStr) : null,
+ end: endStr ? parseDate(endStr) : null,
+ title: removeSquareBrackets(items[0].courseTitle),
+ subject: removeSquareBrackets(items[0].subject),
+ };
+ });
+}
+
+export function dueDateItemToEvent(
+ item: { courseId: string; courseTitle: string; title: string; dueDate: string | null },
+ type: 'assign' | 'quiz',
+): CalendarEvent {
+ const normalizedDate = item.dueDate ? startOfDay(new Date(item.dueDate)) : null;
+ return {
+ id: item.courseId + item.title + item.dueDate,
+ type,
+ start: normalizedDate,
+ end: normalizedDate,
+ title: removeSquareBrackets(item.courseTitle),
+ subject: removeSquareBrackets(item.title),
+ };
+}
diff --git a/src/lib/transformCourseData.ts b/src/lib/transformCourseData.ts
new file mode 100644
index 0000000..d25cbe9
--- /dev/null
+++ b/src/lib/transformCourseData.ts
@@ -0,0 +1,41 @@
+import { Vod, CourseBase } from '@/types';
+
+/**
+ * VOD 데이터와 출석 데이터를 title+week 기준으로 조인 (날짜 필터 없이 전체 반환)
+ */
+export function mergeVodWithAttendance(
+ course: CourseBase,
+ vodList: { week: number; subject: string; title: string; url: string; range: string | null; length: string }[],
+ attendanceList: { title: string; isAttendance: string; weeklyAttendance: string; week: number; requiredTime?: string; watchedTime?: string }[]
+): Vod[] {
+ const attendanceByKey = new Map();
+ for (const att of attendanceList) {
+ attendanceByKey.set(`${att.title}-${att.week}`, att);
+ }
+
+ const results: Vod[] = [];
+ for (const vod of vodList) {
+ const attendance = attendanceByKey.get(`${vod.title}-${vod.week}`);
+ if (!attendance) continue;
+ results.push({
+ ...course,
+ ...vod,
+ isAttendance: attendance.isAttendance,
+ weeklyAttendance: attendance.weeklyAttendance,
+ requiredTime: attendance.requiredTime,
+ watchedTime: attendance.watchedTime,
+ });
+ }
+ return results;
+}
+
+/**
+ * 마감일 기반 항목(과제/퀴즈)에 course 정보를 병합 (날짜 필터 없이 전체 반환)
+ */
+export function mergeDueDateItems(
+ course: CourseBase,
+ items: T[],
+): (CourseBase & T)[] {
+ return items.map((item) => ({ ...course, ...item }));
+}
+
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 0f2c101..4172fb0 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,4 +1,3 @@
-import { TimeDifferenceResult } from '@/content/types';
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
@@ -6,199 +5,15 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
-export function isCurrentDateInRange(dateRange: string | null) {
- if (!dateRange || !dateRange.includes(' ~ ')) {
- return false;
- }
-
- const [startStr, endStr] = dateRange.split(' ~ ');
-
- if (!startStr || !endStr) {
- return false;
- }
-
- const startDate = new Date(startStr.replace(/-/g, '/'));
- const endDate = new Date(endStr.replace(/-/g, '/'));
-
- const currentDate = new Date();
-
- return currentDate >= startDate && currentDate <= endDate;
-}
-
-export function isCurrentDateByDate(date: string | null) {
- if (!date || date.length <= 1) return false;
- const endDate = new Date(date);
- const currentDate = new Date();
- return currentDate <= endDate;
-}
-
-export function isWithinSevenDays(date: string) {
- const dueDate = new Date(date); // 문자열을 Date 객체로 변환
- const now = new Date(); // 현재 날짜
- const diffTime = dueDate.getTime() - now.getTime(); // 두 날짜의 차이 (밀리초)
- const diffDays = diffTime / (1000 * 60 * 60 * 24); // 차이를 일(day)로 변환
- return diffDays <= 7 && diffDays >= 0;
-}
-
-export const calculateTimeDifference = (timeRange: string | null): TimeDifferenceResult => {
- if (!timeRange) {
- return {
- message: `정보없음`,
- borderColor: 'border-amber-500',
- borderLeftColor: 'border-l-amber-500',
- textColor: 'text-amber-500',
- };
- }
-
- const now = new Date();
- const [startString, endString] = timeRange.split(' ~ ');
- const startDate = new Date(startString);
- const endDate = new Date(endString);
-
- if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
- return { message: 'Invalid date format', borderColor: 'gray', borderLeftColor: 'gray', textColor: 'black' };
- }
-
- if (now < endDate) {
- const timeDiff = endDate.getTime() - now.getTime();
- const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
-
- if (days >= 1) {
- return {
- message: `${days}일 후`,
- borderColor: 'border-amber-500',
- borderLeftColor: 'border-l-amber-500',
- textColor: 'text-amber-500',
- };
- } else {
- const hours = Math.floor(timeDiff / (1000 * 60 * 60));
- const minutes = Math.floor(timeDiff / (1000 * 60));
- return {
- message: `${hours !== 0 ? `${hours}시간 후` : `${minutes}분 후`}`,
- borderColor: 'border-red-700',
- borderLeftColor: 'border-l-red-700',
- textColor: 'text-red-700',
- };
- }
- } else {
- return {
- message: '마감',
- borderColor: 'border-red-950',
- borderLeftColor: 'border-l-red-950',
- textColor: 'text-red-950',
- };
- }
-};
-
-export const calculateDueDate = (dueDate: string | null): TimeDifferenceResult => {
- if (!dueDate) {
- return {
- message: `정보없음`,
- borderColor: 'border-amber-500',
- borderLeftColor: 'border-l-amber-500',
- textColor: 'text-amber-500',
- };
- }
-
- const now = new Date();
- const endDate = new Date(dueDate);
-
- if (isNaN(endDate.getTime())) {
- return { message: 'Invalid date format', borderColor: 'gray', borderLeftColor: 'gray', textColor: 'black' };
- }
-
- if (now < endDate) {
- const timeDiff = endDate.getTime() - now.getTime();
- const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
-
- if (days >= 1) {
- return {
- message: `${days}일 후`,
- borderColor: 'border-amber-500',
- borderLeftColor: 'border-l-amber-500',
- textColor: 'text-amber-500',
- };
- } else {
- const hours = Math.floor(timeDiff / (1000 * 60 * 60));
- const minutes = Math.floor(timeDiff / (1000 * 60));
- return {
- message: `${hours !== 0 ? `${hours}시간 후` : `${minutes}분 후`}`,
- borderColor: 'border-red-700',
- borderLeftColor: 'border-l-red-700',
- textColor: 'text-red-700',
- };
- }
- } else {
- return {
- message: '마감',
- borderColor: 'border-red-950',
- borderLeftColor: 'border-l-red-950',
- textColor: 'text-red-950',
- };
- }
-};
-
-export const formatDateString = (input: string | null) => {
- if (!input) return '기한없음';
- const regex = /(\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}):\d{2} ~ (\d{4})-(\d{2})-(\d{2}) (\d{2}:\d{2}):\d{2}/;
-
- const formatted = input.replace(
- regex,
- (_, year1, month1, day1, time1, year2, month2, day2, time2) =>
- `${year1.slice(2)}.${month1}.${day1} ${time1} ~ ${year2.slice(2)}.${month2}.${day2} ${time2}`
- );
-
- return formatted;
-};
-
-export const calculateRemainingTimeByRange = (range: string | null) => {
- if (!range) return '정보없음';
- const [, endDateStr] = range.split(' ~ ');
- const endDate = new Date(endDateStr);
-
- const now = new Date();
-
- const timeDiff = endDate.getTime() - now.getTime();
- const daysLeft = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
- const hoursLeft = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
- const minutesLeft = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
-
- if (daysLeft < 0 || hoursLeft < 0 || minutesLeft < 0) return `마감`;
- return `${daysLeft === 0 ? '' : daysLeft + '일'} ${hoursLeft === 0 ? '' : hoursLeft + '시간'} ${minutesLeft}분 남음`;
-};
-
-export const calculateRemainingTime = (endTime: string | null) => {
- if (!endTime) return '정보없음';
- const endDate = new Date(endTime);
-
- const now = new Date();
-
- const timeDiff = endDate.getTime() - now.getTime();
- const daysLeft = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
- const hoursLeft = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
- const minutesLeft = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
-
- if (daysLeft < 0 || hoursLeft < 0 || minutesLeft < 0) return `마감`;
- return `${daysLeft === 0 ? '' : daysLeft + '일'} ${hoursLeft === 0 ? '' : hoursLeft + '시간'} ${minutesLeft}분 남음`;
-};
-
-export const removeSquareBrackets = (str: string) => {
- return str.replace(/\[[^\]]*\]/g, '');
-};
-
-export const TimeAgo = (givenTimestamp: number) => {
- const now = Date.now();
- const diffMs = now - givenTimestamp;
-
- // 각 단위별 밀리초
- const msPerMinute = 1000 * 60;
- const msPerHour = msPerMinute * 60;
- const msPerDay = msPerHour * 24;
-
- // 차이를 일, 시간, 분 단위로 계산
- const days = Math.floor(diffMs / msPerDay);
- const hours = Math.floor((diffMs % msPerDay) / msPerHour);
- const minutes = Math.floor((diffMs % msPerHour) / msPerMinute);
- if (days === 0 && hours === 0 && minutes === 0) return '지금 막';
- return `${days !== 0 ? days + '일 ' : ''}${hours !== 0 ? hours + '시간 ' : ''}${minutes !== 0 ? minutes + '분 전' : '전'}`;
-};
+// 하위 호환: 기존 import 경로 유지
+export { isAttended, isAbsent } from './attendance';
+export {
+ isCurrentDateInRange,
+ isCurrentDateByDate,
+ isWithinSevenDays,
+ extractEndDate,
+ calculateDueDate,
+ calculateRemainingTime,
+ timeAgo as TimeAgo,
+} from './dateUtils';
+export { removeSquareBrackets, formatDateString } from './stringUtils';
diff --git a/src/mocks/loadMockData.ts b/src/mocks/loadMockData.ts
new file mode 100644
index 0000000..a9a0880
--- /dev/null
+++ b/src/mocks/loadMockData.ts
@@ -0,0 +1,13 @@
+import { Vod, Assign, Quiz } from '@/types';
+
+export type MockData = {
+ vods: Vod[];
+ assigns: Assign[];
+ quizzes: Quiz[];
+};
+
+export async function loadMockData(): Promise {
+ const { mockVods, mockAssigns, mockQuizes } = await import('./mockData');
+ return { vods: mockVods, assigns: mockAssigns, quizzes: mockQuizes };
+}
+
diff --git a/src/mocks/mockData.ts b/src/mocks/mockData.ts
index 16253f3..7d442af 100644
--- a/src/mocks/mockData.ts
+++ b/src/mocks/mockData.ts
@@ -1,4 +1,4 @@
-import { CourseBase, Vod, Assign, Quiz } from '@/content/types';
+import { CourseBase, Vod, Assign, Quiz } from '@/types';
function offsetDate(days: number, hours = 0): string {
const d = new Date();
@@ -20,34 +20,34 @@ export const mockCourses: CourseBase[] = [
];
export const mockVods: Vod[] = [
- // 출석 완료 + 기간 내
{
courseId: '1001',
courseTitle: '자료구조',
prof: '김교수',
week: 7,
subject: '7주차',
- title: '이진 탐색 트리',
- url: '#',
- range: makeRange(-3, 5),
- length: '45:00',
- isAttendance: 'O',
- weeklyAttendance: '1/1',
+ title: '힙과 우선순위 큐',
+ url: '#mock-vod-2',
+ range: makeRange(-5, 0),
+ length: '38:00',
+ isAttendance: 'X',
+ weeklyAttendance: '0/1',
},
- // 결석 + 마감 임박 (오늘 안에 끝남)
+ // 출석 완료 + 기간 내
{
courseId: '1001',
courseTitle: '자료구조',
prof: '김교수',
week: 7,
subject: '7주차',
- title: '힙과 우선순위 큐',
- url: '#',
- range: makeRange(-5, 0, ),
- length: '38:00',
- isAttendance: 'X',
- weeklyAttendance: '0/1',
+ title: '이진 탐색 트리',
+ url: '#mock-vod-1',
+ range: makeRange(-3, 5),
+ length: '45:00',
+ isAttendance: 'O',
+ weeklyAttendance: '1/1',
},
+ // 결석 + 마감 임박 (오늘 안에 끝남)
// 결석 + 기간 여유
{
courseId: '1002',
@@ -56,7 +56,7 @@ export const mockVods: Vod[] = [
week: 6,
subject: '6주차',
title: '프로세스 동기화',
- url: '#',
+ url: '#mock-vod-3',
range: makeRange(-2, 7),
length: '50:00',
isAttendance: 'X',
@@ -70,7 +70,7 @@ export const mockVods: Vod[] = [
week: 6,
subject: '6주차',
title: '데드락',
- url: '#',
+ url: '#mock-vod-4',
range: makeRange(-2, 7),
length: '42:00',
isAttendance: 'O',
@@ -84,7 +84,7 @@ export const mockVods: Vod[] = [
week: 5,
subject: '5주차',
title: 'TCP/IP 프로토콜',
- url: '#',
+ url: '#mock-vod-5',
range: makeRange(-6, 0),
length: '55:00',
isAttendance: 'X',
@@ -97,7 +97,7 @@ export const mockVods: Vod[] = [
week: 5,
subject: '5주차',
title: 'UDP와 소켓 프로그래밍',
- url: '#',
+ url: '#mock-vod-6',
range: makeRange(-6, 0),
length: '35:00',
isAttendance: 'O',
@@ -111,7 +111,7 @@ export const mockVods: Vod[] = [
week: 4,
subject: '4주차',
title: '정규화 이론',
- url: '#',
+ url: '#mock-vod-7',
range: makeRange(-1, 14),
length: '60:00',
isAttendance: 'O',
@@ -120,6 +120,28 @@ export const mockVods: Vod[] = [
];
export const mockAssigns: Assign[] = [
+ // 오늘 마감 (캘린더 동기화 + 중복 테스트용)
+ {
+ courseId: '1001',
+ courseTitle: '자료구조',
+ prof: '김교수',
+ subject: '7주차',
+ title: '오늘 마감 과제 A',
+ url: '#mock-assign-1',
+ isSubmit: false,
+ dueDate: offsetDate(0),
+ },
+ // 중복 테스트: 동일 제목/날짜
+ {
+ courseId: '1001',
+ courseTitle: '자료구조',
+ prof: '김교수',
+ subject: '7주차',
+ title: '오늘 마감 과제 A',
+ url: '#mock-assign-2',
+ isSubmit: false,
+ dueDate: offsetDate(0),
+ },
// 미제출 + 마감 임박 (내일)
{
courseId: '1001',
@@ -127,7 +149,7 @@ export const mockAssigns: Assign[] = [
prof: '김교수',
subject: '7주차',
title: '이진 트리 구현 과제',
- url: '#',
+ url: '#mock-assign-3',
isSubmit: false,
dueDate: offsetDate(1),
},
@@ -138,7 +160,7 @@ export const mockAssigns: Assign[] = [
prof: '이교수',
subject: '6주차',
title: '프로세스 스케줄링 시뮬레이션',
- url: '#',
+ url: '#mock-assign-4',
isSubmit: false,
dueDate: offsetDate(3),
},
@@ -149,7 +171,7 @@ export const mockAssigns: Assign[] = [
prof: '김교수',
subject: '6주차',
title: '스택/큐 구현 과제',
- url: '#',
+ url: '#mock-assign-5',
isSubmit: true,
dueDate: offsetDate(5),
},
@@ -160,7 +182,7 @@ export const mockAssigns: Assign[] = [
prof: '박교수',
subject: '5주차',
title: '소켓 프로그래밍 실습',
- url: '#',
+ url: '#mock-assign-6',
isSubmit: false,
dueDate: offsetDate(7),
},
@@ -171,7 +193,7 @@ export const mockAssigns: Assign[] = [
prof: '최교수',
subject: '4주차',
title: 'SQL 쿼리 작성',
- url: '#',
+ url: '#mock-assign-7',
isSubmit: true,
dueDate: offsetDate(-1),
},
@@ -182,7 +204,7 @@ export const mockAssigns: Assign[] = [
prof: '최교수',
subject: '5주차',
title: 'ERD 설계 과제',
- url: '#',
+ url: '#mock-assign-8',
isSubmit: false,
dueDate: offsetDate(0, 3),
},
@@ -193,7 +215,7 @@ export const mockAssigns: Assign[] = [
prof: '이교수',
subject: '5주차',
title: '메모리 관리 보고서',
- url: '#',
+ url: '#mock-assign-9',
isSubmit: false,
dueDate: offsetDate(-2),
},
@@ -204,13 +226,27 @@ export const mockAssigns: Assign[] = [
prof: '박교수',
subject: '6주차',
title: '네트워크 분석 레포트',
- url: '#',
+ url: '#mock-assign-10',
isSubmit: false,
dueDate: null,
},
];
+// Player 테스트용 mock VOD (실제 LMS VOD ID)
+export const mockPlayerVodIds = ['1086348', '1088220', '1130884'];
+
export const mockQuizes: Quiz[] = [
+ // 오늘 마감 (캘린더 동기화 테스트용)
+ {
+ courseId: '1002',
+ courseTitle: '운영체제',
+ prof: '이교수',
+ subject: '6주차',
+ title: '오늘 마감 퀴즈',
+ url: '#mock-quiz-1',
+ dueDate: offsetDate(0),
+ isSubmit: true,
+ },
// 마감 임박 (내일)
{
courseId: '1001',
@@ -218,8 +254,9 @@ export const mockQuizes: Quiz[] = [
prof: '김교수',
subject: '7주차',
title: '트리 구조 퀴즈',
- url: '#',
+ url: '#mock-quiz-2',
dueDate: offsetDate(1),
+ isSubmit: false,
},
// 마감 여유 (5일)
{
@@ -228,8 +265,9 @@ export const mockQuizes: Quiz[] = [
prof: '이교수',
subject: '6주차',
title: '프로세스 관리 퀴즈',
- url: '#',
+ url: '#mock-quiz-3',
dueDate: offsetDate(5),
+ isSubmit: false,
},
// 마감 몇 시간 뒤 (긴급)
{
@@ -238,8 +276,9 @@ export const mockQuizes: Quiz[] = [
prof: '박교수',
subject: '5주차',
title: 'OSI 모델 퀴즈',
- url: '#',
+ url: '#mock-quiz-4',
dueDate: offsetDate(0, 5),
+ isSubmit: true,
},
// 마감 이미 지남 (새로고침 전 마감)
{
@@ -248,8 +287,9 @@ export const mockQuizes: Quiz[] = [
prof: '최교수',
subject: '4주차',
title: 'SQL 기초 퀴즈',
- url: '#',
+ url: '#mock-quiz-5',
dueDate: offsetDate(-1),
+ isSubmit: true,
},
// 마감 2주 뒤
{
@@ -258,8 +298,9 @@ export const mockQuizes: Quiz[] = [
prof: '최교수',
subject: '5주차',
title: '정규화 퀴즈',
- url: '#',
+ url: '#mock-quiz-6',
dueDate: offsetDate(14),
+ isSubmit: false,
},
// dueDate null 케이스
{
@@ -268,7 +309,8 @@ export const mockQuizes: Quiz[] = [
prof: '김교수',
subject: '8주차',
title: '그래프 탐색 퀴즈',
- url: '#',
+ url: '#mock-quiz-7',
dueDate: null,
+ isSubmit: false,
},
];
diff --git a/src/option/App.tsx b/src/option/App.tsx
deleted file mode 100644
index ebb41cc..0000000
--- a/src/option/App.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Routes, Route, useLocation } from 'react-router-dom';
-import { AnimatePresence, motion } from 'framer-motion';
-import Sidebar from './Sidebar';
-import { Toaster } from '@/components/ui/toaster';
-
-import VodPage from 'src/pages/VodPage';
-import AssignmentPage from 'src/pages/AssignmentPage';
-import DashboardPage from '@/pages/DashboardPage';
-import QuizPage from '@/pages/QuizPage';
-import Header from './Header';
-import Labo from './Labo';
-import ColorSetting from './ColorSetting';
-
-const pageVariants = {
- initial: {
- opacity: 0,
- x: '-5%',
- },
- in: {
- opacity: 1,
- x: 0,
- },
- out: {
- opacity: 0,
- x: '5%',
- },
-};
-
-const pageTransition = {
- type: 'tween',
- ease: 'anticipate',
- duration: 0.5,
-};
-
-const AnimatedRoutes = () => {
- const location = useLocation();
- return (
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- 404 Not Found} />
-
-
-
- );
-};
-
-export default function App() {
- return (
- <>
-
-
- >
- );
-}
diff --git a/src/option/ColorSetting.tsx b/src/option/ColorSetting.tsx
deleted file mode 100644
index 5fdab51..0000000
--- a/src/option/ColorSetting.tsx
+++ /dev/null
@@ -1,429 +0,0 @@
-import { useEffect, useState } from 'react';
-import { Save, RotateCcw } from 'lucide-react';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { Button } from '@/components/ui/button';
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
-import { Input } from '@/components/ui/input';
-import { toast } from '@/hooks/use-toast';
-import { loadDataFromStorage, saveDataToStorage } from '@/lib/storage';
-import type { CourseBase } from '@/content/types';
-import { HexColorPicker } from 'react-colorful';
-
-// Update the CourseColorSetting type to include opacity
-export type CourseColorSetting = {
- courseId: string;
- color: string;
- colorType: 'solid' | 'gradient';
- gradient?: string;
- opacity?: number; // Add opacity property
-};
-
-// Recommended color presets
-const colorPresets = [
- '#e2e2e2', // Light Gray
- '#ff75c3', // Pink
- '#ffa647', // Orange
- '#ffe83f', // Yellow
- '#9fff5b', // Lime
- '#70e2ff', // Cyan
- '#cd93ff', // Purple
- '#09203f', // Dark Blue
-];
-
-// Gradient presets
-const gradientPresets = [
- 'linear-gradient(to right, #ff75c3, #ffa647)',
- 'linear-gradient(to right, #ffa647, #ffe83f)',
- 'linear-gradient(to right, #ffe83f, #9fff5b)',
- 'linear-gradient(to right, #9fff5b, #70e2ff)',
- 'linear-gradient(to right, #70e2ff, #cd93ff)',
- 'linear-gradient(to right, #cd93ff, #ff75c3)',
- 'linear-gradient(to right, #AC32E4, #4801FF)',
- 'linear-gradient(to right, #09203f, #537895)',
-];
-
-export default function ColorSetting() {
- const [courses, setCourses] = useState([]);
- const [courseColors, setCourseColors] = useState([]);
- const [originalColors, setOriginalColors] = useState>({});
-
- useEffect(() => {
- loadDataFromStorage('courses', (data: string | null) => {
- if (data) {
- const loadedCourses: CourseBase[] = JSON.parse(data);
- setCourses(loadedCourses);
- }
- });
- }, []);
-
- useEffect(() => {
- loadDataFromStorage('courseColors', (data: string | null) => {
- if (data) {
- const loadedColors: CourseColorSetting[] = JSON.parse(data);
-
- const updatedColors = loadedColors.map((color) => ({
- ...color,
- colorType: color.colorType || 'solid',
- }));
-
- setCourseColors(updatedColors);
-
- const originals: Record = {};
- updatedColors.forEach((item) => {
- originals[item.courseId] = item.color;
- });
- setOriginalColors(originals);
- }
- });
- }, []);
-
- useEffect(() => {
- if (courses.length > 0 && courseColors.length === 0) {
- const defaultColors = courses.map((course) => ({
- courseId: course.courseId,
- color: '#6366f1',
- colorType: 'solid' as const,
- }));
- setCourseColors(defaultColors);
-
- const originals: Record = {};
- defaultColors.forEach((item) => {
- originals[item.courseId] = item.color;
- });
- setOriginalColors(originals);
- }
- }, [courses, courseColors.length]);
-
- const handleColorChange = (
- courseId: string,
- newColor: string,
- colorType: 'solid' | 'gradient' = 'solid',
- gradientValue?: string,
- opacity?: number
- ) => {
- setCourseColors((prev) =>
- prev.map((item) => {
- if (item.courseId === courseId) {
- return {
- ...item,
- color: newColor,
- colorType,
- gradient: gradientValue,
- opacity: opacity !== undefined ? opacity : item.opacity || 1, // Preserve or set default opacity
- };
- }
- return item;
- })
- );
- };
- const resetToOriginal = (courseId: string) => {
- if (originalColors[courseId]) {
- handleColorChange(courseId, originalColors[courseId], 'solid');
- }
- };
-
- // Save color settings
- const handleSave = () => {
- saveDataToStorage('courseColors', JSON.stringify(courseColors));
-
- const originals: Record = {};
- courseColors.forEach((item) => {
- originals[item.courseId] = item.color;
- });
- setOriginalColors(originals);
-
- toast({
- title: '색상 설정 저장 완료',
- description: '강의 색상 설정이 저장되었습니다.',
- });
- };
-
- const getCourseColorSetting = (courseId: string) => {
- return (
- courseColors.find((item) => item.courseId === courseId) || {
- courseId,
- color: '#6366f1',
- colorType: 'solid' as const,
- }
- );
- };
-
- const getOriginalColor = (courseId: string) => {
- return originalColors[courseId] || '#6366f1';
- };
-
- return (
-
-
-
강의 색상 설정
-
각 강의에 표시될 색상을 설정합니다.
-
-
-
- {courses.map((course) => (
- resetToOriginal(course.courseId)}
- />
- ))}
-
-
-
-
-
- 색상 저장
-
-
-
- );
-}
-
-interface CourseCardProps {
- course: CourseBase;
- colorSetting: CourseColorSetting;
- originalColor: string;
- onColorChange: (
- courseId: string,
- color: string,
- colorType: 'solid' | 'gradient',
- gradient?: string,
- opacity?: number
- ) => void;
- onReset: () => void;
-}
-
-function CourseCard({ course, colorSetting, originalColor, onColorChange, onReset }: CourseCardProps) {
- const hasChanged = colorSetting.color !== originalColor;
- const [customColor, setCustomColor] = useState(colorSetting.color);
- const [customGradient, setCustomGradient] = useState(
- colorSetting.gradient || 'linear-gradient(to right, #ff75c3, #ffa647)'
- );
- const [gradientStartColor, setGradientStartColor] = useState('#ff75c3');
- const [gradientEndColor, setGradientEndColor] = useState('#ffa647');
- const [opacity, setOpacity] = useState(colorSetting.opacity || 1);
-
- useEffect(() => {
- setCustomColor(colorSetting.color);
- setOpacity(colorSetting.opacity || 1);
- if (colorSetting.gradient) {
- setCustomGradient(colorSetting.gradient);
-
- const gradientMatch = colorSetting.gradient.match(
- /linear-gradient\(to right, (#[0-9a-fA-F]{3,6}), (#[0-9a-fA-F]{3,6})\)/
- );
- if (gradientMatch) {
- setGradientStartColor(gradientMatch[1]);
- setGradientEndColor(gradientMatch[2]);
- }
- }
- }, [colorSetting]);
-
- const getBackgroundStyle = () => {
- const opacityValue = opacity !== undefined ? opacity : 1;
-
- switch (colorSetting.colorType) {
- case 'gradient':
- if (colorSetting.gradient) {
- const gradientMatch = colorSetting.gradient.match(
- /linear-gradient\(to right, (#[0-9a-fA-F]{3,6}), (#[0-9a-fA-F]{3,6})\)/
- );
-
- if (gradientMatch) {
- const startColor = gradientMatch[1];
- const endColor = gradientMatch[2];
- const startRgba = hexToRgba(startColor, opacityValue);
- const endRgba = hexToRgba(endColor, opacityValue);
- return { background: `linear-gradient(to right, ${startRgba}, ${endRgba})` };
- }
- return { background: colorSetting.gradient };
- }
- return { background: customGradient };
-
- case 'solid':
- default:
- return { backgroundColor: hexToRgba(colorSetting.color, opacityValue) };
- }
- };
-
- const hexToRgba = (hex: string, opacity: number) => {
- hex = hex.replace('#', '');
-
- let r, g, b;
- if (hex.length === 3) {
- r = Number.parseInt(hex.charAt(0) + hex.charAt(0), 16);
- g = Number.parseInt(hex.charAt(1) + hex.charAt(1), 16);
- b = Number.parseInt(hex.charAt(2) + hex.charAt(2), 16);
- } else {
- r = Number.parseInt(hex.substring(0, 2), 16);
- g = Number.parseInt(hex.substring(2, 4), 16);
- b = Number.parseInt(hex.substring(4, 6), 16);
- }
-
- return `rgba(${r}, ${g}, ${b}, ${opacity})`;
- };
-
- const handleOpacityChange = (value: number) => {
- setOpacity(value);
- if (colorSetting.colorType === 'solid') {
- onColorChange(course.courseId, customColor, 'solid', undefined, value);
- } else {
- onColorChange(course.courseId, customGradient, 'gradient', customGradient, value);
- }
- };
-
- const handleCustomColorChange = (value: string) => {
- setCustomColor(value);
- onColorChange(course.courseId, value, 'solid', undefined, opacity);
- };
-
- const handleGradientColorChange = (startColor: string, endColor: string) => {
- const newGradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
- setGradientStartColor(startColor);
- setGradientEndColor(endColor);
- setCustomGradient(newGradient);
- onColorChange(course.courseId, newGradient, 'gradient', newGradient, opacity);
- };
-
- const opacitySlider = (
-
-
-
- 투명도
-
- {Math.round(opacity * 100)}%
-
-
handleOpacityChange(Number.parseFloat(e.target.value))}
- className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
- />
-
- );
-
- return (
-
-
- {course.courseTitle}
- {course.prof}
-
-
-
-
-
-
- {/* 배경 색상을 적용할 요소 */}
-
- 색상 선택
-
-
-
-
-
-
- Solid
-
-
- Gradient
-
-
-
-
-
- {colorPresets.map((color) => (
-
onColorChange(course.courseId, color, 'solid', undefined, opacity)}
- />
- ))}
-
-
-
- handleCustomColorChange(color)}
- className="w-full h-[120px]"
- />
-
-
-
- handleCustomColorChange(e.target.value)}
- className="h-8"
- />
-
-
- {opacitySlider}
-
-
-
-
- {gradientPresets.map((gradient, index) => (
-
onColorChange(course.courseId, gradient, 'gradient', gradient, opacity)}
- />
- ))}
-
-
-
-
-
시작 색상
-
handleGradientColorChange(color, gradientEndColor)}
- className="w-full h-[120px]"
- />
-
-
-
끝 색상
-
handleGradientColorChange(gradientStartColor, color)}
- className="w-full h-[120px]"
- />
-
-
-
-
-
- {opacitySlider}
-
-
-
-
-
-
선택된 스타일
-
-
- {colorSetting.colorType === 'solid'
- ? `${colorSetting.color} (${Math.round((colorSetting.opacity || 1) * 100)}%)`
- : `Gradient (${Math.round((colorSetting.opacity || 1) * 100)}%)`}
-
-
-
- {hasChanged && (
-
-
-
- )}
-
-
-
- );
-}
diff --git a/src/option/GithubStarBanner.tsx b/src/option/GithubStarBanner.tsx
deleted file mode 100644
index 298b63b..0000000
--- a/src/option/GithubStarBanner.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import type React from 'react';
-import { useState } from 'react';
-import { Github } from 'lucide-react';
-import { motion } from 'framer-motion';
-
-const GitHubStarBanner: React.FC = () => {
- const [isHovered, setIsHovered] = useState(false);
-
- const handleClick = () => {
- window.open('https://github.com/hs-shell/dotbugi', '_blank');
- };
-
- return (
- setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
- className="mt-4 mb-2 mx-1 py-4 px-2 bg-gradient-to-r from-slate-100 to-slate-200 rounded-lg border border-slate-200 shadow-sm cursor-pointer hover:shadow-md transition-all duration-300 group"
- >
-
-
-
이 프로젝트가 마음에 드시나요?
-
GitHub에서 스타를 눌러주세요!
-
-
- );
-};
-
-export default GitHubStarBanner;
diff --git a/src/option/Header.tsx b/src/option/Header.tsx
deleted file mode 100644
index c507aae..0000000
--- a/src/option/Header.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { loadDataFromStorage } from '@/lib/storage';
-import { TimeAgo } from '@/lib/utils';
-import { History } from 'lucide-react';
-import { useEffect, useState } from 'react';
-export default function Header({ location }: { location: string }) {
- const [lastRequest, setLastRequest] = useState('');
- useEffect(() => {
- loadDataFromStorage('lastRequestTime', (data: string | null) => {
- if (!data) setLastRequest('기록이 존재하지 않습니다.');
- else setLastRequest(TimeAgo(parseInt(data)));
- });
- }, []);
-
- let title = '안녕하세요';
-
- switch (location) {
- case '/vod':
- title = '동영상 강의 목록';
- window.document.title = '돋부기 | 강의 목록';
- break;
- case '/assignment':
- title = '과제 목록';
- window.document.title = '돋부기 | 과제 목록';
- break;
- case '/quiz':
- title = '퀴즈 목록';
- window.document.title = '돋부기 | 퀴즈 목록';
- break;
- default:
- title = '대시보드 🚀';
- window.document.title = '돋부기 | 대시보드';
- break;
- }
- return (
-
-
{title}
-
-
- {lastRequest}
-
-
- );
-}
diff --git a/src/option/Labo.tsx b/src/option/Labo.tsx
deleted file mode 100644
index e856125..0000000
--- a/src/option/Labo.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { Button } from '@/components/ui/button';
-import { calendar_v3 } from 'googleapis';
-
-type CalendarEvent = calendar_v3.Schema$Event;
-
-const Labo: React.FC = () => {
- const [token, setToken] = useState(null);
- const [events, setEvents] = useState([]);
-
- const fetchCalendarEvents = (token: string) => {
- fetch(
- 'https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=' +
- new Date().toISOString() +
- '&orderBy=startTime&singleEvents=true',
- {
- headers: {
- Authorization: `Bearer ${token}`,
- },
- }
- )
- .then((response) => response.json())
- .then((data) => setEvents(data.items || []))
- .catch((error) => console.error('캘린더 이벤트 가져오기 실패:', error));
- };
-
- useEffect(() => {
- chrome.identity.getAuthToken({ interactive: false }, (cachedToken) => {
- if (chrome.runtime.lastError || !cachedToken) {
- console.error('자동 로그인 실패:', chrome.runtime.lastError?.message);
- } else {
- setToken(cachedToken);
- fetchCalendarEvents(cachedToken);
- }
- });
- }, []);
-
- const handleLogin = () => {
- chrome.identity.getAuthToken({ interactive: true }, (newToken) => {
- if (chrome.runtime.lastError || !newToken) {
- console.error(chrome.runtime.lastError);
- return;
- }
- setToken(newToken);
- fetchCalendarEvents(newToken);
- });
- };
-
- const handleLogout = () => {
- if (!token) return;
-
- chrome.identity.removeCachedAuthToken({ token }, () => {
- fetch(`https://oauth2.googleapis.com/revoke?token=${token}`, {
- method: 'POST',
- headers: {
- 'Content-type': 'application/x-www-form-urlencoded',
- },
- })
- .then((response) => {
- if (response.ok) {
- setToken(null);
- setEvents([]);
- } else {
- console.error('토큰 폐기 요청에 실패했습니다.');
- }
- })
- .catch((error) => {
- console.error('에러 발생:', error);
- });
- });
- };
-
- const addCalendarEvent = () => {
- if (!token) return;
-
- const event = {
- summary: '테스트 이벤트 🎉',
- description: '이것은 Google Calendar API를 사용한 이벤트 생성입니다.',
- start: {
- dateTime: new Date().toISOString(),
- timeZone: 'Asia/Seoul',
- },
- end: {
- dateTime: new Date(new Date().getTime() + 60 * 60 * 1000).toISOString(),
- timeZone: 'Asia/Seoul',
- },
- };
-
- fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${token}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(event),
- })
- .then((response) => response.json())
- .then(() => {
- alert('새로운 이벤트가 추가되었습니다!');
- fetchCalendarEvents(token);
- })
- .catch((error) => console.error('이벤트 추가 실패:', error));
- };
-
- return (
-
-
Google Calendar 연동
- {token ? (
-
-
- 새 이벤트 추가
-
-
- 연동 해제
-
-
내 캘린더 일정
-
- {events.length > 0 ? (
- events.map((event, index) => (
-
- {event.summary}
-
- {event.start?.dateTime?.replace('T', ' ').substring(0, 16)} ~{' '}
- {event.end?.dateTime?.replace('T', ' ').substring(0, 16)}
-
-
- ))
- ) : (
- 일정이 없습니다.
- )}
-
-
- ) : (
-
Google 계정 연동
- )}
-
- );
-};
-
-export default Labo;
diff --git a/src/option/Sidebar.tsx b/src/option/Sidebar.tsx
deleted file mode 100644
index 811751c..0000000
--- a/src/option/Sidebar.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-'use client';
-
-import { Calendar, LayoutDashboard, NotebookText, Palette, Video, Zap } from 'lucide-react';
-import type React from 'react';
-import { Link, useLocation } from 'react-router-dom';
-import icon from '@/assets/icon.png';
-import GitHubStarBanner from './GithubStarBanner';
-
-const Sidebar: React.FC = () => {
- return (
-
-
-
-
-
{
- window.open('https://learn.hansung.ac.kr/', '_blank');
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
-
- } label="강의" />
- } label="과제" />
- } label="퀴즈" />
-
-
-
-
- } label="캘린더 연동" />
- } label="색상 변경" />
-
-
-
-
-
-
-
-
- );
-};
-
-interface SidebarItemProps {
- to: string;
- icon: React.ReactNode;
- label: string;
-}
-
-const SidebarItem: React.FC = ({ to, icon, label }) => {
- const location = useLocation();
- const isActive = location.pathname === to; // 현재 경로와 to가 일치하는지 확인
-
- return (
-
-
- {icon}
- {label}
-
-
- );
-};
-
-export default Sidebar;
diff --git a/src/option/SummaryCard.tsx b/src/option/SummaryCard.tsx
deleted file mode 100644
index 2162a84..0000000
--- a/src/option/SummaryCard.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Card, CardContent, CardHeader } from '@/components/ui/card';
-import { Progress } from '@/components/ui/progress';
-import useCardData from '@/hooks/useCardData';
-import { Zap, Video, NotebookTextIcon } from 'lucide-react';
-import { ReactNode } from 'react';
-import clsx from 'clsx';
-import { Link } from 'react-router-dom';
-
-interface CardItemProps {
- title: string;
- icon: ReactNode;
- data: { done: number; total: number }[];
- color: string;
- link: string;
-}
-
-const colorMap: Record = {
- blue: 'bg-blue-500',
- violet: 'bg-violet-500',
- amber: 'bg-amber-500',
- red: 'bg-red-500',
- green: 'bg-green-500',
-};
-
-export default function SummaryCard() {
- const { vodSummary, assignSummary, quizSummary } = useCardData();
-
- const CardItem = ({ title, icon, data, color, link }: CardItemProps) => {
- const done = data.length > 0 ? data[0].done : 0;
- const total = data.length > 0 ? data[0].total : 1;
- const percentage = Math.round((done / total) * 100);
- const bgColorClass = colorMap[color] || 'bg-gray-500'; // 기본값 설정
-
- return (
-
-
-
- {title}
- {icon}
-
-
- {title.includes('퀴즈') ? (
- {data.length > 0 ? `${total} 개` : '0 개'}
- ) : (
- {data.length > 0 ? `${done} / ${total}` : '0 개'}
- )}
- {title.includes('퀴즈') ? (
-
- ) : (
-
- )}
-
- {title.includes('퀴즈') ? '직접 확인' : isNaN(percentage) ? 0 + '% 완료' : percentage + '% 완료'}
-
-
-
-
- );
- };
-
- return (
-
- }
- data={vodSummary}
- color="blue"
- link={'vod'}
- />
- }
- data={assignSummary}
- color="violet"
- link={'assignment'}
- />
- }
- data={quizSummary}
- color="amber"
- link={'quiz'}
- />
-
- );
-}
diff --git a/src/option/calendar.tsx b/src/option/calendar.tsx
deleted file mode 100644
index 02513b2..0000000
--- a/src/option/calendar.tsx
+++ /dev/null
@@ -1,586 +0,0 @@
-import { useState, useMemo, useEffect } from 'react';
-import { Card } from '@/components/ui/card';
-import { Button } from '@/components/ui/button';
-import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
-import { cn } from '@/lib/utils';
-import {
- format,
- addMonths,
- subMonths,
- startOfMonth,
- startOfWeek,
- isSameMonth,
- isToday,
- isBefore,
- addDays,
- isSunday,
-} from 'date-fns';
-import { ChevronLeft, ChevronRight, NotebookText, Zap, ListFilter, CalendarArrowUp } from 'lucide-react';
-import useCalendarEvents, { CalendarEvent } from '@/hooks/useCalendarEvents';
-import filter from '@/assets/filter.svg';
-import { Label } from '@/components/ui/label';
-import {
- getOAuthToken,
- addCalendarEvent,
- getCalendarEvents,
- convertCalendarEventsToGoogleEvents,
- GoogleCalendarEvent,
-} from '@/lib/calendarUtils';
-import { toast } from '@/hooks/use-toast';
-import { loadDataFromStorage } from '@/lib/storage';
-import GoogleCalendar from '@/assets/calendar.png';
-function isSameDate(d1: Date, d2: Date) {
- return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate();
-}
-
-function isInEventRange(day: Date, event: CalendarEvent) {
- if (!event.start || !event.end) return false;
- return day >= event.start && day <= event.end;
-}
-
-function isSingleDayEvent(event: CalendarEvent) {
- if (!event.start || !event.end) return false;
- return isSameDate(event.start, event.end);
-}
-
-function getRangePosition(day: Date, event: CalendarEvent): 'single' | 'start' | 'middle' | 'end' | 'after' | null {
- if (!event.start || !event.end) return null;
-
- if (!isInEventRange(day, event)) return null;
- if (isSingleDayEvent(event)) return 'single';
- if (isSameDate(day, addDays(event.start, 1))) return 'after';
- if (isSameDate(day, event.start)) return 'start';
- if (isSameDate(day, event.end)) return 'end';
- return 'middle';
-}
-
-function eventsOverlap(a: CalendarEvent, b: CalendarEvent) {
- if (a.start === null || a.end === null || b.start === null || b.end === null) {
- return false;
- }
- return a.start <= b.end && b.start <= a.end;
-}
-// 헬퍼: hex 색상을 rgba 문자열로 변환 (투명도 적용)
-function hexToRgba(hex: string, opacity: number): string {
- hex = hex.replace('#', '');
- const r = parseInt(hex.substring(0, 2), 16);
- const g = parseInt(hex.substring(2, 4), 16);
- const b = parseInt(hex.substring(4, 6), 16);
- return `rgba(${r}, ${g}, ${b}, ${opacity})`;
-}
-
-interface CalendarEventWithRow extends CalendarEvent {
- row: number;
-}
-
-const colorClasses = [
- 'bg-rose-100 text-rose-800',
- 'bg-amber-100 text-amber-800',
- 'bg-emerald-100 text-emerald-800',
- 'bg-sky-100 text-sky-800',
- 'bg-violet-100 text-violet-800',
- 'bg-fuchsia-100 text-fuchsia-800',
- 'bg-lime-100 text-lime-800',
- 'bg-cyan-100 text-cyan-800',
-];
-
-export function Calendar() {
- const events = useCalendarEvents();
- const [currentMonth, setCurrentMonth] = useState(new Date());
- const [selectedDate, setSelectedDate] = useState(null);
-
- // 필터 state 관리
- const [selectedFilters, setSelectedFilters] = useState([]);
- const [isFilterOpen, setIsFilterOpen] = useState(false);
-
- const typeFilters = [
- { value: 'vod', label: '동영상 강의' },
- { value: 'assign', label: '과제' },
- { value: 'quiz', label: '퀴즈' },
- ];
-
- const titleFilters = Array.from(new Set(events.map((event) => event.title))).map((title) => ({
- value: title,
- label: title,
- }));
-
- const filterOptions = [...typeFilters, ...titleFilters];
-
- const toggleFilter = (filter: string) => {
- setSelectedFilters((prev) => (prev.includes(filter) ? prev.filter((f) => f !== filter) : [...prev, filter]));
- };
-
- const clearFilters = () => {
- setSelectedFilters([]);
- };
-
- const isFilterSet = selectedFilters.length > 0;
-
- const firstDayOfMonth = startOfMonth(currentMonth);
- const startDate = startOfWeek(firstDayOfMonth, { weekStartsOn: 0 });
- const dayList = useMemo(() => {
- return Array.from({ length: 42 }, (_, i) => addDays(startDate, i));
- }, [startDate]);
-
- const handlePrevMonth = () => {
- setCurrentMonth((prev) => subMonths(prev, 1));
- };
-
- const handleNextMonth = () => {
- setCurrentMonth((prev) => addMonths(prev, 1));
- };
-
- const handleSelectDate = (date: Date) => {
- if (isSameMonth(date, currentMonth)) {
- setSelectedDate(date);
- } else {
- if (isBefore(date, firstDayOfMonth)) {
- handlePrevMonth();
- } else {
- handleNextMonth();
- }
- setSelectedDate(date);
- }
- };
-
- const eventsWithRow = useMemo(() => {
- const sorted = [...events].sort((a, b) => {
- const aTime = a.start ? a.start.getTime() : Number.MIN_SAFE_INTEGER;
- const bTime = b.start ? b.start.getTime() : Number.MIN_SAFE_INTEGER;
- return aTime - bTime;
- });
-
- const assigned: CalendarEventWithRow[] = [];
- for (const event of sorted) {
- let row = 0;
- while (assigned.some((e) => e.row === row && eventsOverlap(e, event))) {
- row++;
- }
- assigned.push({ ...event, row });
- }
- return assigned;
- }, [events]);
-
- const subjectList = useMemo(() => {
- return Array.from(new Set(events.map((event) => event.title)));
- }, [events]);
-
- const subjectColorMap = useMemo(() => {
- const map: { [key: string]: string } = {};
- subjectList.forEach((subject, index) => {
- map[subject] = colorClasses[index % colorClasses.length];
- });
- return map;
- }, [subjectList]);
-
- // 저장소에서 강의 색상 데이터 불러오기 (vod 데이터에 적용)
- const [courseColors, setCourseColors] = useState<{
- [courseId: string]: { color: string; colorType: string; gradient?: string; opacity: number };
- } | null>(null);
-
- useEffect(() => {
- loadDataFromStorage('courseColors', (data: string | null) => {
- if (data) {
- try {
- const parsedData = JSON.parse(data);
- const map = Array.isArray(parsedData)
- ? parsedData.reduce(
- (acc, cur) => {
- acc[cur.courseId] = cur;
- return acc;
- },
- {} as { [courseId: string]: { color: string; colorType: string; gradient?: string; opacity: number } }
- )
- : {};
- setCourseColors(map);
- } catch (error) {
- console.error('courseColors 파싱 에러:', error);
- }
- }
- });
- }, []);
-
- const renderEvents = (day: Date, isCurrent: boolean) => {
- const weekStart = startOfWeek(day, { weekStartsOn: 0 });
- const weekEnd = addDays(weekStart, 6);
-
- const weekEvents = eventsWithRow.filter(
- (event) => event.end !== null && event.start !== null && event.end >= weekStart && event.start <= weekEnd
- );
-
- const weekStack: { [eventId: string]: number } = {};
- weekEvents
- .sort((a, b) => {
- const aTime = a.start ? a.start.getTime() : Number.MIN_SAFE_INTEGER;
- const bTime = b.start ? b.start.getTime() : Number.MIN_SAFE_INTEGER;
- return aTime - bTime;
- })
- .forEach((event) => {
- let row = 0;
- while (
- Object.entries(weekStack).some(([id, assignedRow]) => {
- if (id === event.id) return false;
- const assignedEvent = weekEvents.find((e) => e.id === id);
- if (!assignedEvent) return false;
- const eventStartInWeek = event.start ? (event.start < weekStart ? weekStart : event.start) : weekStart;
- const eventEndInWeek = event.end ? (event.end > weekEnd ? weekEnd : event.end) : weekEnd;
- const assignedStartInWeek = assignedEvent.start
- ? assignedEvent.start < weekStart
- ? weekStart
- : assignedEvent.start
- : weekStart;
- const assignedEndInWeek = assignedEvent.end
- ? assignedEvent.end > weekEnd
- ? weekEnd
- : assignedEvent.end
- : weekEnd;
-
- return (
- assignedRow === row && eventStartInWeek <= assignedEndInWeek && assignedStartInWeek <= eventEndInWeek
- );
- })
- ) {
- row++;
- }
- weekStack[event.id] = row;
- });
-
- const typeFilterValues = typeFilters.map((f) => f.value);
- const selectedTypeFilters = selectedFilters.filter((f) => typeFilterValues.includes(f));
- const selectedTitleFilters = selectedFilters.filter((f) => !typeFilterValues.includes(f));
-
- const eventsOfTheDay = eventsWithRow
- .filter((event) => {
- if (!isInEventRange(day, event)) return false;
- if (selectedTypeFilters.length > 0 && selectedTitleFilters.length > 0) {
- return selectedTypeFilters.includes(event.type) && selectedTitleFilters.includes(event.title);
- } else if (selectedTypeFilters.length > 0) {
- return selectedTypeFilters.includes(event.type);
- } else if (selectedTitleFilters.length > 0) {
- return selectedTitleFilters.includes(event.title);
- }
- return true;
- })
- .map((event) => ({ ...event, row: weekStack[event.id] ?? 0 }));
-
- const maxRow = eventsOfTheDay.length > 0 ? Math.max(...eventsOfTheDay.map((e) => e.row)) : -1;
- const numRows = maxRow + 1;
-
- return (
-
- {Array.from({ length: numRows }, (_, rowIndex) => {
- const event = eventsOfTheDay.find((e) => e.row === rowIndex);
- if (event) {
- const rangePosition = getRangePosition(day, event);
- if (!rangePosition) return
;
-
- const eventId = event.id.split('-')[0];
- const isVodCustom = event.type === 'vod' && courseColors && eventId && courseColors[eventId];
- let customStyle = {};
-
- if (isVodCustom) {
- const courseData = courseColors?.[eventId];
-
- const eventStart = event.start ?? new Date(0);
- const eventEnd = event.end ?? new Date(0);
-
- const totalDays = Math.floor((eventEnd.getTime() - eventStart.getTime()) / (1000 * 3600 * 24)) + 1;
-
- if (courseData?.colorType === 'gradient' && courseData.gradient && totalDays > 1) {
- const dayIndex = Math.floor((day.getTime() - eventStart.getTime()) / (1000 * 3600 * 24));
-
- const regex = /linear-gradient\(to right, (#[0-9a-fA-F]+), (#[0-9a-fA-F]+)\)/;
- const match = courseData.gradient.match(regex);
-
- if (match) {
- const rgba1 = hexToRgba(match[1], courseData.opacity);
- const rgba2 = hexToRgba(match[2], courseData.opacity);
- customStyle = {
- backgroundImage: `linear-gradient(to right, ${rgba1}, ${rgba2})`,
- backgroundSize: `${totalDays * 100}% 100%`,
- backgroundPosition: `${-(dayIndex * 100)}% 0`,
- };
- } else {
- customStyle = { backgroundImage: courseData.gradient, opacity: courseData.opacity };
- }
- } else {
- // 단색(hex)인 경우: null 값 방지 후 rgba 변환
- customStyle = { background: hexToRgba(courseData?.color ?? '#000000', courseData?.opacity ?? 1) };
- }
- }
-
- if (rangePosition === 'single') {
- return (
-
-
- {event.type === 'assign' ? (
-
- ) : event.type === 'quiz' ? (
-
- ) : null}
-
-
- {event.title} - {event.subject}
-
-
- );
- }
-
- const isStart = rangePosition === 'start';
- const isEnd = rangePosition === 'end';
- const showTitle = isStart;
-
- const additionalClasses = cn(isStart && 'rounded-l-sm ml-1', isEnd && 'rounded-r-sm mr-1');
-
- const defaultClass = isVodCustom
- ? ''
- : isCurrent
- ? `${subjectColorMap[event.title]} text-zinc-700`
- : 'bg-zinc-100 text-zinc-300';
-
- return (
-
- {showTitle && (
-
- {event.title} - {event.subject}
-
- )}
-
- );
- } else {
- return
;
- }
- })}
-
- );
- };
-
- const handleRedirectToCalendar = async () => {
- const token = await getOAuthToken();
- if (!token) {
- toast({
- title: '동기화 실패 🚨',
- description: '구글 캘린더 연동 상태를 확인해주세요',
- variant: 'destructive',
- });
- return;
- }
-
- window.open('https://calendar.google.com/calendar', '_blank');
- };
- const handleCalendarSync = async () => {
- const token = await getOAuthToken();
- if (!token) {
- toast({
- title: '동기화 실패 🚨',
- description: '구글 캘린더 연동 상태를 확인해주세요',
- variant: 'destructive',
- });
- return;
- }
-
- try {
- const existingEvents: GoogleCalendarEvent[] = await getCalendarEvents(token);
- const newEventsData: GoogleCalendarEvent[] = convertCalendarEventsToGoogleEvents(events);
-
- const normalizeEvent = (event: {
- summary: string;
- description?: string;
- start: { dateTime: string };
- end: { dateTime: string };
- }) => ({
- summary: event.summary.trim().toLowerCase(),
- description: (event.description || '').trim().toLowerCase(),
- startTime: new Date(event.start.dateTime).getTime(),
- endTime: new Date(event.end.dateTime).getTime(),
- });
-
- const uniqueNewEvents = newEventsData.filter((newEvent) => {
- const normNew = normalizeEvent(newEvent);
- return !existingEvents.some((existingEvent) => {
- const normExisting = normalizeEvent(existingEvent);
- return (
- normExisting.summary === normNew.summary &&
- normExisting.description === normNew.description &&
- normExisting.startTime === normNew.startTime &&
- normExisting.endTime === normNew.endTime
- );
- });
- });
-
- if (uniqueNewEvents.length === 0) {
- toast({
- title: '캘린더가 최신 상태입니다 🤩',
- description: '이미 최신 정보로 동기화되었습니다.',
- variant: 'default',
- });
- } else {
- for (const event of uniqueNewEvents) {
- await addCalendarEvent(event, token);
- }
- toast({
- title: '동기화 성공 🚀',
- description: `${uniqueNewEvents.length}개의 이벤트가 추가되었습니다.`,
- variant: 'default',
- });
- }
- } catch (error) {
- toast({
- title: '동기화 오류 🚨',
- description: '이벤트 동기화 중 오류가 발생했습니다.',
- variant: 'destructive',
- });
- console.error(error);
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
{format(currentMonth, 'yyyy년 MM월')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- setIsFilterOpen((prev) => !prev)}
- className="flex justify-self-end rounded-lg gap-1 bg-white hover:bg-zinc-100 transition-all duration-200 mt-2 mb-2 mr-5 ml-2 py-3 px-5"
- >
- {isFilterSet ? (
-
- ) : (
-
- )}
-
-
-
-
- {filterOptions.map((option) => (
-
-
-
toggleFilter(option.value)}
- className="shadow-md rounded-sm peer h-5 w-5 cursor-pointer appearance-none border border-zinc-800 bg-white checked:border-primary checked:bg-primary focus:outline-none focus:ring-primary focus:ring-offset-0"
- />
-
-
-
-
-
- {option.label}
-
-
- ))}
-
-
- 모두 지우기
-
- setIsFilterOpen(false)}
- >
- 닫기
-
-
-
-
-
-
-
- {/* 요일 헤더 */}
-
- {['일', '월', '화', '수', '목', '금', '토'].map((dayName) => (
-
- {dayName}
-
- ))}
-
-
- {/* 달력 그리드 */}
-
- {dayList.map((dayItem) => {
- const isCurrent = isSameMonth(dayItem, currentMonth);
- const isTodayDate = isToday(dayItem);
- const sunday = isSunday(dayItem);
- const isSelected = selectedDate && isSameDate(selectedDate, dayItem);
- return (
-
handleSelectDate(dayItem)}
- className={cn(
- 'relative cursor-pointer w-full min-h-[120px] py-2 rounded-lg',
- isCurrent ? 'bg-white' : 'text-gray-300',
- isSelected && 'bg-zinc-50',
- 'hover:bg-zinc-50 transition-all duration-300'
- )}
- >
-
-
- {format(dayItem, 'd')}
-
- {renderEvents(dayItem, isCurrent)}
-
- );
- })}
-
-
- );
-}
diff --git a/src/option/components/AssignCard.tsx b/src/option/components/AssignCard.tsx
deleted file mode 100644
index a20acec..0000000
--- a/src/option/components/AssignCard.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { useEffect, useState } from 'react';
-import { Card, CardContent } from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
-import { Assign } from '@/content/types';
-
-import { calculateDueDate, calculateRemainingTime } from '@/lib/utils';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
-
-interface TaskStatusCardProps {
- assign: Assign;
-}
-
-const AssignCard: React.FC = ({ assign }) => {
- const [showRemainingTime, setShowRemainingTime] = useState(false);
-
- useEffect(() => {
- let timer: NodeJS.Timeout;
- if (showRemainingTime) {
- timer = setTimeout(() => {
- setShowRemainingTime(false);
- }, 5000);
- }
- return () => clearTimeout(timer);
- }, [showRemainingTime]);
-
- if (!assign) {
- return <>>;
- }
- const timeDifference = calculateDueDate(assign.dueDate);
-
- return (
- {
- window.open(assign.url, '_blank');
- }}
- >
-
-
-
-
{assign.courseTitle}
-
-
{assign.title}
-
-
-
-
- {assign.isSubmit ? '제출완료' : '제출필요'}
-
-
-
-
-
- {assign.dueDate}
-
-
-
- {calculateRemainingTime(assign.dueDate)}
-
-
-
-
-
-
- );
-};
-
-export default AssignCard;
diff --git a/src/option/components/AssignContent.tsx b/src/option/components/AssignContent.tsx
deleted file mode 100644
index 2c741c0..0000000
--- a/src/option/components/AssignContent.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { Card, CardContent } from '@/components/ui/card';
-import { useEffect, useState } from 'react';
-import { Assign } from '@/content/types';
-import { loadDataFromStorage } from '@/lib/storage';
-import AssignCard from './AssignCard';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import thung from '@/assets/thung.png';
-
-export function AssignContent() {
- const [assignArray, setAssignArray] = useState([]);
-
- useEffect(() => {
- loadDataFromStorage('assign', (data: string | null) => {
- if (!data) return;
-
- let parsedData: Assign[];
- if (typeof data === 'string') {
- try {
- parsedData = JSON.parse(data);
- } catch (error) {
- console.error('JSON 파싱 에러:', error);
- return;
- }
- } else {
- parsedData = data as Assign[];
- }
-
- const sortedAssignArray = parsedData.sort((a, b) => {
- // 미제출 우선 배치
- if (!a.isSubmit && b.isSubmit) return -1;
- if (a.isSubmit && !b.isSubmit) return 1;
-
- // 마감 빠른 순 (null은 맨 뒤)
- const dateA = a.dueDate === null ? Number.MAX_SAFE_INTEGER : new Date(a.dueDate).getTime();
- const dateB = b.dueDate === null ? Number.MAX_SAFE_INTEGER : new Date(b.dueDate).getTime();
- if (dateA !== dateB) return dateA - dateB;
-
- return a.courseTitle.localeCompare(b.courseTitle);
- });
-
- setAssignArray(sortedAssignArray);
- });
- }, []);
-
- return (
-
-
- {assignArray.length === 0 ? (
-
-
-
- ) : (
-
-
-
- {assignArray.map((assign) => {
- const key = `${assign.courseId}-${assign.title}-${assign.dueDate}`;
- return
;
- })}
-
-
-
- )}
-
-
- );
-}
diff --git a/src/option/components/CourseDetailModal.tsx b/src/option/components/CourseDetailModal.tsx
deleted file mode 100644
index fdcb39a..0000000
--- a/src/option/components/CourseDetailModal.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-import ReactDOM from 'react-dom';
-import { useEffect, useState } from 'react';
-import { Button } from '@/components/ui/button';
-import type { Vod } from '@/content/types';
-import { Card, CardContent } from '@/components/ui/card';
-import { BadgeCheck, Siren, TriangleAlert, Video, X } from 'lucide-react';
-import { calculateRemainingTimeByRange, calculateTimeDifference, formatDateString } from '@/lib/utils';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import type React from 'react';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
-import { Badge } from '@/components/ui/badge';
-
-interface ModalProps {
- vodList: Vod[];
- onClose: () => void;
-}
-
-const CourseDetailModal: React.FC = ({ vodList, onClose }: ModalProps) => {
- const [isVisible, setIsVisible] = useState(false);
- const [showRemainingTime, setShowRemainingTime] = useState(false);
-
- useEffect(() => {
- setIsVisible(true);
- return () => {
- setIsVisible(false);
- };
- }, []);
-
- useEffect(() => {
- let timer: NodeJS.Timeout;
- if (showRemainingTime) {
- timer = setTimeout(() => {
- setShowRemainingTime(false);
- }, 5000);
- }
- return () => clearTimeout(timer);
- }, [showRemainingTime]);
-
- const handleClose = () => {
- setIsVisible(false);
- setTimeout(() => {
- onClose();
- }, 300);
- };
-
- const timeDifference = calculateTimeDifference(vodList[0].range);
-
- const modalContent = (
-
-
e.stopPropagation()}
- >
-
-
-
-
-
-
{vodList[0].courseTitle}
-
{vodList[0].subject}
-
-
-
-
- {vodList.map((vod, index) => {
- const isAttendance = vod.isAttendance.toLowerCase().trim() === 'o';
- return (
-
{
- window.open(`${vod.url.replace('view', 'viewer')}`, '_blank', 'VodContentWindow');
- }}
- >
-
-
-
-
{vod.title}
-
{vod.length}
-
- {isAttendance ? (
-
- ) : timeDifference.message.includes('마감') ? (
-
- ) : (
-
- )}
-
-
- );
- })}
-
-
-
-
-
-
-
- 출석인정기간 : {formatDateString(vodList[0].range)}
-
-
-
- {calculateRemainingTimeByRange(vodList[0].range)}
-
-
-
-
-
-
-
- );
-
- const modalRoot = document.getElementById('shadow-modal-root');
- if (!modalRoot) {
- console.error('Modal root element not found.');
- return null;
- }
-
- return ReactDOM.createPortal(modalContent, modalRoot);
-};
-
-export default CourseDetailModal;
diff --git a/src/option/components/CourseHideModal.tsx b/src/option/components/CourseHideModal.tsx
deleted file mode 100644
index 8ca515a..0000000
--- a/src/option/components/CourseHideModal.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import ReactDOM from 'react-dom';
-import { useEffect, useState } from 'react';
-import type { Vod } from '@/content/types';
-import type React from 'react';
-import { Button } from '@/components/ui/button';
-
-interface ModalProps {
- vod: Vod;
- onClose: () => void;
-}
-
-const CourseHideModal: React.FC = ({ vod, onClose }) => {
- const [isVisible, setIsVisible] = useState(false);
-
- useEffect(() => {
- setIsVisible(true);
- return () => {
- setIsVisible(false);
- };
- }, []);
-
- const handleClose = () => {
- setIsVisible(false);
- setTimeout(() => {
- onClose();
- }, 300);
- };
-
- const handleHideCourse = () => {
- handleClose();
- };
-
- const modalContent = (
-
-
e.stopPropagation()} // 모달 클릭 시 닫히지 않도록 방지
- >
-
해당 강좌를 숨기시겠습니까?
-
- {vod.courseTitle} - {vod.subject}
-
-
-
-
- 닫기
-
-
- 숨기기
-
-
-
-
- );
-
- const modalRoot = document.getElementById('shadow-modal-root');
- if (!modalRoot) {
- console.error('Modal root element not found.');
- return null;
- }
-
- return ReactDOM.createPortal(modalContent, modalRoot);
-};
-
-export default CourseHideModal;
diff --git a/src/option/components/CustomTabs.tsx b/src/option/components/CustomTabs.tsx
deleted file mode 100644
index a95aa29..0000000
--- a/src/option/components/CustomTabs.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { cn } from '@/lib/utils';
-
-interface CustomTabsProps {
- tabs: string[];
- activeTab: string;
- onTabChange: (tab: string) => void;
-}
-
-export function CustomTabs({ tabs, activeTab, onTabChange }: CustomTabsProps) {
- return (
-
- {tabs.map((tab) => (
- onTabChange(tab)}
- className={cn(
- 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
- activeTab === tab
- ? 'bg-background text-foreground shadow-sm'
- : 'text-muted-foreground hover:bg-background/50 hover:text-foreground'
- )}
- >
- {tab}
-
- ))}
-
- );
-}
diff --git a/src/option/components/QuizCard.tsx b/src/option/components/QuizCard.tsx
deleted file mode 100644
index 74d08c7..0000000
--- a/src/option/components/QuizCard.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { useEffect, useState } from 'react';
-import { Card, CardContent } from '@/components/ui/card';
-import { Badge } from '@/components/ui/badge';
-import { Quiz } from '@/content/types';
-
-import { calculateDueDate, calculateRemainingTime } from '@/lib/utils';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
-
-interface TaskStatusCardProps {
- quiz: Quiz;
-}
-
-const QuizCard: React.FC = ({ quiz }) => {
- const [showRemainingTime, setShowRemainingTime] = useState(false);
-
- useEffect(() => {
- let timer: NodeJS.Timeout;
- if (showRemainingTime) {
- timer = setTimeout(() => {
- setShowRemainingTime(false);
- }, 5000);
- }
- return () => clearTimeout(timer);
- }, [showRemainingTime]);
-
- if (!quiz) {
- return <>>;
- }
-
- const timeDifference = calculateDueDate(quiz.dueDate);
-
- return (
- {
- window.open(quiz.url, '_blank');
- }}
- >
-
-
-
-
{quiz.courseTitle}
-
-
{quiz.title}
-
-
-
-
- 직접확인
-
-
-
-
-
- {quiz.dueDate}
-
-
-
- {calculateRemainingTime(quiz.dueDate)}
-
-
-
-
-
-
- );
-};
-
-export default QuizCard;
diff --git a/src/option/components/QuizContent.tsx b/src/option/components/QuizContent.tsx
deleted file mode 100644
index 7724c9d..0000000
--- a/src/option/components/QuizContent.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Card, CardContent } from '@/components/ui/card';
-import { useEffect, useState } from 'react';
-import { Quiz } from '@/content/types';
-import { loadDataFromStorage } from '@/lib/storage';
-import QuizCard from './QuizCard';
-import { ScrollArea } from '@radix-ui/react-scroll-area';
-import thung from '@/assets/thung.png';
-
-export function QuizContent() {
- const [quizArray, setQuizArray] = useState([]);
-
- useEffect(() => {
- loadDataFromStorage('quiz', (data: string | null) => {
- if (!data) return;
-
- let parsedData: Quiz[];
- if (typeof data === 'string') {
- try {
- parsedData = JSON.parse(data);
- } catch (error) {
- console.error('JSON 파싱 에러:', error);
- return;
- }
- } else {
- parsedData = data as Quiz[];
- }
-
- const sortedQuizArray = parsedData.sort((a, b) => {
- // 마감 빠른 순 (null은 맨 뒤)
- const dateA = a.dueDate === null ? Number.MAX_SAFE_INTEGER : new Date(a.dueDate).getTime();
- const dateB = b.dueDate === null ? Number.MAX_SAFE_INTEGER : new Date(b.dueDate).getTime();
- if (dateA !== dateB) return dateA - dateB;
-
- return a.courseTitle.localeCompare(b.courseTitle);
- });
-
- setQuizArray(sortedQuizArray);
- });
- }, []);
-
- return (
-
-
- {quizArray.length === 0 ? (
-
-
-
- ) : (
-
-
-
- {quizArray.map((quiz) => {
- const key = `${quiz.courseId}-${quiz.title}-${quiz.dueDate}`;
- return ;
- })}
-
-
-
- )}
-
-
- );
-}
diff --git a/src/option/components/Sidebar.tsx b/src/option/components/Sidebar.tsx
deleted file mode 100644
index ad90465..0000000
--- a/src/option/components/Sidebar.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { SidebarHeader } from './SidebarHeader';
-import { TYPES } from '@/content/types';
-import { data } from './data';
-
-export function Sidebar({ activeTab, setActiveTab }: { activeTab: TYPES; setActiveTab: (type: TYPES) => void }) {
- return (
-
-
-
-
- {data.map((item) => (
- {
- setActiveTab(item.type);
- }}
- >
-
-
- {item.icon}
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/src/option/components/SidebarHeader.tsx b/src/option/components/SidebarHeader.tsx
deleted file mode 100644
index dbd4f7a..0000000
--- a/src/option/components/SidebarHeader.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import icon from '@/assets/icon.png';
-
-export function SidebarHeader() {
- return (
-
-
-
- );
-}
diff --git a/src/option/components/VodCard.tsx b/src/option/components/VodCard.tsx
deleted file mode 100644
index 4484b07..0000000
--- a/src/option/components/VodCard.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { useState } from 'react';
-import { Card, CardContent } from '@/components/ui/card';
-import { Progress } from '@/components/ui/progress';
-import { Badge } from '@/components/ui/badge';
-import { Vod } from '@/content/types';
-
-import { calculateRemainingTimeByRange, formatDateString, removeSquareBrackets } from '@/lib/utils';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
-import CourseDetailModal from './CourseDetailModal';
-
-interface TaskStatusCardProps {
- vodList: Vod[];
-}
-
-const VodCard: React.FC = ({ vodList }) => {
- const [isVisible, setIsVisible] = useState(false);
-
- if (vodList.length === 0) return <>>;
-
- let value = 0;
- vodList.forEach((vod) => {
- if (vod.isAttendance.toLowerCase() === 'o') value += 1;
- });
- const total = (value * 100) / vodList.length;
-
-
- return (
- <>
- {isVisible && (
- {
- setIsVisible(false);
- }}
- />
- )}
- {
- setIsVisible(true);
- }}
- >
-
-
-
-
- {removeSquareBrackets(vodList[0].courseTitle)}
-
-
-
{vodList[0].subject}
-
-
-
-
- {total === 0 ? '학습전' : vodList[0].weeklyAttendance.toLowerCase() === 'o' ? '학습완료' : '학습중'}
-
-
-
-
-
- {formatDateString(vodList[0].range)}
-
-
-
- {calculateRemainingTimeByRange(vodList[0].range)}
-
-
-
-
-
-
- 진도율
- {Math.round(total)}%
-
-
-
-
-
- >
- );
-};
-
-export default VodCard;
diff --git a/src/option/components/VodContent.tsx b/src/option/components/VodContent.tsx
deleted file mode 100644
index 9871b6d..0000000
--- a/src/option/components/VodContent.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { Card, CardContent } from '@/components/ui/card';
-import { useEffect, useState } from 'react';
-import { Vod } from '@/content/types';
-import { loadDataFromStorage } from '@/lib/storage';
-import VodCard from './VodCard';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import thung from '@/assets/thung.png';
-import { isCurrentDateInRange } from '@/lib/utils';
-
-export function VodContent() {
- const [vodArray, setVodArray] = useState([]);
- useEffect(() => {
- loadDataFromStorage('vod', (data: string | null) => {
- if (!data) return;
-
- let parsedData: Vod[];
- if (typeof data === 'string') {
- try {
- parsedData = JSON.parse(data);
- } catch (error) {
- console.error('JSON 파싱 에러:', error);
- return;
- }
- } else {
- parsedData = data as Vod[];
- }
-
- const groupedData = parsedData.reduce(
- (acc, item) => {
- const key = `${item.courseId}-${item.subject}-${item.range}`;
- if (!acc[key]) {
- acc[key] = [];
- }
- acc[key].push(item);
- return acc;
- },
- {} as Record
- );
-
- const sortedVodGroups = Object.values(groupedData).sort((groupA, groupB) => {
- const firstA = groupA[0];
- const firstB = groupB[0];
-
- const isAX = firstA.weeklyAttendance.toUpperCase().startsWith('X');
- const isBX = firstB.weeklyAttendance.toUpperCase().startsWith('X');
-
- // X가 있는 항목을 먼저 정렬
- if (isAX && !isBX) return -1;
- if (!isAX && isBX) return 1;
-
- const rangeA = firstA.range;
- const rangeB = firstB.range;
- const isRangeANull = rangeA === null;
- const isRangeBNull = rangeB === null;
-
- // isCurrentDateInRange가 true인 항목을 먼저 정렬 (X와 O 모두)
- const isCurrentDateInRangeA = isCurrentDateInRange(firstA.range);
- const isCurrentDateInRangeB = isCurrentDateInRange(firstB.range);
-
- if (isAX) {
- // X일 때는 isCurrentDateInRange가 true인 항목을 먼저 배치, 그 다음 null
- if (isCurrentDateInRangeA && !isCurrentDateInRangeB) return -1;
- if (!isCurrentDateInRangeA && isCurrentDateInRangeB) return 1;
- if (isRangeANull && !isRangeBNull) return 1;
- if (!isRangeANull && isRangeBNull) return -1;
- } else {
- // O일 때는 isCurrentDateInRange가 true인 항목을 먼저 배치, 그 다음 null, 그 다음 시간순 정렬
- if (isCurrentDateInRangeA && !isCurrentDateInRangeB) return -1;
- if (!isCurrentDateInRangeA && isCurrentDateInRangeB) return 1;
- if (isRangeANull && !isRangeBNull) return 1;
- if (!isRangeANull && isRangeBNull) return -1;
-
- // rangeStart 날짜 기준으로 시간순으로 정렬
- if (!isRangeANull && !isRangeBNull) {
- const rangeStartA = rangeA.split(' ~ ')[0];
- const rangeStartB = rangeB.split(' ~ ')[0];
- const dateA = new Date(rangeStartA);
- const dateB = new Date(rangeStartB);
-
- if (dateA < dateB) return -1;
- if (dateA > dateB) return 1;
- }
- }
-
- // courseTitle로 기본 정렬
- if (firstA.courseTitle < firstB.courseTitle) return -1;
- if (firstA.courseTitle > firstB.courseTitle) return 1;
-
- return 0;
- });
-
- setVodArray(sortedVodGroups);
- });
- }, []);
-
- return (
-
-
- {vodArray.length === 0 ? (
-
-
-
- ) : (
-
-
-
- {vodArray.map((vodGroup, index) => {
- return ;
- })}
-
-
-
- )}
-
-
- );
-}
diff --git a/src/option/components/data.tsx b/src/option/components/data.tsx
deleted file mode 100644
index 58efcbe..0000000
--- a/src/option/components/data.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { TYPES } from '@/content/types';
-import { NotebookText, Video, Zap } from 'lucide-react';
-
-export const data = [
- {
- title: '강의',
- icon: ,
- type: TYPES.vod,
- },
- {
- title: '과제',
- icon: ,
- type: TYPES.assignment,
- },
- {
- title: '퀴즈',
- icon: ,
- type: TYPES.quiz,
- },
-];
diff --git a/src/option/index.tsx b/src/option/index.tsx
deleted file mode 100644
index 67d4741..0000000
--- a/src/option/index.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import { HashRouter as Router } from 'react-router-dom';
-import '@/styles/option.css';
-import App from './App';
-
-const rootElement = document.getElementById('root');
-if (rootElement) {
- createRoot(rootElement).render(
-
-
-
-
-
- );
-}
diff --git a/src/option/option.ts b/src/option/option.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pages/AssignmentPage.tsx b/src/pages/AssignmentPage.tsx
deleted file mode 100644
index 2c13369..0000000
--- a/src/pages/AssignmentPage.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { AssignContent } from '@/option/components/AssignContent';
-
-export default function AssignmentPage() {
- return ;
-}
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx
deleted file mode 100644
index 2418497..0000000
--- a/src/pages/DashboardPage.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Calendar } from '@/option/calendar';
-import SummaryCard from '@/option/SummaryCard';
-export default function DashboardPage() {
- return (
-
-
-
-
- );
-}
diff --git a/src/pages/QuizPage.tsx b/src/pages/QuizPage.tsx
deleted file mode 100644
index 8eeaa4f..0000000
--- a/src/pages/QuizPage.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { QuizContent } from '@/option/components/QuizContent';
-
-export default function QuizPage() {
- return ;
-}
diff --git a/src/pages/VodPage.tsx b/src/pages/VodPage.tsx
deleted file mode 100644
index 2bfab8c..0000000
--- a/src/pages/VodPage.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { VodContent } from '@/option/components/VodContent';
-
-export default function VodPage() {
- return ;
-}
diff --git a/src/player/components/PlayerIframe.tsx b/src/player/components/PlayerIframe.tsx
deleted file mode 100644
index 88463db..0000000
--- a/src/player/components/PlayerIframe.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-
-interface PlayerIframeProps {
- videoSrc: string;
- onNextVideo: () => void;
- isPlaying: boolean;
-}
-
-export default function PlayerIframe({ videoSrc, onNextVideo, isPlaying }: PlayerIframeProps) {
- const iframeRef = useRef(null);
- const isPlayingRef = useRef(isPlaying);
- const onNextVideoRef = useRef(onNextVideo);
-
- const [time, setTime] = useState({ current: 0, duration: 0 });
-
- // iframe 랜더링 시 이벤트 등록
- const setIframeRef = (node: HTMLIFrameElement | null) => {
- iframeRef.current = node;
- if (node) {
- node.addEventListener('load', loadEventListener);
- }
- };
-
- useEffect(() => {
- const interval = setInterval(() => {
- const video = getVideoElement();
- if (video) {
- setTime({ current: video.currentTime, duration: video.duration });
- }
- }, 500);
- return () => clearInterval(interval);
- }, []);
-
- useEffect(() => {
- isPlayingRef.current = isPlaying;
- onNextVideoRef.current = onNextVideo;
- }, [isPlaying, onNextVideo]);
-
- // iframe 내 video 요소 접근 헬퍼
- const getVideoElement = () => {
- const iframe = iframeRef.current;
- if (!iframe) return null;
- try {
- const doc = iframe.contentDocument || iframe.contentWindow?.document;
- return (doc?.querySelector('video') as HTMLVideoElement | null) ?? null;
- } catch (err) {
- console.error('iframe 접근 에러', err);
- return null;
- }
- };
-
- // 재생, 일시정지 처리 함수
- const controlPlayback = (video: HTMLVideoElement | null, play: boolean) => {
- if (!video) return;
- if (play) {
- video.muted = false;
- video.volume = 0.01;
- video.play().catch((e) => console.warn('재생 실패:', e));
- } else {
- video.pause();
- }
- };
-
- // load 이벤트 시 video listener 세팅
- const loadEventListener = () => {
- const video = getVideoElement();
- if (!video) return;
-
- // 중복 방지
- video.onended = null;
- video.onended = () => onNextVideoRef.current();
-
- controlPlayback(video, isPlayingRef.current);
- };
-
- // iframe load 이벤트 연결
- useEffect(() => {
- const iframe = iframeRef.current;
- if (!iframe) return;
- iframe.addEventListener('load', loadEventListener);
- return () => iframe.removeEventListener('load', loadEventListener);
- }, [videoSrc]);
-
- // isPlaying 변경 시 재생 상태 제어
- useEffect(() => {
- controlPlayback(getVideoElement(), isPlaying);
- }, [isPlaying]);
-
- useEffect(() => {
- const interval = setInterval(() => {
- const video = getVideoElement();
- if (video && !video.paused) {
- video.play().catch(() => {});
- }
- }, 60_000);
- return () => clearInterval(interval);
- }, []);
-
- return (
-
-
- {isPlaying ? (
- time.duration > 0 ? (
-
-
- {Math.floor(time.current / 60)}:
- {Math.floor(time.current % 60)
- .toString()
- .padStart(2, '0')}{' '}
-
- /
-
- {Math.floor(time.duration / 60)}:
- {Math.floor(time.duration % 60)
- .toString()
- .padStart(2, '0')}
-
-
- ) : (
-
영상 정보가 없습니다
- )
- ) : (
-
- 자동수강을 시작하려면 수강시작 버튼을 눌러주세요
-
- )}
-
-
- {videoSrc && isPlaying && (
-
- )}
-
- );
-}
diff --git a/src/player/components/SortableItem.tsx b/src/player/components/SortableItem.tsx
deleted file mode 100644
index c58a4d2..0000000
--- a/src/player/components/SortableItem.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { Vod } from '@/content/types';
-import { useSortable } from '@dnd-kit/sortable';
-import { CSS } from '@dnd-kit/utilities';
-import { Dispatch, SetStateAction } from 'react';
-
-interface SortableItemProps {
- vod: Vod;
- idx: number;
- currentVideoIndex: number;
- setCurrentVideoIndex: Dispatch>;
-}
-export default function SortableItem({ vod, idx, currentVideoIndex, setCurrentVideoIndex }: SortableItemProps) {
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
- id: `${vod.week}-${vod.title}`,
- });
-
- const style = {
- transform: CSS.Transform.toString(transform),
- transition,
- opacity: isDragging ? 0.5 : 1,
- cursor: 'grab',
- };
-
- return (
- setCurrentVideoIndex(idx)}
- className={`p-3 rounded-lg transition-all flex-shrink-0 ${
- idx === currentVideoIndex
- ? 'bg-white border border-blue-400'
- : 'bg-zinc-50 border border-transparent hover:border-zinc-200'
- }`}
- >
-
-
-
{vod.title}
-
- {vod.courseTitle} - {vod.prof}
-
-
-
-
- );
-}
diff --git a/src/popover/dashboard/Dashboard.tsx b/src/popover/dashboard/Dashboard.tsx
new file mode 100644
index 0000000..cd1164f
--- /dev/null
+++ b/src/popover/dashboard/Dashboard.tsx
@@ -0,0 +1,463 @@
+import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import icon from '@/assets/icon.png';
+import exit from '@/assets/exit.png';
+import { TAB_TYPE } from '@/types';
+import { OctagonAlert } from 'lucide-react';
+import { Spinner } from '@/components/ui/spinner';
+import { useGetCourses } from '@/hooks/useGetCourses';
+import VodList from './components/VodList';
+import AssignList from './components/AssignList';
+import QuizList from './components/QuizList';
+import { useCourseData } from '@/hooks/useCourseData';
+import { useDashboardFilters } from '@/hooks/useDashboardFilters';
+import TabNavigation from './components/TabNavigation';
+import StickyPopoverTrigger from './components/StickyPopoverTrigger';
+import DashboardHeader from './components/DashboardHeader';
+import InfoBubble from './components/InfoBubble';
+import NotificationBubble, { type Notification } from './components/NotificationBubble';
+import { useTranslation } from 'react-i18next';
+import { isAttended } from '@/lib/utils';
+import { makeVodGroupKey } from '@/lib/generateKey';
+import Setting from './components/Setting';
+import { useHiddenTasks } from '@/hooks/useHiddenTasks';
+import {
+ getOAuthToken,
+ removeCachedAuthToken,
+ getCalendarEvents,
+ addCalendarEventsBatch,
+ convertCalendarEventsToGoogleEvents,
+} from '@/lib/calendarUtils';
+import { vodGroupsToEvents, dueDateItemToEvent } from '@/lib/transformCalendarEvents';
+
+const BUBBLE_DISMISS_KEY = 'dotbugi_bubble_dismissed';
+const BUBBLE_DISMISS_DURATION = import.meta.env.VITE_MOCK ? 1000 * 30 : 1000 * 60 * 60; // mock: 30초, prod: 1시간
+
+function isBubbleDismissed(): boolean {
+ const raw = localStorage.getItem(BUBBLE_DISMISS_KEY);
+ if (!raw) return false;
+ return Date.now() - parseInt(raw, 10) < BUBBLE_DISMISS_DURATION;
+}
+
+let notifIdCounter = 0;
+
+export default function Dashboard() {
+ const { t } = useTranslation('common');
+ const { allCourses, trackedCourses, trackedCourseIds } = useGetCourses();
+
+ const {
+ vods,
+ assigns,
+ quizzes,
+ isPending,
+ remainingTime,
+ isError,
+ refreshCourseData,
+ addCourseData,
+ removeCourseData,
+ } = useCourseData(trackedCourses);
+
+ const { hiddenUrls, hideTask, hideTasks, unhideTask, isHidden } = useHiddenTasks();
+
+ // 숨긴 태스크 필터링
+ const visibleVods = useMemo(() => vods.filter((v) => !isHidden(v.url)), [vods, isHidden]);
+ const visibleAssigns = useMemo(() => assigns.filter((a) => !isHidden(a.url)), [assigns, isHidden]);
+ const visibleQuizzes = useMemo(() => quizzes.filter((q) => !isHidden(q.url)), [quizzes, isHidden]);
+
+ // 설정에서 숨긴 태스크 목록 표시용
+ const hiddenTaskInfos = useMemo(() => {
+ const all = [
+ ...vods.map((v) => ({ url: v.url, title: v.title, courseTitle: v.courseTitle })),
+ ...assigns.map((a) => ({ url: a.url, title: a.title, courseTitle: a.courseTitle })),
+ ...quizzes.map((q) => ({ url: q.url, title: q.title, courseTitle: q.courseTitle })),
+ ];
+ return all.filter((item) => hiddenUrls.has(item.url));
+ }, [vods, assigns, quizzes, hiddenUrls]);
+
+ const [activeTab, setActiveTab] = useState(TAB_TYPE.VIDEO);
+ const [isOpen, setIsOpen] = useState(false);
+ const [bubbleDismissed, setBubbleDismissed] = useState(isBubbleDismissed);
+ const [bubbleHiddenByOpen, setBubbleHiddenByOpen] = useState(false);
+ const [calendarToken, setCalendarToken] = useState(null);
+ const [notifications, setNotifications] = useState([]);
+ const [notifHiddenByOpen, setNotifHiddenByOpen] = useState(false);
+ const bubbleTimerRef = useRef | null>(null);
+ const notifTimerRef = useRef | null>(null);
+ const tokenExpiredNotifRef = useRef(null);
+
+ const isCalendarConnected = calendarToken !== null;
+
+ // DOM 토글에서 trackedCourseIds 변경 시 데이터 추가/삭제
+ const prevTrackedIdsRef = useRef>(new Set(trackedCourseIds));
+ useEffect(() => {
+ const prevSet = prevTrackedIdsRef.current;
+ const currSet = new Set(trackedCourseIds);
+ prevTrackedIdsRef.current = currSet;
+
+ if (prevSet.size === 0) return;
+
+ const allCourseIds = new Set(allCourses.map((c) => c.courseId));
+
+ for (const id of trackedCourseIds) {
+ if (!prevSet.has(id) && allCourseIds.has(id)) {
+ const course = allCourses.find((c) => c.courseId === id);
+ if (course) addCourseData(course);
+ }
+ }
+
+ for (const id of prevSet) {
+ if (!currSet.has(id)) {
+ removeCourseData(id);
+ }
+ }
+ }, [trackedCourseIds, allCourses, addCourseData, removeCourseData]);
+
+ const pushNotification = useCallback((n: Omit) => {
+ const id = `notif-${++notifIdCounter}`;
+ setNotifications((prev) => [...prev, { ...n, id }]);
+ return id;
+ }, []);
+
+ const dismissNotification = useCallback((id: string) => {
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
+ }, []);
+
+ const updateNotification = useCallback((id: string, updates: Partial>) => {
+ setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, ...updates } : n)));
+ }, []);
+
+ // 새로고침 시작/완료 시 NotificationBubble 표시
+ const refreshNotifRef = useRef(null);
+ const prevIsPendingRef = useRef(false);
+ useEffect(() => {
+ if (isPending && !prevIsPendingRef.current) {
+ if (refreshNotifRef.current) dismissNotification(refreshNotifRef.current);
+ refreshNotifRef.current = pushNotification({ type: 'loading', messageKey: 'refreshing' });
+ } else if (!isPending && prevIsPendingRef.current && refreshNotifRef.current) {
+ updateNotification(refreshNotifRef.current, {
+ type: 'success',
+ messageKey: 'refreshSuccess',
+ autoDismiss: true,
+ });
+ refreshNotifRef.current = null;
+ }
+ prevIsPendingRef.current = isPending;
+ }, [isPending, pushNotification, dismissNotification, updateNotification]);
+
+ const showTokenExpiredNotif = useCallback(() => {
+ if (tokenExpiredNotifRef.current) return;
+ const id = pushNotification({
+ type: 'warning',
+ messageKey: 'calendar.tokenExpired',
+ action: {
+ labelKey: 'calendar.tokenExpiredAction',
+ onClick: async () => {
+ const token = await getOAuthToken(true);
+ if (token) setCalendarToken(token);
+ },
+ },
+ });
+ tokenExpiredNotifRef.current = id;
+ }, [pushNotification]);
+
+ const clearTokenExpiredNotif = useCallback(() => {
+ if (tokenExpiredNotifRef.current) {
+ dismissNotification(tokenExpiredNotifRef.current);
+ tokenExpiredNotifRef.current = null;
+ }
+ }, [dismissNotification]);
+
+ const handleTokenExpired = useCallback(async () => {
+ if (calendarToken) {
+ await removeCachedAuthToken(calendarToken);
+ }
+ setCalendarToken(null);
+ showTokenExpiredNotif();
+ }, [calendarToken, showTokenExpiredNotif]);
+
+ // 로그인 성공 시 만료 알림 제거
+ useEffect(() => {
+ if (calendarToken) {
+ clearTokenExpiredNotif();
+ }
+ }, [calendarToken, clearTokenExpiredNotif]);
+
+ useEffect(() => {
+ if (import.meta.env.VITE_MOCK) {
+ removeCachedAuthToken('').catch(() => {});
+ setCalendarToken(null);
+ showTokenExpiredNotif();
+ } else {
+ getOAuthToken(false).then((token) => {
+ if (token) setCalendarToken(token);
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const handleCalendarLogin = async () => {
+ const token = await getOAuthToken(true);
+ if (token) {
+ setCalendarToken(token);
+ }
+ };
+
+ const handleCalendarLogout = async () => {
+ if (calendarToken) {
+ try {
+ await removeCachedAuthToken(calendarToken);
+ } catch (e) {
+ console.warn('[Dotbugi] removeCachedAuthToken failed:', e);
+ }
+ }
+ setCalendarToken(null);
+ };
+
+ const handleCalendarSync = async () => {
+ const token = calendarToken ?? (await getOAuthToken(true));
+ if (!token) return;
+
+ const syncId = pushNotification({ type: 'loading', messageKey: 'calendar.syncing' });
+
+ const calendarEvents = [
+ ...vodGroupsToEvents(visibleVods),
+ ...visibleAssigns.map((a) => dueDateItemToEvent(a, 'assign')),
+ ...visibleQuizzes.map((q) => dueDateItemToEvent(q, 'quiz')),
+ ];
+
+ const { events: existingEvents, tokenExpired: fetchExpired } = await getCalendarEvents(token);
+ if (fetchExpired) {
+ dismissNotification(syncId);
+ await handleTokenExpired();
+ return;
+ }
+
+ const newEventsData = convertCalendarEventsToGoogleEvents(calendarEvents);
+
+ const eventKey = (event: {
+ summary?: string;
+ start: { dateTime?: string; date?: string };
+ end: { dateTime?: string; date?: string };
+ }) =>
+ `${(event.summary || '').trim().toLowerCase()}|${new Date(event.start.dateTime || event.start.date || '').getTime()}|${new Date(event.end.dateTime || event.end.date || '').getTime()}`;
+
+ const existingKeys = new Set(existingEvents.map(eventKey));
+ const uniqueNewEvents = newEventsData.filter((e) => !existingKeys.has(eventKey(e)));
+
+ if (uniqueNewEvents.length === 0) {
+ updateNotification(syncId, { type: 'success', messageKey: 'calendar.syncNoNew', autoDismiss: true });
+ return;
+ }
+
+ const result = await addCalendarEventsBatch(uniqueNewEvents, token);
+
+ if (result.tokenExpired) {
+ dismissNotification(syncId);
+ await handleTokenExpired();
+ return;
+ }
+
+ if (result.failed === 0) {
+ updateNotification(syncId, {
+ type: 'success',
+ messageKey: 'calendar.syncSuccess',
+ messageParams: { count: result.added },
+ autoDismiss: true,
+ });
+ } else if (result.added === 0) {
+ updateNotification(syncId, {
+ type: 'error',
+ messageKey: 'calendar.syncFailed',
+ messageParams: { failed: result.failed },
+ autoDismiss: true,
+ });
+ } else {
+ updateNotification(syncId, {
+ type: 'warning',
+ messageKey: 'calendar.syncPartial',
+ messageParams: { added: result.added, failed: result.failed },
+ autoDismiss: true,
+ });
+ }
+ };
+
+ const {
+ searchTerm,
+ setSearchTerm,
+ isFilterOpen,
+ setIsFilterOpen,
+ filters,
+ courseTitlesMap,
+ filteredVods,
+ filteredAssigns,
+ filteredQuizzes,
+ handleCourseTitleChange,
+ handleAttendanceFilterChange,
+ handleSubmitFilterChange,
+ clearFilters,
+ } = useDashboardFilters({ vods: visibleVods, assigns: visibleAssigns, quizzes: visibleQuizzes, activeTab });
+
+ const taskCount = useMemo(() => {
+ const unattendedGroups = new Set();
+ for (const v of visibleVods) {
+ if (!isAttended(v.weeklyAttendance)) {
+ unattendedGroups.add(makeVodGroupKey(v.courseId, v.subject, v.range));
+ }
+ }
+ const unsubmittedAssigns = visibleAssigns.filter((a) => !a.isSubmit).length;
+ const unsubmittedQuizzes = visibleQuizzes.filter((q) => !q.isSubmit).length;
+ return unattendedGroups.size + unsubmittedAssigns + unsubmittedQuizzes;
+ }, [visibleVods, visibleAssigns, visibleQuizzes]);
+
+ const hasIncomplete = useMemo(
+ () => ({
+ [TAB_TYPE.VIDEO]: visibleVods.some((v) => !isAttended(v.weeklyAttendance)),
+ [TAB_TYPE.ASSIGN]: visibleAssigns.some((a) => !a.isSubmit),
+ [TAB_TYPE.QUIZ]: visibleQuizzes.some((q) => !q.isSubmit),
+ }),
+ [visibleVods, visibleAssigns, visibleQuizzes]
+ );
+
+ const handleToggleOpen = (e: React.MouseEvent) => {
+ setIsOpen((prev) => {
+ if (!prev) {
+ setBubbleHiddenByOpen(true);
+ setNotifHiddenByOpen(true);
+ if (bubbleTimerRef.current) {
+ clearTimeout(bubbleTimerRef.current);
+ bubbleTimerRef.current = null;
+ }
+ if (notifTimerRef.current) {
+ clearTimeout(notifTimerRef.current);
+ notifTimerRef.current = null;
+ }
+ } else {
+ bubbleTimerRef.current = setTimeout(() => {
+ setBubbleHiddenByOpen(false);
+ bubbleTimerRef.current = null;
+ }, 500);
+ notifTimerRef.current = setTimeout(() => {
+ setNotifHiddenByOpen(false);
+ notifTimerRef.current = null;
+ }, 500);
+ }
+ return !prev;
+ });
+ e.preventDefault();
+ };
+
+ useEffect(() => {
+ return () => {
+ if (bubbleTimerRef.current) clearTimeout(bubbleTimerRef.current);
+ if (notifTimerRef.current) clearTimeout(notifTimerRef.current);
+ };
+ }, []);
+
+ const handleRefresh = () => {
+ if (isPending) return;
+ refreshCourseData();
+ };
+
+ const handleDismissBubble = () => {
+ setBubbleDismissed(true);
+ localStorage.setItem(BUBBLE_DISMISS_KEY, Date.now().toString());
+ };
+
+ const showBubble = !isOpen && !bubbleDismissed && !bubbleHiddenByOpen && !isPending;
+
+ const filterHandlers = {
+ searchTerm,
+ onSearchChange: setSearchTerm,
+ filters,
+ isFilterOpen,
+ onFilterToggle: () => setIsFilterOpen((prev: boolean) => !prev),
+ courseTitlesMap,
+ onCourseTitleChange: handleCourseTitleChange,
+ onAttendanceFilterChange: handleAttendanceFilterChange,
+ onSubmitFilterChange: handleSubmitFilterChange,
+ onClearFilters: clearFilters,
+ };
+
+ const headerActions = {
+ remainingTime,
+ isPending,
+ onRefresh: handleRefresh,
+ onOpenSetting: () => setActiveTab(TAB_TYPE.SETTING),
+ isCalendarConnected,
+ onCalendarSync: handleCalendarSync,
+ };
+
+ return (
+ <>
+
+
+ {!isOpen && (
+
+ {!notifHiddenByOpen && notifications.length > 0 && (
+
+ )}
+ {showBubble && (
+
+ )}
+
+ )}
+
+
+
+
+
+ {isOpen && notifications.length > 0 && (
+
+
+
+ )}
+
+
+ {isPending ? (
+
+
+
+ ) : isError ? (
+
+
+
{t('error.occurred')}
+
location.reload()}
+ className="py-4 text-xl font-medium underline text-zinc-500 hover:text-zinc-950 hover:cursor-pointer transition-all duration-200"
+ >
+ {t('error.refreshPage')}
+
+
+ ) : (
+ <>
+ {activeTab === 'VIDEO' &&
}
+ {activeTab === 'ASSIGN' &&
}
+ {activeTab === 'QUIZ' &&
}
+ {activeTab === 'SETTING' && (
+
+ )}
+ >
+ )}
+
+
+
+
+ >
+ );
+}
diff --git a/src/popover/dashboard/components/AssignList.tsx b/src/popover/dashboard/components/AssignList.tsx
new file mode 100644
index 0000000..dddea64
--- /dev/null
+++ b/src/popover/dashboard/components/AssignList.tsx
@@ -0,0 +1,27 @@
+import { Assign } from '@/types';
+import DueDateList from './DueDateList';
+import { useTranslation } from 'react-i18next';
+
+interface AssignmentProps {
+ courseData: Assign[];
+ onHideTask?: (url: string) => void;
+}
+
+export default function AssignList({ courseData, onHideTask }: AssignmentProps) {
+ const { t } = useTranslation('common');
+ return (
+ (item.isSubmit ? 'border-green-500' : timeDiff.borderColor)}
+ getStatusIcon={(item, timeDiff) => {
+ if (item.isSubmit) return 'check';
+ if (timeDiff.textColor.includes('red')) return 'siren';
+ return 'warning';
+ }}
+ getStatusColor={(item, timeDiff) => (item.isSubmit ? 'text-green-500' : timeDiff.textColor)}
+ getStatusLabel={(item) => (item.isSubmit ? t('submit.doneSpaced') : t('submit.neededSpaced'))}
+ onHideTask={onHideTask}
+ />
+ );
+}
diff --git a/src/popover/dashboard/components/CardFooterContent.tsx b/src/popover/dashboard/components/CardFooterContent.tsx
new file mode 100644
index 0000000..a90b072
--- /dev/null
+++ b/src/popover/dashboard/components/CardFooterContent.tsx
@@ -0,0 +1,59 @@
+import { BadgeCheck, Clock, Siren, TriangleAlert } from 'lucide-react';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import { TimeDifferenceResult } from '@/types';
+
+const TOOLTIP_STYLE: React.CSSProperties = {
+ backgroundColor: 'rgba(24, 24, 27, 0.6)',
+ borderRadius: '4px',
+ fontSize: '11px',
+ paddingTop: '1px',
+ paddingBottom: '1px',
+ paddingLeft: '4px',
+ paddingRight: '4px',
+ zIndex: '9999',
+};
+
+interface CardFooterContentProps {
+ timeDifference: TimeDifferenceResult;
+ tooltipText: string;
+ statusColor: string;
+ statusIcon: 'check' | 'siren' | 'warning';
+ statusLabel: string;
+}
+
+function StatusIcon({ icon }: { icon: CardFooterContentProps['statusIcon'] }) {
+ switch (icon) {
+ case 'check':
+ return ;
+ case 'siren':
+ return ;
+ case 'warning':
+ return ;
+ }
+}
+
+export default function CardFooterContent({
+ timeDifference,
+ tooltipText,
+ statusColor,
+ statusIcon,
+ statusLabel,
+}: CardFooterContentProps) {
+ return (
+ <>
+
+
+
+
+ {timeDifference.message}
+
+
+ {tooltipText}
+
+
+ >
+ );
+}
diff --git a/src/popover/dashboard/components/DashboardHeader.tsx b/src/popover/dashboard/components/DashboardHeader.tsx
new file mode 100644
index 0000000..399020f
--- /dev/null
+++ b/src/popover/dashboard/components/DashboardHeader.tsx
@@ -0,0 +1,196 @@
+import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
+import { useMemo } from 'react';
+import { Filters, TAB_TYPE } from '@/types';
+import { EllipsisVertical, ListFilter, RefreshCw, Search, Settings } from 'lucide-react';
+import GoogleCalendar from '@/assets/calendar.png';
+import filter from '@/assets/filter.svg';
+import { Button } from '@/components/ui/button';
+import FilterBadge from './FilterBadge';
+import FilterPanel from './FilterPanel';
+import { useTranslation } from 'react-i18next';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+
+const attendanceValues = ['attended', 'absent'] as const;
+const submitOptions = [
+ { value: true, internalLabel: 'done' as const },
+ { value: false, internalLabel: 'needed' as const },
+];
+
+export interface FilterHandlers {
+ searchTerm: string;
+ onSearchChange: (value: string) => void;
+ filters: Record;
+ isFilterOpen: boolean;
+ onFilterToggle: () => void;
+ courseTitlesMap: Record;
+ onCourseTitleChange: (title: string) => void;
+ onAttendanceFilterChange: (status: string) => void;
+ onSubmitFilterChange: (isSubmit: boolean) => void;
+ onClearFilters: () => void;
+}
+
+export interface HeaderActions {
+ remainingTime: number;
+ isPending: boolean;
+ onRefresh: () => void;
+ onOpenSetting: () => void;
+ isCalendarConnected: boolean;
+ onCalendarSync: () => void;
+}
+
+interface DashboardHeaderProps {
+ activeTab: TAB_TYPE;
+ filter: FilterHandlers;
+ actions: HeaderActions;
+}
+
+export default function DashboardHeader({ activeTab, filter: f, actions: a }: DashboardHeaderProps) {
+ const { t } = useTranslation(['popover', 'common']);
+
+ const TAB_TITLES: Record = {
+ VIDEO: t('header.vodList'),
+ ASSIGN: t('header.assignList'),
+ QUIZ: t('header.quizList'),
+ SETTING: t('header.setting'),
+ };
+
+ const attendanceOptions = attendanceValues.map((v) => t(`common:attendance.${v}`));
+ const submitOptionsTranslated = submitOptions.map((o) => ({
+ label: t(`common:submit.${o.internalLabel}`),
+ value: o.value,
+ }));
+
+ const isFilterSet = useMemo(() => {
+ const currentFilters = f.filters[activeTab];
+ const { courseTitles, attendanceStatuses, submitStatuses } = currentFilters;
+ return (
+ (courseTitles && courseTitles.length > 0) ||
+ (attendanceStatuses && attendanceStatuses.length > 0) ||
+ (submitStatuses && submitStatuses.length > 0)
+ );
+ }, [f.filters, activeTab]);
+
+ const refreshDisabled = a.isPending;
+
+ return (
+
+
+
{TAB_TITLES[activeTab]}
+
+
+
+
+ {a.remainingTime >= (import.meta.env.VITE_MOCK ? 1 : 5) && (
+
+ )}
+
+
+
+
+
+ {t('common:refresh')}
+ = (import.meta.env.VITE_MOCK ? 1 : 5) ? 'bg-red-500' : 'bg-green-500'}`}
+ />
+
+
+
+ {t('common:setting')}
+
+ {a.isCalendarConnected && (
+ <>
+
+
+
+ {t('common:calendar.syncShort')}
+
+ >
+ )}
+
+
+
+ {activeTab !== 'SETTING' && (
+ <>
+
+
+ f.onSearchChange(e.target.value)}
+ autoFocus={true}
+ className="bg-zinc-50 rounded-xl border border-zinc-300 w-full text-lg h-12 pl-12 pr-4 placeholder-gray-400 font-medium py-0 outline-none focus:ring-0 focus:border-zinc-300 focus:bg-slate-50 transition-all duration-200"
+ />
+
+
+
+ {f.filters[activeTab].courseTitles.map((title) => (
+ f.onCourseTitleChange(title)} />
+ ))}
+ {activeTab === 'VIDEO' &&
+ f.filters[activeTab].attendanceStatuses?.map((status) => (
+ f.onAttendanceFilterChange(status)}
+ />
+ ))}
+ {(activeTab === 'ASSIGN' || activeTab === 'QUIZ') &&
+ f.filters[activeTab].submitStatuses?.map((status) => (
+ f.onSubmitFilterChange(status)}
+ />
+ ))}
+
+
+
+
+
+ {isFilterSet ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {t('common:clearAll')}
+
+
+ {t('common:close')}
+
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/src/popover/dashboard/components/DueDateList.tsx b/src/popover/dashboard/components/DueDateList.tsx
new file mode 100644
index 0000000..77a177e
--- /dev/null
+++ b/src/popover/dashboard/components/DueDateList.tsx
@@ -0,0 +1,85 @@
+import { calculateDueDate, calculateRemainingTime } from '@/lib/utils';
+import { Card, CardFooter, CardHeader } from '@/components/ui/card';
+import { TimeDifferenceResult } from '@/types';
+import EmptyState from './EmptyState';
+import CardFooterContent from './CardFooterContent';
+import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu';
+import { EyeOff } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
+interface DueDateItem {
+ courseTitle: string;
+ title: string;
+ url: string;
+ dueDate: string | null;
+}
+
+interface DueDateListProps {
+ courseData: T[];
+ emptyLabel: 'vod' | 'assign' | 'quiz';
+ getBorderClass: (item: T, timeDiff: TimeDifferenceResult) => string;
+ getStatusIcon: (item: T, timeDiff: TimeDifferenceResult) => 'check' | 'siren' | 'warning';
+ getStatusColor: (item: T, timeDiff: TimeDifferenceResult) => string;
+ getStatusLabel: (item: T) => string;
+ onHideTask?: (url: string) => void;
+}
+
+export default function DueDateList({
+ courseData,
+ emptyLabel,
+ getBorderClass,
+ getStatusIcon,
+ getStatusColor,
+ getStatusLabel,
+ onHideTask,
+}: DueDateListProps) {
+ const { t } = useTranslation('common');
+
+ if (!courseData || courseData.length === 0) {
+ return ;
+ }
+
+ return (
+
+ {courseData.map((course, index) => {
+ const timeDifference = calculateDueDate(course.dueDate!);
+
+ return (
+
+
+ window.open(course.url, '_blank')}
+ className={`cursor-pointer w-full rounded-2xl shadow-md bg-white overflow-hidden border-0 border-l-4 ${getBorderClass(course, timeDifference)} hover:bg-zinc-100 transition-all duration-200`}
+ >
+
+
+
{course.courseTitle}
+
{course.title}
+
+
+
+
+
+
+
+
+ onHideTask?.(course.url)}
+ className="text-lg gap-2 cursor-pointer"
+ >
+
+ {t('hide.task')}
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/src/popover/dashboard/components/EmptyState.tsx b/src/popover/dashboard/components/EmptyState.tsx
new file mode 100644
index 0000000..0bd0d0d
--- /dev/null
+++ b/src/popover/dashboard/components/EmptyState.tsx
@@ -0,0 +1,18 @@
+import thung from '@/assets/thung.png';
+import { useTranslation } from 'react-i18next';
+
+interface EmptyStateProps {
+ label: 'vod' | 'assign' | 'quiz';
+}
+
+export default function EmptyState({ label }: EmptyStateProps) {
+ const { t } = useTranslation('common');
+ return (
+
+
+
+ {t(`empty.${label}`)}
+
+
+ );
+}
diff --git a/src/content/components/FilterBadge.tsx b/src/popover/dashboard/components/FilterBadge.tsx
similarity index 100%
rename from src/content/components/FilterBadge.tsx
rename to src/popover/dashboard/components/FilterBadge.tsx
diff --git a/src/content/components/FilterItem.tsx b/src/popover/dashboard/components/FilterItem.tsx
similarity index 100%
rename from src/content/components/FilterItem.tsx
rename to src/popover/dashboard/components/FilterItem.tsx
diff --git a/src/content/components/FilterPanel.tsx b/src/popover/dashboard/components/FilterPanel.tsx
similarity index 95%
rename from src/content/components/FilterPanel.tsx
rename to src/popover/dashboard/components/FilterPanel.tsx
index b0bea48..a8cdad5 100644
--- a/src/content/components/FilterPanel.tsx
+++ b/src/popover/dashboard/components/FilterPanel.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import FilterItem from './FilterItem';
-import { TAB_TYPE, Filters } from '../types';
+import { TAB_TYPE, Filters } from '@/types';
interface FilterPanelProps {
filters: Record;
@@ -57,7 +57,7 @@ const FilterPanel: React.FC = ({
)}
- {activeTab === 'ASSIGN' && (
+ {(activeTab === 'ASSIGN' || activeTab === 'QUIZ') && (
{submitOptions.map((option) => (
diff --git a/src/popover/dashboard/components/InfoBubble.tsx b/src/popover/dashboard/components/InfoBubble.tsx
new file mode 100644
index 0000000..186f32a
--- /dev/null
+++ b/src/popover/dashboard/components/InfoBubble.tsx
@@ -0,0 +1,52 @@
+import { CheckCircle, Clock, X } from 'lucide-react';
+import { motion } from 'framer-motion';
+import { useTranslation } from 'react-i18next';
+
+interface InfoBubbleProps {
+ taskCount: number;
+ remainingTime: number;
+ onDismiss: () => void;
+}
+
+export default function InfoBubble({ taskCount, remainingTime, onDismiss }: InfoBubbleProps) {
+ const { t } = useTranslation('common');
+ const allDone = taskCount === 0;
+
+ const isStale = remainingTime >= 30;
+ const timeText =
+ remainingTime < 60
+ ? t('date.minutesAgoShort', { minutes: Math.round(remainingTime) })
+ : t('date.hoursAgoShort', { hours: Math.floor(remainingTime / 60) });
+
+ return (
+
+
+
+
+ {allDone && }
+ {allDone ? t('bubble.allDone') : t('bubble.tasks', { count: taskCount })}
+
+
+
+ {t('bubble.updatedAgo', { time: timeText })}
+
+
+
{
+ e.stopPropagation();
+ onDismiss();
+ }}
+ className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full bg-zinc-300 hover:bg-zinc-500 transition-colors"
+ >
+
+
+
+
+ );
+}
diff --git a/src/popover/dashboard/components/NotificationBubble.tsx b/src/popover/dashboard/components/NotificationBubble.tsx
new file mode 100644
index 0000000..7fd0557
--- /dev/null
+++ b/src/popover/dashboard/components/NotificationBubble.tsx
@@ -0,0 +1,115 @@
+import { useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import { CheckCircle, XCircle, AlertTriangle, Loader2, X } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
+export type NotificationType = 'success' | 'error' | 'warning' | 'loading';
+
+export interface Notification {
+ id: string;
+ type: NotificationType;
+ messageKey: string;
+ messageParams?: Record
;
+ autoDismiss?: boolean;
+ action?: {
+ labelKey: string;
+ onClick: () => void;
+ };
+}
+
+interface NotificationBubbleProps {
+ notifications: Notification[];
+ onDismiss: (id: string) => void;
+}
+
+const ICON_MAP = {
+ success: CheckCircle,
+ error: XCircle,
+ warning: AlertTriangle,
+ loading: Loader2,
+};
+
+const ICON_COLOR_MAP = {
+ success: 'text-green-500',
+ error: 'text-red-500',
+ warning: 'text-orange-500',
+ loading: 'text-zinc-400 animate-spin',
+};
+
+export default function NotificationBubble({ notifications, onDismiss }: NotificationBubbleProps) {
+ return (
+
+ {notifications.map((n) => (
+
+ ))}
+
+ );
+}
+
+function NotificationItem({
+ notification,
+ onDismiss,
+}: {
+ notification: Notification;
+ onDismiss: (id: string) => void;
+}) {
+ useEffect(() => {
+ if (!notification.autoDismiss) return;
+ const timer = setTimeout(() => onDismiss(notification.id), 4000);
+ return () => clearTimeout(timer);
+ }, [notification.id, notification.autoDismiss, onDismiss]);
+
+ const { t } = useTranslation('common');
+ const Icon = ICON_MAP[notification.type];
+ const message = t(notification.messageKey, notification.messageParams);
+
+ return (
+
+
+
+
+
+
+
+ {message}
+
+ {notification.action && (
+
+ {t(notification.action.labelKey)}
+
+ )}
+
+ {notification.type !== 'loading' && (
+
onDismiss(notification.id)}
+ className="flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full bg-zinc-300 hover:bg-zinc-500 transition-colors"
+ >
+
+
+ )}
+
+
+ );
+}
diff --git a/src/popover/dashboard/components/QuizList.tsx b/src/popover/dashboard/components/QuizList.tsx
new file mode 100644
index 0000000..f6d92bd
--- /dev/null
+++ b/src/popover/dashboard/components/QuizList.tsx
@@ -0,0 +1,27 @@
+import { Quiz } from '@/types';
+import DueDateList from './DueDateList';
+import { useTranslation } from 'react-i18next';
+
+interface QuizTabProps {
+ courseData: Quiz[];
+ onHideTask?: (url: string) => void;
+}
+
+export default function QuizList({ courseData, onHideTask }: QuizTabProps) {
+ const { t } = useTranslation('common');
+ return (
+ (item.isSubmit ? 'border-green-500' : timeDiff.borderColor)}
+ getStatusIcon={(item, timeDiff) => {
+ if (item.isSubmit) return 'check';
+ if (timeDiff.textColor.includes('red')) return 'siren';
+ return 'warning';
+ }}
+ getStatusColor={(item, timeDiff) => (item.isSubmit ? 'text-green-500' : timeDiff.textColor)}
+ getStatusLabel={(item) => (item.isSubmit ? t('submit.doneSpaced') : t('submit.neededSpaced'))}
+ onHideTask={onHideTask}
+ />
+ );
+}
diff --git a/src/popover/dashboard/components/Setting.tsx b/src/popover/dashboard/components/Setting.tsx
new file mode 100644
index 0000000..6be510f
--- /dev/null
+++ b/src/popover/dashboard/components/Setting.tsx
@@ -0,0 +1,206 @@
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { SUPPORTED_LANGUAGES, changeLanguage, type LanguageCode } from '@/i18n';
+import { Globe, Github, X } from 'lucide-react';
+import { motion } from 'framer-motion';
+import { Card } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+import GoogleCalendar from '@/assets/calendar.png';
+import GmailIcon from '@/assets/gmail.png';
+
+const CONTACT_EMAIL = 'hsu.dotbugi@gmail.com';
+const KAKAO_LINK = 'https://open.kakao.com/o/sZBnxllh';
+const CALENDAR_LINK = 'https://calendar.google.com/calendar';
+
+interface HiddenTaskInfo {
+ url: string;
+ title: string;
+ courseTitle: string;
+}
+
+interface SettingProps {
+ isCalendarConnected: boolean;
+ onCalendarLogin: () => void;
+ onCalendarLogout: () => void;
+ hiddenTasks?: HiddenTaskInfo[];
+ onUnhideTask?: (url: string) => void;
+}
+
+export default function Setting({
+ isCalendarConnected,
+ onCalendarLogin,
+ onCalendarLogout,
+ hiddenTasks,
+ onUnhideTask,
+}: SettingProps) {
+ const { i18n, t } = useTranslation('common');
+
+ return (
+
+ {/* Google Calendar 배너 */}
+
window.open(CALENDAR_LINK, '_blank')}
+ >
+
+
+
{t('calendar.bannerTitle')}
+
+ {t('calendar.bannerDesc')}
+ {isCalendarConnected ? (
+ {
+ e.stopPropagation();
+ onCalendarLogout();
+ }}
+ >
+ {t('calendar.logout')}
+
+ ) : (
+ {
+ e.stopPropagation();
+ onCalendarLogin();
+ }}
+ >
+ {t('calendar.login')}
+
+ )}
+
+
+ {/* 언어 설정 */}
+
+ changeLanguage(value as LanguageCode)}>
+
+
+ {t('language')}
+
+
+
+
+
+ {SUPPORTED_LANGUAGES.map((lang) => (
+
+ {lang.label}
+
+ ))}
+
+
+
+
+ {/* 문의하기 */}
+
+
+
+
+ {/* 숨긴 태스크 */}
+ {hiddenTasks && hiddenTasks.length > 0 && (
+
+ {t('hide.title')}
+
+ {hiddenTasks.map((task) => (
+
+
+
{task.courseTitle}
+
{task.title}
+
+
onUnhideTask?.(task.url)}
+ className="flex-shrink-0 w-7 h-7 flex items-center justify-center rounded-full bg-zinc-300 hover:bg-zinc-500 transition-colors"
+ title={t('hide.unhide')}
+ >
+
+
+
+ ))}
+
+
+ )}
+
+ {/* GitHub Star 배너 */}
+
+
+ );
+}
+
+function GitHubStarBanner() {
+ const { t } = useTranslation('common');
+ const [isHovered, setIsHovered] = useState(false);
+
+ return (
+
+ window.open('https://github.com/hs-shell/dotbugi', '_blank')}
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ className="flex flex-col items-center text-center w-full rounded-2xl px-4 py-4 cursor-pointer bg-white hover:bg-zinc-100 transition-colors duration-500 group"
+ >
+
+
{t('github.likeProject')}
+
{t('github.starOnGithub')}
+
+
+ );
+}
diff --git a/src/content/StickyPopoverTrigger.tsx b/src/popover/dashboard/components/StickyPopoverTrigger.tsx
similarity index 100%
rename from src/content/StickyPopoverTrigger.tsx
rename to src/popover/dashboard/components/StickyPopoverTrigger.tsx
diff --git a/src/popover/dashboard/components/TabNavigation.tsx b/src/popover/dashboard/components/TabNavigation.tsx
new file mode 100644
index 0000000..73fc419
--- /dev/null
+++ b/src/popover/dashboard/components/TabNavigation.tsx
@@ -0,0 +1,39 @@
+import { NotebookText, VideoIcon, Zap } from 'lucide-react';
+import { TAB_TYPE } from '@/types';
+import { useTranslation } from 'react-i18next';
+
+interface TabNavigationProps {
+ activeTab: string;
+ setActiveTab: (tabType: TAB_TYPE) => void;
+ hasIncomplete?: Partial>;
+}
+
+export default function TabNavigation({ activeTab, setActiveTab, hasIncomplete }: TabNavigationProps) {
+ const { t } = useTranslation('common');
+
+ const tabs = [
+ { type: TAB_TYPE.VIDEO, icon: , label: t('vod') },
+ { type: TAB_TYPE.ASSIGN, icon: , label: t('assign') },
+ { type: TAB_TYPE.QUIZ, icon: , label: t('quiz') },
+ ];
+
+ return (
+
+ {tabs.map(({ type, icon, label }) => (
+
setActiveTab(type)}
+ >
+
+ {icon}
+ {hasIncomplete?.[type] && (
+
+ )}
+
+
{label}
+
+ ))}
+
+ );
+}
diff --git a/src/popover/dashboard/components/VodList.tsx b/src/popover/dashboard/components/VodList.tsx
new file mode 100644
index 0000000..d9b807f
--- /dev/null
+++ b/src/popover/dashboard/components/VodList.tsx
@@ -0,0 +1,140 @@
+import { useState } from 'react';
+import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card';
+import { Vod } from '@/types';
+import { calculateDueDate, calculateRemainingTime, extractEndDate, formatDateString, isAbsent, isAttended } from '@/lib/utils';
+import { makeVodGroupKey } from '@/lib/generateKey';
+import { ChevronDown, ChevronUp, EyeOff } from 'lucide-react';
+import EmptyState from './EmptyState';
+import CardFooterContent from './CardFooterContent';
+import { useTranslation } from 'react-i18next';
+import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu';
+
+interface VideoProps {
+ courseData: Vod[];
+ onHideTask?: (url: string) => void;
+ onHideTasks?: (urls: string[]) => void;
+}
+
+export default function VodList({ courseData, onHideTask, onHideTasks }: VideoProps) {
+ const { t } = useTranslation('common');
+ const [expandedCards, setExpandedCards] = useState>({});
+
+ const toggleCard = (key: string) => {
+ setExpandedCards((prev) => ({ ...prev, [key]: !prev[key] }));
+ };
+
+ if (!courseData || courseData.length === 0) {
+ return ;
+ }
+
+ const groupedData = courseData.reduce>((acc, item) => {
+ const key = makeVodGroupKey(item.courseId, item.subject, item.range);
+ if (!acc[key]) acc[key] = [];
+ acc[key].push(item);
+ return acc;
+ }, {});
+
+ const getEndTime = (range: string | null) => {
+ const end = extractEndDate(range);
+ return end ? new Date(end).getTime() : Number.MAX_SAFE_INTEGER;
+ };
+
+ const compareByAbsentThenEndTime = (a: Vod, b: Vod, absentKey: keyof Vod) => {
+ const aAbsent = isAbsent(a[absentKey] as string);
+ const bAbsent = isAbsent(b[absentKey] as string);
+ if (aAbsent !== bAbsent) return aAbsent ? -1 : 1;
+ return getEndTime(a.range) - getEndTime(b.range);
+ };
+
+ const sortedVodGroups = Object.values(groupedData).sort((groupA, groupB) =>
+ compareByAbsentThenEndTime(groupA[0], groupB[0], 'weeklyAttendance'),
+ );
+
+ return (
+
+ {sortedVodGroups.map((vods) => {
+ if (!vods || vods.length === 0) return null;
+
+ const sortedVods = vods.slice().sort((a, b) => compareByAbsentThenEndTime(a, b, 'isAttendance'));
+
+ const item = vods[0];
+ const cardKey = makeVodGroupKey(item.courseId, item.subject, item.range);
+ const timeDifference = calculateDueDate(extractEndDate(item.range));
+ const isExpanded = expandedCards[cardKey] || false;
+ const attended = isAttended(item.weeklyAttendance);
+
+ return (
+
+
+
+ toggleCard(cardKey)}
+ >
+
+
{item.courseTitle}
+
{item.subject}
+
+ {isExpanded ? : }
+
+ {isExpanded && (
+
+ {sortedVods.map((vod, vodIndex) => (
+
+
+ window.open(vod.url.replace('view', 'viewer'), '_blank', 'VodContentWindow')}
+ >
+
+ {vod.title}
+
+
+ {formatDateString(vod.range)},{' '}
+
+ {vod.length}
+
+
+
+
+
+ onHideTask?.(vod.url)}
+ className="text-lg gap-2 cursor-pointer"
+ >
+
+ {t('hide.task')}
+
+
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ onHideTasks?.(vods.map((v) => v.url))}
+ className="text-lg gap-2 cursor-pointer"
+ >
+
+ {t('hide.task')}
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/src/content/index.tsx b/src/popover/index.tsx
similarity index 77%
rename from src/content/index.tsx
rename to src/popover/index.tsx
index e8b5228..dd5bddb 100644
--- a/src/content/index.tsx
+++ b/src/popover/index.tsx
@@ -1,11 +1,14 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
-import App from './App';
+import '@/i18n';
+import Dashboard from './dashboard/Dashboard';
import styles from '@/styles/shadow.css?inline';
-import { createShadowRoot } from '@/lib/createShadowRoot';
-import { ShadowRootContext } from '@/lib/ShadowRootContext';
+import { createShadowRoot } from '@/popover/lib/createShadowRoot';
+import { ShadowRootContext } from '@/popover/lib/ShadowRootContext';
import { TooltipProvider } from '@/components/ui/tooltip';
-import PlayerApp from '@/player/App';
+import PlayerApp from '@/popover/player/App';
+import { injectCourseToggles } from '@/lib/injectCourseToggles';
+import { injectCourseStatus } from '@/lib/courseStatus';
const HANSUNG_URL = 'https://learn.hansung.ac.kr/';
const footer = document.getElementById('page-footer');
@@ -14,6 +17,7 @@ const leftMenus = document.getElementsByClassName('left-menus');
const url = window.location.href;
if (footer && url === HANSUNG_URL) {
+ injectCourseToggles();
const backtop = document.getElementById('back-top') as HTMLDivElement;
if (backtop) backtop.remove();
@@ -44,13 +48,18 @@ if (footer && url === HANSUNG_URL) {
-
+
);
}
+// 강의 페이지에서 트래킹 상태 배지 주입
+if (url.includes('/course/view.php?id=') && url.startsWith(HANSUNG_URL)) {
+ injectCourseStatus();
+}
+
if (leftMenus.length === 2 && url.startsWith(HANSUNG_URL)) {
const leftMenu = leftMenus[0];
diff --git a/src/lib/ShadowRootContext.tsx b/src/popover/lib/ShadowRootContext.tsx
similarity index 100%
rename from src/lib/ShadowRootContext.tsx
rename to src/popover/lib/ShadowRootContext.tsx
diff --git a/src/lib/createShadowRoot.ts b/src/popover/lib/createShadowRoot.ts
similarity index 100%
rename from src/lib/createShadowRoot.ts
rename to src/popover/lib/createShadowRoot.ts
diff --git a/src/player/App.tsx b/src/popover/player/App.tsx
similarity index 100%
rename from src/player/App.tsx
rename to src/popover/player/App.tsx
diff --git a/src/popover/player/components/PlayerIframe.tsx b/src/popover/player/components/PlayerIframe.tsx
new file mode 100644
index 0000000..bb211d2
--- /dev/null
+++ b/src/popover/player/components/PlayerIframe.tsx
@@ -0,0 +1,192 @@
+import { useEffect, useRef, useState, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+
+const formatMMSS = (seconds: number) =>
+ `${Math.floor(seconds / 60)}:${Math.floor(seconds % 60).toString().padStart(2, '0')}`;
+
+interface PlayerIframeProps {
+ videoSrc: string;
+ onNextVideo: () => void;
+ isPlaying: boolean;
+}
+
+export default function PlayerIframe({ videoSrc, onNextVideo, isPlaying }: PlayerIframeProps) {
+ const { t } = useTranslation('player');
+ const iframeRef = useRef(null);
+ const isPlayingRef = useRef(isPlaying);
+ const onNextVideoRef = useRef(onNextVideo);
+ const abortRef = useRef(null);
+
+ const [time, setTime] = useState({ current: 0, duration: 0 });
+
+ useEffect(() => {
+ isPlayingRef.current = isPlaying;
+ onNextVideoRef.current = onNextVideo;
+ }, [isPlaying, onNextVideo]);
+
+ const getVideoElement = useCallback(() => {
+ const iframe = iframeRef.current;
+ if (!iframe) return null;
+ try {
+ const doc = iframe.contentDocument || iframe.contentWindow?.document;
+ return (doc?.querySelector('video') as HTMLVideoElement | null) ?? null;
+ } catch {
+ return null;
+ }
+ }, []);
+
+ const controlPlayback = useCallback((video: HTMLVideoElement | null, play: boolean) => {
+ if (!video) return;
+ if (play) {
+ video.muted = false;
+ video.volume = 0.01;
+ video.play().catch((e) => console.warn('Playback failed:', e));
+ } else {
+ video.pause();
+ }
+ }, []);
+
+ // iframe load 시: 메타 fetch → 스킵 or 이어보기 or 재생
+ const handleLoad = useCallback(async () => {
+ const iframe = iframeRef.current;
+ if (!iframe) return;
+
+ // 이전 fetch 취소
+ abortRef.current?.abort();
+ const controller = new AbortController();
+ abortRef.current = controller;
+
+ // 메타 fetch
+ try {
+ const res = await fetch(iframe.src, { credentials: 'include', signal: controller.signal });
+ const html = await res.text();
+
+ if (controller.signal.aborted) return;
+
+ const completeMatch = html.match(/var\s+is_complete\s*=\s*(\d+)/);
+ const progressMatch = html.match(/var\s+before_progress\s*=\s*(\d+)/);
+ const isComplete = completeMatch?.[1] === '1';
+ const beforeProgress = progressMatch ? parseInt(progressMatch[1], 10) : 0;
+
+ // is_complete면 스킵
+ if (isComplete && isPlayingRef.current) {
+ onNextVideoRef.current();
+ return;
+ }
+
+ const video = getVideoElement();
+ if (!video) return;
+
+ video.onended = null;
+ video.onended = () => onNextVideoRef.current();
+
+ // 이어보기
+ if (!isComplete && beforeProgress > 0) {
+ const trySeek = () => {
+ if (controller.signal.aborted) return;
+ if (video.readyState >= 1 && video.duration > 0) {
+ video.currentTime = Math.min(beforeProgress, video.duration - 1);
+ } else {
+ setTimeout(trySeek, 500);
+ }
+ };
+ trySeek();
+ }
+
+ controlPlayback(video, isPlayingRef.current);
+ } catch {
+ // fetch 실패 시 일반 재생
+ if (controller.signal.aborted) return;
+ const video = getVideoElement();
+ if (!video) return;
+ video.onended = null;
+ video.onended = () => onNextVideoRef.current();
+ controlPlayback(video, isPlayingRef.current);
+ }
+ }, [getVideoElement, controlPlayback]);
+
+ // callback ref: iframe mount 시 즉시 load 리스너 등록
+ const setIframeRef = useCallback(
+ (node: HTMLIFrameElement | null) => {
+ if (iframeRef.current) {
+ iframeRef.current.removeEventListener('load', handleLoad);
+ }
+ iframeRef.current = node;
+ if (node) {
+ node.addEventListener('load', handleLoad);
+ }
+ },
+ [handleLoad],
+ );
+
+ // 시간 표시
+ useEffect(() => {
+ const interval = setInterval(() => {
+ const video = getVideoElement();
+ if (video) {
+ setTime({ current: video.currentTime, duration: video.duration });
+ }
+ }, 500);
+ return () => clearInterval(interval);
+ }, [getVideoElement]);
+
+ useEffect(() => {
+ controlPlayback(getVideoElement(), isPlaying);
+ }, [isPlaying, controlPlayback, getVideoElement]);
+
+ // cleanup abort on unmount
+ useEffect(() => {
+ return () => abortRef.current?.abort();
+ }, []);
+
+ // 백그라운드 탭 fallback
+ useEffect(() => {
+ const interval = setInterval(() => {
+ const video = getVideoElement();
+ if (!video || !isPlayingRef.current) return;
+
+ if (video.paused) {
+ video.play().catch(() => {});
+ }
+
+ if (video.duration > 0 && video.currentTime >= video.duration - 0.5) {
+ onNextVideoRef.current();
+ }
+ }, 3_000);
+ return () => clearInterval(interval);
+ }, [getVideoElement]);
+
+ return (
+
+ {isPlaying ? (
+ time.duration > 0 ? (
+
+ {formatMMSS(time.current)}
+ /
+ {formatMMSS(time.duration)}
+
+ ) : (
+
+ {t('noVideoInfo')}
+
+ )
+ ) : (
+
+ {t('startPrompt')}
+
+ )}
+
+ {videoSrc && isPlaying && (
+
+ )}
+
+ );
+}
diff --git a/src/player/components/PlayerPopoverContent.tsx b/src/popover/player/components/PlayerPopoverContent.tsx
similarity index 70%
rename from src/player/components/PlayerPopoverContent.tsx
rename to src/popover/player/components/PlayerPopoverContent.tsx
index 45d5106..c5029f4 100644
--- a/src/player/components/PlayerPopoverContent.tsx
+++ b/src/popover/player/components/PlayerPopoverContent.tsx
@@ -1,6 +1,6 @@
import { PopoverContent } from '@radix-ui/react-popover';
import PlayerIframe from './PlayerIframe';
-import type { Vod } from '@/content/types';
+import type { Vod } from '@/types';
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from 'react';
import { loadDataFromStorage } from '@/lib/storage';
import { Button } from '@/components/ui/button';
@@ -8,7 +8,8 @@ import { Button } from '@/components/ui/button';
import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragEndEvent } from '@dnd-kit/core';
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import SortableItem from './SortableItem';
-import { isCurrentDateInRange } from '@/lib/utils';
+import { isAttended, isCurrentDateInRange } from '@/lib/utils';
+import { useTranslation } from 'react-i18next';
interface PlayerPopoverContentProps {
isPopoverOpen: boolean;
@@ -17,6 +18,7 @@ interface PlayerPopoverContentProps {
}
export default function PlayerPopoverContent({ isPopoverOpen, isPlaying, setIsPlaying }: PlayerPopoverContentProps) {
+ const { t } = useTranslation('player');
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
const [vods, setVods] = useState([]);
const [isDone, setIsDone] = useState(false);
@@ -54,27 +56,63 @@ export default function PlayerPopoverContent({ isPopoverOpen, isPlaying, setIsPl
setCurrentVideoIndex((prev) => (prev + 1) % vods.length);
}, [vods, currentVideoIndex, setIsPlaying]);
- useEffect(() => {
- loadDataFromStorage('vod', (data) => {
- if (!data) return;
- const filtered = (data as Vod[]).filter(
- (vod) => isCurrentDateInRange(vod.range) && vod.isAttendance.toLowerCase() !== 'o'
+ const skipPlayerFilter = !!import.meta.env.VITE_MOCK_COURSES;
+ const usePureMock = !!import.meta.env.VITE_MOCK && !import.meta.env.VITE_MOCK_COURSES;
+
+ const loadUnattendedVods = useCallback(async () => {
+ if (usePureMock) {
+ const { mockPlayerVodIds } = await import('@/mocks/mockData');
+ const BASE = 'https://learn.hansung.ac.kr/mod/vod/view.php?id=';
+ setVods(
+ mockPlayerVodIds.map((id, i) => ({
+ courseId: 'mock',
+ courseTitle: 'Player Test',
+ prof: '',
+ week: i + 1,
+ subject: `${i + 1}주차`,
+ title: `VOD-${id}`,
+ url: `${BASE}${id}`,
+ range: null,
+ length: '00:00',
+ isAttendance: 'X',
+ weeklyAttendance: 'X',
+ })),
);
- setVods(filtered);
+ return;
+ }
+ loadDataFromStorage('vod', (data) => {
+ if (!data) return;
+ loadDataFromStorage('hiddenTaskUrls', (hiddenUrls) => {
+ const hidden = new Set(hiddenUrls ?? []);
+ if (skipPlayerFilter) {
+ setVods(data.filter((vod) => !hidden.has(vod.url)));
+ } else {
+ setVods(data.filter((vod) => !hidden.has(vod.url) && isCurrentDateInRange(vod.range) && !isAttended(vod.isAttendance)));
+ }
+ });
});
- }, []);
+ }, [skipPlayerFilter, usePureMock]);
+
+ useEffect(() => {
+ loadUnattendedVods();
+ }, [loadUnattendedVods]);
+
+ // Dashboard에서 새로고침 시 storage 변경을 감지해서 player 목록 갱신
+ useEffect(() => {
+ const listener = (changes: { [key: string]: chrome.storage.StorageChange }) => {
+ if ((changes.vod || changes.hiddenTaskUrls) && !isPlaying) {
+ loadUnattendedVods();
+ }
+ };
+ chrome.storage.onChanged.addListener(listener);
+ return () => chrome.storage.onChanged.removeListener(listener);
+ }, [loadUnattendedVods, isPlaying]);
useEffect(() => {
if (isPopoverOpen && !isPlaying && vods.length === 0) {
- loadDataFromStorage('vod', (data) => {
- if (!data) return;
- const filtered = (data as Vod[]).filter(
- (vod) => isCurrentDateInRange(vod.range) && vod.isAttendance.toLowerCase() !== 'o'
- );
- setVods(filtered);
- });
+ loadUnattendedVods();
}
- }, [isPlaying, vods, isPopoverOpen]);
+ }, [isPlaying, vods, isPopoverOpen, loadUnattendedVods]);
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -97,12 +135,12 @@ export default function PlayerPopoverContent({ isPopoverOpen, isPlaying, setIsPl
const autoPlayText =
isHover && isPlaying
- ? '수강 종료'
+ ? t('stopWatching')
: isPlaying
- ? `${formatExpectedEndTime(totalDurationSeconds)} 완료 예정`
+ ? t('expectedEnd', { time: formatExpectedEndTime(totalDurationSeconds) })
: isDone
- ? '수강 완료'
- : '수강 시작';
+ ? t('doneWatching')
+ : t('startWatching');
return (
-
강의 목록
+
+
{t('lectureList')}
+
+ {currentVideoIndex + 1} / {vods.length}
+
+