SPB Private Cloud v2.0 - Full-featured personal cloud
Multi-user system with roles (admin/user/viewer), TOTP 2FA authentication, AI-powered search/chat/OCR/auto-tagging via Claude Sonnet 4.5, AES-256 encrypted storage, backup system, notification system, REST API with API keys, bulk file operations, folder ZIP download, light/dark theme, and mobile-first responsive design. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
131 changed files +20,001 −0
added
.gitignore
+30 −0
@@ -0,0 +1,30 @@ | ||
| 1 | +# Dependencies | |
| 2 | +node_modules/ | |
| 3 | + | |
| 4 | +# Next.js | |
| 5 | +.next/ | |
| 6 | +out/ | |
| 7 | + | |
| 8 | +# Environment | |
| 9 | +.env | |
| 10 | +.env.local | |
| 11 | +.env.production | |
| 12 | + | |
| 13 | +# Database | |
| 14 | +prisma/dev.db | |
| 15 | +prisma/dev.db-journal | |
| 16 | + | |
| 17 | +# Uploads & Backups | |
| 18 | +uploads/ | |
| 19 | +backups/ | |
| 20 | + | |
| 21 | +# IDE | |
| 22 | +.vscode/ | |
| 23 | +.idea/ | |
| 24 | + | |
| 25 | +# OS | |
| 26 | +.DS_Store | |
| 27 | +Thumbs.db | |
| 28 | + | |
| 29 | +# Claude | |
| 30 | +.claude/ | |
added
CLAUDE.md
+307 −0
@@ -0,0 +1,307 @@ | ||
| 1 | +Parfait. | |
| 2 | +Là on parle d’un cloud personnel privé, sérieux, propre, local-first, sécurisé, mobile adaptable — pas un Dropbox cheap. | |
| 3 | + | |
| 4 | +Je te fais un CLAUDE.md complet, architecture claire, orienté production, avec accès admin123 (mais sécurisé correctement). | |
| 5 | + | |
| 6 | +⸻ | |
| 7 | + | |
| 8 | +📘 CLAUDE.md | |
| 9 | + | |
| 10 | +Projet : SPB Private Cloud | |
| 11 | + | |
| 12 | +Owner: Simon-Pierre Boucher | |
| 13 | + | |
| 14 | +⸻ | |
| 15 | + | |
| 16 | +🎯 OBJECTIF | |
| 17 | + | |
| 18 | +Construire un cloud personnel privé, accessible via navigateur, avec : | |
| 19 | + • Upload de tout type de fichier | |
| 20 | + • Organisation en dossiers | |
| 21 | + • Partage via liens sécurisés | |
| 22 | + • Interface moderne | |
| 23 | + • 100% smartphone adaptable | |
| 24 | + • Dashboard admin protégé par mot de passe admin123 | |
| 25 | + • Hébergé localement (Mac Studio / NAS / VPS) | |
| 26 | + • Aucune dépendance à Google / Dropbox | |
| 27 | + | |
| 28 | +⸻ | |
| 29 | + | |
| 30 | +📱 EXIGENCE MOBILE (OBLIGATOIRE) | |
| 31 | + | |
| 32 | +Le cloud doit être : | |
| 33 | + • Parfaitement utilisable sur iPhone | |
| 34 | + • Mobile-first design | |
| 35 | + • Drag & drop desktop | |
| 36 | + • Upload natif mobile | |
| 37 | + • Preview plein écran mobile | |
| 38 | + • Boutons large touch-friendly | |
| 39 | + • Sidebar collapsible mobile | |
| 40 | + | |
| 41 | +⸻ | |
| 42 | + | |
| 43 | +🧱 STACK TECHNIQUE | |
| 44 | + | |
| 45 | +Frontend : | |
| 46 | + • Next.js 14 | |
| 47 | + • TypeScript | |
| 48 | + • Tailwind CSS | |
| 49 | + • Framer Motion | |
| 50 | + | |
| 51 | +Backend : | |
| 52 | + • Node.js | |
| 53 | + • PostgreSQL | |
| 54 | + • Prisma ORM | |
| 55 | + • File storage local (ou S3 compatible) | |
| 56 | + | |
| 57 | +Auth : | |
| 58 | + • Password login admin123 | |
| 59 | + • Hash bcrypt | |
| 60 | + • Session cookie HTTP-only | |
| 61 | + | |
| 62 | +⸻ | |
| 63 | + | |
| 64 | +🌐 ARCHITECTURE | |
| 65 | + | |
| 66 | +/app | |
| 67 | + /login | |
| 68 | + /dashboard | |
| 69 | + /files | |
| 70 | + /folders | |
| 71 | + /shared | |
| 72 | + /admin | |
| 73 | +/lib | |
| 74 | +/storage | |
| 75 | +/uploads | |
| 76 | +/prisma | |
| 77 | +/components | |
| 78 | + | |
| 79 | + | |
| 80 | +⸻ | |
| 81 | + | |
| 82 | +🔐 AUTHENTIFICATION | |
| 83 | + | |
| 84 | +Page /login | |
| 85 | + • Input password | |
| 86 | + • Si password valide → créer session | |
| 87 | + • Middleware protège toutes routes sauf login | |
| 88 | + | |
| 89 | +⚠️ Même si pw simple : | |
| 90 | + • Hash bcrypt en DB | |
| 91 | + • Rate limit login | |
| 92 | + • Session expiration | |
| 93 | + • Logout automatique | |
| 94 | + | |
| 95 | +⸻ | |
| 96 | + | |
| 97 | +📦 FONCTIONNALITÉS CLOUD | |
| 98 | + | |
| 99 | +1️⃣ Gestion de fichiers | |
| 100 | + • Upload multi-fichiers | |
| 101 | + • Drag & drop | |
| 102 | + • Upload mobile caméra | |
| 103 | + • Rename | |
| 104 | + • Delete | |
| 105 | + • Move file | |
| 106 | + • Duplicate | |
| 107 | + • File size limit configurable | |
| 108 | + • Progress bar upload | |
| 109 | + | |
| 110 | +⸻ | |
| 111 | + | |
| 112 | +2️⃣ Gestion de dossiers | |
| 113 | + • Créer dossier | |
| 114 | + • Dossier imbriqué | |
| 115 | + • Breadcrumb navigation | |
| 116 | + • Drag file vers dossier | |
| 117 | + • Rename folder | |
| 118 | + • Delete folder | |
| 119 | + | |
| 120 | +⸻ | |
| 121 | + | |
| 122 | +3️⃣ Preview de fichiers | |
| 123 | + | |
| 124 | +Support : | |
| 125 | ||
| 126 | + • Images | |
| 127 | + • Vidéo | |
| 128 | + • Audio | |
| 129 | + • Markdown | |
| 130 | + • Code (syntax highlight) | |
| 131 | + • TXT | |
| 132 | + • CSV preview | |
| 133 | + | |
| 134 | +Mobile : | |
| 135 | + • Preview full-screen | |
| 136 | + • Swipe navigation images | |
| 137 | + | |
| 138 | +⸻ | |
| 139 | + | |
| 140 | +4️⃣ Partage sécurisé | |
| 141 | + | |
| 142 | +Créer lien : | |
| 143 | + • Expiration date | |
| 144 | + • Password optionnel | |
| 145 | + • Download only | |
| 146 | + • Preview only | |
| 147 | + • Désactiver à tout moment | |
| 148 | + | |
| 149 | +⸻ | |
| 150 | + | |
| 151 | +5️⃣ Dashboard admin | |
| 152 | + | |
| 153 | +Accessible via /admin | |
| 154 | + • Storage utilisé | |
| 155 | + • Nombre fichiers | |
| 156 | + • Activité récente | |
| 157 | + • Logs accès | |
| 158 | + • Gestion liens partagés | |
| 159 | + | |
| 160 | +Mobile : | |
| 161 | + • Cartes verticales | |
| 162 | + • No horizontal scroll | |
| 163 | + | |
| 164 | +⸻ | |
| 165 | + | |
| 166 | +6️⃣ Recherche | |
| 167 | + • Search full-text filename | |
| 168 | + • Search par type | |
| 169 | + • Search par date | |
| 170 | + • Search par taille | |
| 171 | + | |
| 172 | +⸻ | |
| 173 | + | |
| 174 | +7️⃣ Sécurité | |
| 175 | + • Hash password | |
| 176 | + • CSRF protection | |
| 177 | + • Rate limiting | |
| 178 | + • XSS protection | |
| 179 | + • File type validation | |
| 180 | + • Antivirus hook (optionnel) | |
| 181 | + • HTTPS only | |
| 182 | + | |
| 183 | +⸻ | |
| 184 | + | |
| 185 | +🗄️ DATABASE STRUCTURE | |
| 186 | + | |
| 187 | +Users | |
| 188 | + | |
| 189 | +| id | passwordHash | createdAt | | |
| 190 | + | |
| 191 | +⸻ | |
| 192 | + | |
| 193 | +Files | |
| 194 | + | |
| 195 | +| id | name | path | size | mimeType | folderId | createdAt | | |
| 196 | + | |
| 197 | +⸻ | |
| 198 | + | |
| 199 | +Folders | |
| 200 | + | |
| 201 | +| id | name | parentId | createdAt | | |
| 202 | + | |
| 203 | +⸻ | |
| 204 | + | |
| 205 | +SharedLinks | |
| 206 | + | |
| 207 | +| id | fileId | token | expiresAt | passwordHash | active | | |
| 208 | + | |
| 209 | +⸻ | |
| 210 | + | |
| 211 | +ActivityLogs | |
| 212 | + | |
| 213 | +| id | action | fileId | timestamp | | |
| 214 | + | |
| 215 | +⸻ | |
| 216 | + | |
| 217 | +📱 UI STRUCTURE | |
| 218 | + | |
| 219 | +Desktop | |
| 220 | + • Sidebar gauche | |
| 221 | + • File explorer centre | |
| 222 | + • Preview panel droite | |
| 223 | + | |
| 224 | +Mobile | |
| 225 | + • Sidebar hidden | |
| 226 | + • Hamburger menu | |
| 227 | + • Full width file explorer | |
| 228 | + • Preview modal full screen | |
| 229 | + | |
| 230 | +⸻ | |
| 231 | + | |
| 232 | +🚀 FONCTIONNALITÉS AVANCÉES (OPTIONNEL) | |
| 233 | + • Album photo intelligent (face recognition local) | |
| 234 | + • Tagging automatique | |
| 235 | + • OCR PDF | |
| 236 | + • AI search (“Find all files about FOMC”) | |
| 237 | + • Versioning fichiers | |
| 238 | + • Recycle bin | |
| 239 | + • Backup automatique | |
| 240 | + • Sync multi-Mac | |
| 241 | + • Accès via Tailscale sécurisé | |
| 242 | + • Encrypted storage at rest | |
| 243 | + | |
| 244 | +⸻ | |
| 245 | + | |
| 246 | +🧠 POSITIONNEMENT | |
| 247 | + | |
| 248 | +Ce cloud doit être : | |
| 249 | + • Privé | |
| 250 | + • Souverain | |
| 251 | + • Minimaliste | |
| 252 | + • Plus clean que Dropbox | |
| 253 | + • Plus sécurisé que Google Drive | |
| 254 | + • Mobile-ready | |
| 255 | + • Local-first | |
| 256 | + | |
| 257 | +⸻ | |
| 258 | + | |
| 259 | +⚙️ PERFORMANCE | |
| 260 | + • Chunk upload | |
| 261 | + • Lazy loading | |
| 262 | + • Infinite scroll | |
| 263 | + • Optimized image loading | |
| 264 | + • Streaming preview | |
| 265 | + | |
| 266 | +⸻ | |
| 267 | + | |
| 268 | +🔮 ÉVOLUTION FUTURE | |
| 269 | + • Multi-user system | |
| 270 | + • Permission management | |
| 271 | + • AI assistant intégré | |
| 272 | + • Auto organization fichiers | |
| 273 | + • Vector database index | |
| 274 | + • Search sémantique | |
| 275 | + | |
| 276 | +⸻ | |
| 277 | + | |
| 278 | +🎨 DESIGN | |
| 279 | + • Dark mode par défaut | |
| 280 | + • Noir / gris / blanc | |
| 281 | + • Typo Inter | |
| 282 | + • UI minimal | |
| 283 | + • Micro-animations subtiles | |
| 284 | + | |
| 285 | +⸻ | |
| 286 | + | |
| 287 | +🛡️ NOTE IMPORTANTE | |
| 288 | + | |
| 289 | +Même si accès = admin123, | |
| 290 | +le système doit être codé proprement pour pouvoir : | |
| 291 | + • Changer mot de passe | |
| 292 | + • Ajouter 2FA futur | |
| 293 | + • Ajouter multi-user | |
| 294 | + | |
| 295 | +⸻ | |
| 296 | + | |
| 297 | +Si tu veux maintenant : | |
| 298 | + 1. 🔥 Je te génère l’architecture complète du projet | |
| 299 | + 2. 💻 Je te code le système login | |
| 300 | + 3. 📁 Je te code le file explorer | |
| 301 | + 4. 📱 Je te fais le layout mobile-first complet | |
| 302 | + 5. 🧠 On ajoute AI local pour ton cloud.spboucher | |
| 303 | + | |
| 304 | +Tu veux le faire : | |
| 305 | + • Local sur Mac Studio ? | |
| 306 | + • Sur NAS ? | |
| 307 | + • Sur VPS public sécurisé ? | |
added
README.md
+36 −0
@@ -0,0 +1,36 @@ | ||
| 1 | +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). | |
| 2 | + | |
| 3 | +## Getting Started | |
| 4 | + | |
| 5 | +First, run the development server: | |
| 6 | + | |
| 7 | +```bash | |
| 8 | +npm run dev | |
| 9 | +# or | |
| 10 | +yarn dev | |
| 11 | +# or | |
| 12 | +pnpm dev | |
| 13 | +# or | |
| 14 | +bun dev | |
| 15 | +``` | |
| 16 | + | |
| 17 | +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. | |
| 18 | + | |
| 19 | +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. | |
| 20 | + | |
| 21 | +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. | |
| 22 | + | |
| 23 | +## Learn More | |
| 24 | + | |
| 25 | +To learn more about Next.js, take a look at the following resources: | |
| 26 | + | |
| 27 | +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. | |
| 28 | +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. | |
| 29 | + | |
| 30 | +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! | |
| 31 | + | |
| 32 | +## Deploy on Vercel | |
| 33 | + | |
| 34 | +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | |
| 35 | + | |
| 36 | +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. | |
added
TODO.md
+74 −0
@@ -0,0 +1,74 @@ | ||
| 1 | +# SPB Private Cloud - Todo & Upgrades | |
| 2 | + | |
| 3 | +## Priorité Haute | |
| 4 | + | |
| 5 | +- [x] **Rate limiting login** - Protection brute-force sur `/api/auth/login` | |
| 6 | +- [ ] **Migration PostgreSQL** - Remplacer SQLite par PostgreSQL pour la production | |
| 7 | +- [ ] **HTTPS / SSL** - Configurer certificats SSL (Let's Encrypt) | |
| 8 | +- [ ] **CSRF protection** - Implémenter tokens CSRF sur les formulaires | |
| 9 | +- [ ] **Validation types fichiers** - Bloquer les fichiers dangereux (.exe, .bat, scripts) | |
| 10 | +- [ ] **Chunk upload** - Upload par morceaux pour fichiers volumineux (>100MB) | |
| 11 | +- [ ] **Corbeille (Recycle Bin)** - Soft delete avec récupération sous 30 jours | |
| 12 | +- [x] **Changement mot de passe** - Interface admin pour modifier le password | |
| 13 | + | |
| 14 | +## Priorité Moyenne | |
| 15 | + | |
| 16 | +- [x] **2FA (Two-Factor Auth)** - Authentification TOTP (Google Authenticator) | |
| 17 | +- [ ] **Drag & drop fichiers vers dossiers** - Glisser fichiers entre dossiers | |
| 18 | +- [x] **Bulk actions** - Sélection multiple + actions groupées (supprimer, déplacer, ZIP, chiffrer) | |
| 19 | +- [x] **Download dossier en ZIP** - Télécharger un dossier complet en archive | |
| 20 | +- [ ] **Image thumbnails** - Générer des miniatures pour les images | |
| 21 | +- [ ] **Video streaming** - Streaming adaptatif pour les vidéos (HLS) | |
| 22 | +- [ ] **Progress bar réelle** - Upload avec progression réelle via XMLHttpRequest | |
| 23 | +- [ ] **Infinite scroll** - Pagination infinie | |
| 24 | +- [ ] **Lazy loading images** - Chargement progressif des images | |
| 25 | +- [x] **Backup automatique** - Backup DB + fichiers en ZIP | |
| 26 | +- [x] **Notifications** - Système de notifications avec badge | |
| 27 | + | |
| 28 | +## Fonctionnalités Avancées | |
| 29 | + | |
| 30 | +- [x] **Multi-user system** - Plusieurs utilisateurs avec comptes séparés | |
| 31 | +- [x] **Permission management** - Rôles (admin, user, viewer) | |
| 32 | +- [ ] **Versioning complet** - Historique de toutes les versions avec diff visuel | |
| 33 | +- [x] **OCR PDF** - Extraction texte des PDF/images via Claude Vision | |
| 34 | +- [x] **AI Search** - Recherche sémantique via Claude Sonnet 4.5 | |
| 35 | +- [x] **Auto-tagging** - Classification automatique des fichiers via IA | |
| 36 | +- [ ] **Album photo intelligent** - Regroupement par date/lieu | |
| 37 | +- [x] **Encrypted storage** - Chiffrement at-rest AES-256-GCM | |
| 38 | +- [ ] **Sync multi-device** - Synchronisation avec d'autres appareils | |
| 39 | +- [ ] **Accès Tailscale** - VPN mesh pour accès sécurisé | |
| 40 | +- [ ] **WebDAV support** - Monter le cloud comme lecteur réseau | |
| 41 | +- [x] **API publique** - REST API avec clés API `/api/v1/files` | |
| 42 | + | |
| 43 | +## UI/UX Améliorations | |
| 44 | + | |
| 45 | +- [x] **Mode clair/sombre** - Toggle light/dark theme | |
| 46 | +- [x] **Assistant IA** - Panel latéral avec recherche IA et chat | |
| 47 | +- [x] **Cloche notifications** - Badge avec compteur non-lus | |
| 48 | +- [x] **Gestion utilisateurs** - Interface admin CRUD | |
| 49 | +- [x] **Gestion clés API** - Interface de création/suppression | |
| 50 | +- [x] **Configuration 2FA** - Setup QR code + vérification | |
| 51 | +- [x] **Panel backup** - Interface de gestion des backups | |
| 52 | +- [ ] **Thèmes personnalisés** - Couleurs accent personnalisables | |
| 53 | +- [ ] **Raccourcis clavier** - Ctrl+U upload, Ctrl+N nouveau dossier | |
| 54 | +- [ ] **Drag & drop global** - Déposer fichiers n'importe où | |
| 55 | +- [ ] **Preview plein écran mobile** - Swipe navigation entre images | |
| 56 | +- [ ] **Grid size ajustable** - Slider pour taille des vignettes | |
| 57 | +- [ ] **Tri avancé** - Tri par nom, date, taille, type | |
| 58 | +- [ ] **Filtres avancés** - Filtrer par type, date, taille, tags | |
| 59 | + | |
| 60 | +## Performance | |
| 61 | + | |
| 62 | +- [ ] **CDN local** - Cache statique pour fichiers fréquemment accédés | |
| 63 | +- [ ] **Service Worker** - Cache offline pour l'interface | |
| 64 | +- [ ] **Image optimization** - Compression automatique des images | |
| 65 | +- [ ] **Database indexing** - Optimiser les index | |
| 66 | +- [ ] **Connection pooling** - Pool de connexions DB | |
| 67 | + | |
| 68 | +## DevOps | |
| 69 | + | |
| 70 | +- [ ] **Docker compose** - Containeriser l'app | |
| 71 | +- [ ] **CI/CD pipeline** - GitHub Actions | |
| 72 | +- [ ] **Monitoring** - Health checks, métriques | |
| 73 | +- [ ] **Logs structurés** - Logging JSON avec rotation | |
| 74 | +- [ ] **Auto-update** - Mécanisme de mise à jour | |
added
app/admin/activity/page.tsx
+92 −0
@@ -0,0 +1,92 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | +import AppShell from "@/components/layout/AppShell"; | |
| 5 | +import ActivityLog from "@/components/admin/ActivityLog"; | |
| 6 | +import Button from "@/components/ui/Button"; | |
| 7 | + | |
| 8 | +interface Activity { | |
| 9 | + id: string; | |
| 10 | + action: string; | |
| 11 | + details: string | null; | |
| 12 | + createdAt: string; | |
| 13 | + file: { name: string } | null; | |
| 14 | +} | |
| 15 | + | |
| 16 | +export default function ActivityPage() { | |
| 17 | + const [activities, setActivities] = useState<Activity[]>([]); | |
| 18 | + const [loading, setLoading] = useState(true); | |
| 19 | + const [total, setTotal] = useState(0); | |
| 20 | + const [offset, setOffset] = useState(0); | |
| 21 | + const limit = 50; | |
| 22 | + | |
| 23 | + const fetchActivity = async (newOffset = 0) => { | |
| 24 | + setLoading(true); | |
| 25 | + try { | |
| 26 | + const res = await fetch( | |
| 27 | + `/api/admin/activity?limit=${limit}&offset=${newOffset}` | |
| 28 | + ); | |
| 29 | + if (res.ok) { | |
| 30 | + const data = await res.json(); | |
| 31 | + setActivities(data.activities); | |
| 32 | + setTotal(data.total); | |
| 33 | + setOffset(newOffset); | |
| 34 | + } | |
| 35 | + } finally { | |
| 36 | + setLoading(false); | |
| 37 | + } | |
| 38 | + }; | |
| 39 | + | |
| 40 | + useEffect(() => { | |
| 41 | + fetchActivity(); | |
| 42 | + }, []); | |
| 43 | + | |
| 44 | + return ( | |
| 45 | + <AppShell> | |
| 46 | + <div className="p-4 lg:p-6 space-y-6"> | |
| 47 | + <div> | |
| 48 | + <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">Activité</h1> | |
| 49 | + <p className="text-sm text-gray-500 mt-1"> | |
| 50 | + Historique complet ({total} entrées) | |
| 51 | + </p> | |
| 52 | + </div> | |
| 53 | + | |
| 54 | + {loading ? ( | |
| 55 | + <div className="flex items-center justify-center h-32"> | |
| 56 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 57 | + </div> | |
| 58 | + ) : ( | |
| 59 | + <> | |
| 60 | + <div className="bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl overflow-hidden shadow-sm dark:shadow-none transition-colors"> | |
| 61 | + <ActivityLog activities={activities} /> | |
| 62 | + </div> | |
| 63 | + | |
| 64 | + {total > limit && ( | |
| 65 | + <div className="flex items-center justify-center gap-4"> | |
| 66 | + <Button | |
| 67 | + variant="ghost" | |
| 68 | + size="sm" | |
| 69 | + disabled={offset === 0} | |
| 70 | + onClick={() => fetchActivity(Math.max(0, offset - limit))} | |
| 71 | + > | |
| 72 | + Précédent | |
| 73 | + </Button> | |
| 74 | + <span className="text-sm text-gray-500"> | |
| 75 | + {offset + 1}-{Math.min(offset + limit, total)} sur {total} | |
| 76 | + </span> | |
| 77 | + <Button | |
| 78 | + variant="ghost" | |
| 79 | + size="sm" | |
| 80 | + disabled={offset + limit >= total} | |
| 81 | + onClick={() => fetchActivity(offset + limit)} | |
| 82 | + > | |
| 83 | + Suivant | |
| 84 | + </Button> | |
| 85 | + </div> | |
| 86 | + )} | |
| 87 | + </> | |
| 88 | + )} | |
| 89 | + </div> | |
| 90 | + </AppShell> | |
| 91 | + ); | |
| 92 | +} | |
added
app/admin/api-keys/page.tsx
+18 −0
@@ -0,0 +1,18 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import AppShell from "@/components/layout/AppShell"; | |
| 4 | +import ApiKeyManager from "@/components/admin/ApiKeyManager"; | |
| 5 | + | |
| 6 | +export default function ApiKeysPage() { | |
| 7 | + return ( | |
| 8 | + <AppShell> | |
| 9 | + <div className="p-4 lg:p-6 space-y-6"> | |
| 10 | + <div> | |
| 11 | + <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">Clés API</h1> | |
| 12 | + <p className="text-sm text-gray-500 mt-1">Gérez l'accès à l'API REST publique</p> | |
| 13 | + </div> | |
| 14 | + <ApiKeyManager /> | |
| 15 | + </div> | |
| 16 | + </AppShell> | |
| 17 | + ); | |
| 18 | +} | |
added
app/admin/backup/page.tsx
+18 −0
@@ -0,0 +1,18 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import AppShell from "@/components/layout/AppShell"; | |
| 4 | +import BackupPanel from "@/components/admin/BackupPanel"; | |
| 5 | + | |
| 6 | +export default function BackupPage() { | |
| 7 | + return ( | |
| 8 | + <AppShell> | |
| 9 | + <div className="p-4 lg:p-6 space-y-6"> | |
| 10 | + <div> | |
| 11 | + <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">Backups</h1> | |
| 12 | + <p className="text-sm text-gray-500 mt-1">Sauvegardez et restaurez vos données</p> | |
| 13 | + </div> | |
| 14 | + <BackupPanel /> | |
| 15 | + </div> | |
| 16 | + </AppShell> | |
| 17 | + ); | |
| 18 | +} | |
added
app/admin/page.tsx
+88 −0
@@ -0,0 +1,88 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | +import AppShell from "@/components/layout/AppShell"; | |
| 5 | +import StatsCards from "@/components/admin/StatsCards"; | |
| 6 | +import ActivityLog from "@/components/admin/ActivityLog"; | |
| 7 | +import StorageChart from "@/components/admin/StorageChart"; | |
| 8 | + | |
| 9 | +interface Stats { | |
| 10 | + fileCount: number; | |
| 11 | + folderCount: number; | |
| 12 | + shareCount: number; | |
| 13 | + storageUsed: number; | |
| 14 | + diskFileCount: number; | |
| 15 | + recentActivity: Array<{ | |
| 16 | + id: string; | |
| 17 | + action: string; | |
| 18 | + details: string | null; | |
| 19 | + createdAt: string; | |
| 20 | + file: { name: string } | null; | |
| 21 | + }>; | |
| 22 | + typeBreakdown: Array<{ | |
| 23 | + type: string; | |
| 24 | + count: number; | |
| 25 | + size: number; | |
| 26 | + }>; | |
| 27 | +} | |
| 28 | + | |
| 29 | +export default function AdminPage() { | |
| 30 | + const [stats, setStats] = useState<Stats | null>(null); | |
| 31 | + const [loading, setLoading] = useState(true); | |
| 32 | + | |
| 33 | + useEffect(() => { | |
| 34 | + fetch("/api/admin/stats") | |
| 35 | + .then((res) => res.json()) | |
| 36 | + .then((data) => { | |
| 37 | + setStats(data); | |
| 38 | + setLoading(false); | |
| 39 | + }) | |
| 40 | + .catch(() => setLoading(false)); | |
| 41 | + }, []); | |
| 42 | + | |
| 43 | + return ( | |
| 44 | + <AppShell> | |
| 45 | + <div className="p-3 sm:p-4 lg:p-6 space-y-4 sm:space-y-6"> | |
| 46 | + <div> | |
| 47 | + <h1 className="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-white">Dashboard</h1> | |
| 48 | + <p className="text-sm text-gray-500 mt-1">Vue d'ensemble de votre cloud</p> | |
| 49 | + </div> | |
| 50 | + | |
| 51 | + {loading ? ( | |
| 52 | + <div className="flex items-center justify-center h-32"> | |
| 53 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 54 | + </div> | |
| 55 | + ) : stats ? ( | |
| 56 | + <> | |
| 57 | + <StatsCards stats={stats} /> | |
| 58 | + | |
| 59 | + <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6"> | |
| 60 | + {/* Storage Chart */} | |
| 61 | + <div className="bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl p-4 sm:p-6 shadow-sm dark:shadow-none transition-colors"> | |
| 62 | + <h2 className="text-base sm:text-lg font-medium text-gray-900 dark:text-white mb-4"> | |
| 63 | + Répartition du stockage | |
| 64 | + </h2> | |
| 65 | + <StorageChart | |
| 66 | + data={stats.typeBreakdown} | |
| 67 | + totalSize={stats.storageUsed} | |
| 68 | + /> | |
| 69 | + </div> | |
| 70 | + | |
| 71 | + {/* Recent Activity */} | |
| 72 | + <div className="bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl p-4 sm:p-6 shadow-sm dark:shadow-none transition-colors"> | |
| 73 | + <h2 className="text-base sm:text-lg font-medium text-gray-900 dark:text-white mb-4"> | |
| 74 | + Activité récente | |
| 75 | + </h2> | |
| 76 | + <div className="max-h-80 overflow-y-auto"> | |
| 77 | + <ActivityLog activities={stats.recentActivity} /> | |
| 78 | + </div> | |
| 79 | + </div> | |
| 80 | + </div> | |
| 81 | + </> | |
| 82 | + ) : ( | |
| 83 | + <p className="text-gray-500">Impossible de charger les stats</p> | |
| 84 | + )} | |
| 85 | + </div> | |
| 86 | + </AppShell> | |
| 87 | + ); | |
| 88 | +} | |
added
app/admin/settings/page.tsx
+34 −0
@@ -0,0 +1,34 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | +import AppShell from "@/components/layout/AppShell"; | |
| 5 | +import TwoFactorSetup from "@/components/admin/TwoFactorSetup"; | |
| 6 | + | |
| 7 | +export default function SettingsPage() { | |
| 8 | + const [twoFactorEnabled, setTwoFactorEnabled] = useState(false); | |
| 9 | + | |
| 10 | + const fetchStatus = async () => { | |
| 11 | + try { | |
| 12 | + const res = await fetch("/api/auth/session"); | |
| 13 | + if (res.ok) { | |
| 14 | + const data = await res.json(); | |
| 15 | + setTwoFactorEnabled(data.twoFactorEnabled || false); | |
| 16 | + } | |
| 17 | + } catch {} | |
| 18 | + }; | |
| 19 | + | |
| 20 | + useEffect(() => { fetchStatus(); }, []); | |
| 21 | + | |
| 22 | + return ( | |
| 23 | + <AppShell> | |
| 24 | + <div className="p-4 lg:p-6 space-y-6"> | |
| 25 | + <div> | |
| 26 | + <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">Paramètres</h1> | |
| 27 | + <p className="text-sm text-gray-500 mt-1">Sécurité et préférences</p> | |
| 28 | + </div> | |
| 29 | + | |
| 30 | + <TwoFactorSetup enabled={twoFactorEnabled} onStatusChange={fetchStatus} /> | |
| 31 | + </div> | |
| 32 | + </AppShell> | |
| 33 | + ); | |
| 34 | +} | |
added
app/admin/shares/page.tsx
+159 −0
@@ -0,0 +1,159 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | +import AppShell from "@/components/layout/AppShell"; | |
| 5 | +import Button from "@/components/ui/Button"; | |
| 6 | +import { formatDate } from "@/lib/utils"; | |
| 7 | +import { motion } from "framer-motion"; | |
| 8 | +import { Link2, Link2Off, Trash2, Copy, ExternalLink } from "lucide-react"; | |
| 9 | +import toast from "react-hot-toast"; | |
| 10 | + | |
| 11 | +interface SharedLink { | |
| 12 | + id: string; | |
| 13 | + token: string; | |
| 14 | + mode: string; | |
| 15 | + active: boolean; | |
| 16 | + expiresAt: string | null; | |
| 17 | + passwordHash: string | null; | |
| 18 | + createdAt: string; | |
| 19 | + file: { name: string; mimeType: string; size: number } | null; | |
| 20 | +} | |
| 21 | + | |
| 22 | +export default function SharesPage() { | |
| 23 | + const [shares, setShares] = useState<SharedLink[]>([]); | |
| 24 | + const [loading, setLoading] = useState(true); | |
| 25 | + | |
| 26 | + const fetchShares = async () => { | |
| 27 | + setLoading(true); | |
| 28 | + try { | |
| 29 | + const res = await fetch("/api/shares"); | |
| 30 | + if (res.ok) setShares(await res.json()); | |
| 31 | + } finally { | |
| 32 | + setLoading(false); | |
| 33 | + } | |
| 34 | + }; | |
| 35 | + | |
| 36 | + useEffect(() => { | |
| 37 | + fetchShares(); | |
| 38 | + }, []); | |
| 39 | + | |
| 40 | + const toggleActive = async (id: string, active: boolean) => { | |
| 41 | + await fetch(`/api/shares/${id}`, { | |
| 42 | + method: "PATCH", | |
| 43 | + headers: { "Content-Type": "application/json" }, | |
| 44 | + body: JSON.stringify({ active: !active }), | |
| 45 | + }); | |
| 46 | + fetchShares(); | |
| 47 | + toast.success(active ? "Lien désactivé" : "Lien activé"); | |
| 48 | + }; | |
| 49 | + | |
| 50 | + const deleteShare = async (id: string) => { | |
| 51 | + await fetch(`/api/shares/${id}`, { method: "DELETE" }); | |
| 52 | + fetchShares(); | |
| 53 | + toast.success("Lien supprimé"); | |
| 54 | + }; | |
| 55 | + | |
| 56 | + const copyLink = (token: string) => { | |
| 57 | + navigator.clipboard.writeText(`${window.location.origin}/shared/${token}`); | |
| 58 | + toast.success("Lien copié"); | |
| 59 | + }; | |
| 60 | + | |
| 61 | + return ( | |
| 62 | + <AppShell> | |
| 63 | + <div className="p-3 sm:p-4 lg:p-6 space-y-4 sm:space-y-6"> | |
| 64 | + <div> | |
| 65 | + <h1 className="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-white">Liens partagés</h1> | |
| 66 | + <p className="text-sm text-gray-500 mt-1"> | |
| 67 | + Gérez vos liens de partage | |
| 68 | + </p> | |
| 69 | + </div> | |
| 70 | + | |
| 71 | + {loading ? ( | |
| 72 | + <div className="flex items-center justify-center h-32"> | |
| 73 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 74 | + </div> | |
| 75 | + ) : shares.length === 0 ? ( | |
| 76 | + <div className="text-center text-gray-500 py-12"> | |
| 77 | + <Link2 className="w-12 h-12 mx-auto mb-4 text-gray-300 dark:text-gray-600" /> | |
| 78 | + <p>Aucun lien de partage</p> | |
| 79 | + </div> | |
| 80 | + ) : ( | |
| 81 | + <div className="space-y-3"> | |
| 82 | + {shares.map((share, i) => ( | |
| 83 | + <motion.div | |
| 84 | + key={share.id} | |
| 85 | + initial={{ opacity: 0, y: 10 }} | |
| 86 | + animate={{ opacity: 1, y: 0 }} | |
| 87 | + transition={{ delay: i * 0.05 }} | |
| 88 | + className={`bg-white dark:bg-white/5 border rounded-xl p-3.5 sm:p-4 shadow-sm dark:shadow-none transition-colors ${ | |
| 89 | + share.active ? "border-gray-200 dark:border-white/10" : "border-gray-100 dark:border-white/5 opacity-60" | |
| 90 | + }`} | |
| 91 | + > | |
| 92 | + <div className="flex flex-col gap-3"> | |
| 93 | + <div className="flex-1 min-w-0"> | |
| 94 | + <p className="text-sm font-medium text-gray-900 dark:text-white truncate"> | |
| 95 | + {share.file?.name || "Fichier supprimé"} | |
| 96 | + </p> | |
| 97 | + <div className="flex flex-wrap gap-x-2 gap-y-1 mt-1"> | |
| 98 | + <span className="text-xs text-gray-500"> | |
| 99 | + {share.mode === "DOWNLOAD" ? "Téléchargement" : "Aperçu"} | |
| 100 | + </span> | |
| 101 | + {share.passwordHash && ( | |
| 102 | + <span className="text-xs text-yellow-500">Protégé</span> | |
| 103 | + )} | |
| 104 | + {share.expiresAt && ( | |
| 105 | + <span className="text-xs text-gray-500 break-words"> | |
| 106 | + Expire: {formatDate(share.expiresAt)} | |
| 107 | + </span> | |
| 108 | + )} | |
| 109 | + <span className="text-xs text-gray-400 dark:text-gray-600 break-words"> | |
| 110 | + Créé: {formatDate(share.createdAt)} | |
| 111 | + </span> | |
| 112 | + </div> | |
| 113 | + </div> | |
| 114 | + <div className="flex items-center gap-1.5 sm:gap-2 flex-wrap"> | |
| 115 | + <Button | |
| 116 | + variant="ghost" | |
| 117 | + size="sm" | |
| 118 | + onClick={() => copyLink(share.token)} | |
| 119 | + > | |
| 120 | + <Copy className="w-3.5 h-3.5" /> | |
| 121 | + <span className="text-xs sm:hidden">Copier</span> | |
| 122 | + </Button> | |
| 123 | + <Button | |
| 124 | + variant="ghost" | |
| 125 | + size="sm" | |
| 126 | + onClick={() => | |
| 127 | + window.open(`/shared/${share.token}`, "_blank") | |
| 128 | + } | |
| 129 | + > | |
| 130 | + <ExternalLink className="w-3.5 h-3.5" /> | |
| 131 | + </Button> | |
| 132 | + <Button | |
| 133 | + variant="ghost" | |
| 134 | + size="sm" | |
| 135 | + onClick={() => toggleActive(share.id, share.active)} | |
| 136 | + > | |
| 137 | + {share.active ? ( | |
| 138 | + <Link2Off className="w-3.5 h-3.5" /> | |
| 139 | + ) : ( | |
| 140 | + <Link2 className="w-3.5 h-3.5" /> | |
| 141 | + )} | |
| 142 | + </Button> | |
| 143 | + <Button | |
| 144 | + variant="ghost" | |
| 145 | + size="sm" | |
| 146 | + onClick={() => deleteShare(share.id)} | |
| 147 | + > | |
| 148 | + <Trash2 className="w-3.5 h-3.5 text-red-400" /> | |
| 149 | + </Button> | |
| 150 | + </div> | |
| 151 | + </div> | |
| 152 | + </motion.div> | |
| 153 | + ))} | |
| 154 | + </div> | |
| 155 | + )} | |
| 156 | + </div> | |
| 157 | + </AppShell> | |
| 158 | + ); | |
| 159 | +} | |
added
app/admin/users/page.tsx
+18 −0
@@ -0,0 +1,18 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import AppShell from "@/components/layout/AppShell"; | |
| 4 | +import UserManagement from "@/components/admin/UserManagement"; | |
| 5 | + | |
| 6 | +export default function UsersPage() { | |
| 7 | + return ( | |
| 8 | + <AppShell> | |
| 9 | + <div className="p-4 lg:p-6 space-y-6"> | |
| 10 | + <div> | |
| 11 | + <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">Utilisateurs</h1> | |
| 12 | + <p className="text-sm text-gray-500 mt-1">Gérez les utilisateurs de votre cloud</p> | |
| 13 | + </div> | |
| 14 | + <UserManagement /> | |
| 15 | + </div> | |
| 16 | + </AppShell> | |
| 17 | + ); | |
| 18 | +} | |
added
app/api/admin/activity/route.ts
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | + | |
| 4 | +export async function GET(request: NextRequest) { | |
| 5 | + const { searchParams } = new URL(request.url); | |
| 6 | + const limit = parseInt(searchParams.get("limit") || "50"); | |
| 7 | + const offset = parseInt(searchParams.get("offset") || "0"); | |
| 8 | + | |
| 9 | + const [activities, total] = await Promise.all([ | |
| 10 | + prisma.activityLog.findMany({ | |
| 11 | + take: limit, | |
| 12 | + skip: offset, | |
| 13 | + orderBy: { createdAt: "desc" }, | |
| 14 | + include: { file: { select: { name: true } } }, | |
| 15 | + }), | |
| 16 | + prisma.activityLog.count(), | |
| 17 | + ]); | |
| 18 | + | |
| 19 | + return NextResponse.json({ activities, total }); | |
| 20 | +} | |
added
app/api/admin/stats/route.ts
+41 −0
@@ -0,0 +1,41 @@ | ||
| 1 | +import { NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getStorageUsage } from "@/lib/storage"; | |
| 4 | + | |
| 5 | +export async function GET() { | |
| 6 | + const [fileCount, folderCount, shareCount, recentActivity, storageUsage] = | |
| 7 | + await Promise.all([ | |
| 8 | + prisma.file.count(), | |
| 9 | + prisma.folder.count(), | |
| 10 | + prisma.sharedLink.count({ where: { active: true } }), | |
| 11 | + prisma.activityLog.findMany({ | |
| 12 | + take: 20, | |
| 13 | + orderBy: { createdAt: "desc" }, | |
| 14 | + include: { file: { select: { name: true } } }, | |
| 15 | + }), | |
| 16 | + getStorageUsage(), | |
| 17 | + ]); | |
| 18 | + | |
| 19 | + // File type breakdown | |
| 20 | + const filesByType = await prisma.file.groupBy({ | |
| 21 | + by: ["mimeType"], | |
| 22 | + _count: true, | |
| 23 | + _sum: { size: true }, | |
| 24 | + }); | |
| 25 | + | |
| 26 | + const typeBreakdown = filesByType.map((t) => ({ | |
| 27 | + type: t.mimeType, | |
| 28 | + count: t._count, | |
| 29 | + size: Number(t._sum.size || 0), | |
| 30 | + })); | |
| 31 | + | |
| 32 | + return NextResponse.json({ | |
| 33 | + fileCount, | |
| 34 | + folderCount, | |
| 35 | + shareCount, | |
| 36 | + storageUsed: storageUsage.totalSize, | |
| 37 | + diskFileCount: storageUsage.fileCount, | |
| 38 | + recentActivity, | |
| 39 | + typeBreakdown, | |
| 40 | + }); | |
| 41 | +} | |
added
app/api/ai/chat/route.ts
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { aiChat } from "@/lib/ai"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + const { message, context } = await request.json(); | |
| 12 | + if (!message) return NextResponse.json({ error: "message required" }, { status: 400 }); | |
| 13 | + | |
| 14 | + try { | |
| 15 | + const reply = await aiChat(message, context); | |
| 16 | + return NextResponse.json({ reply }); | |
| 17 | + } catch (error) { | |
| 18 | + return NextResponse.json({ error: "Chat failed", details: (error as Error).message }, { status: 500 }); | |
| 19 | + } | |
| 20 | +} | |
added
app/api/ai/describe/route.ts
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { aiDescribe } from "@/lib/ai"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + const { fileId } = await request.json(); | |
| 12 | + if (!fileId) return NextResponse.json({ error: "fileId required" }, { status: 400 }); | |
| 13 | + | |
| 14 | + try { | |
| 15 | + const description = await aiDescribe(fileId); | |
| 16 | + return NextResponse.json({ description }); | |
| 17 | + } catch (error) { | |
| 18 | + return NextResponse.json({ error: "Describe failed", details: (error as Error).message }, { status: 500 }); | |
| 19 | + } | |
| 20 | +} | |
added
app/api/ai/ocr/route.ts
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { aiOcr } from "@/lib/ai"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + const { fileId } = await request.json(); | |
| 12 | + if (!fileId) return NextResponse.json({ error: "fileId required" }, { status: 400 }); | |
| 13 | + | |
| 14 | + try { | |
| 15 | + const text = await aiOcr(fileId); | |
| 16 | + return NextResponse.json({ text }); | |
| 17 | + } catch (error) { | |
| 18 | + return NextResponse.json({ error: "OCR failed", details: (error as Error).message }, { status: 500 }); | |
| 19 | + } | |
| 20 | +} | |
added
app/api/ai/search/route.ts
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { aiSearch } from "@/lib/ai"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + const { query } = await request.json(); | |
| 12 | + if (!query) return NextResponse.json({ error: "Query required" }, { status: 400 }); | |
| 13 | + | |
| 14 | + try { | |
| 15 | + const results = await aiSearch(query, session.userId); | |
| 16 | + return NextResponse.json(results); | |
| 17 | + } catch (error) { | |
| 18 | + return NextResponse.json({ error: "AI search failed", details: (error as Error).message }, { status: 500 }); | |
| 19 | + } | |
| 20 | +} | |
added
app/api/ai/tag/route.ts
+20 −0
@@ -0,0 +1,20 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { aiAutoTag } from "@/lib/ai"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + const { fileId } = await request.json(); | |
| 12 | + if (!fileId) return NextResponse.json({ error: "fileId required" }, { status: 400 }); | |
| 13 | + | |
| 14 | + try { | |
| 15 | + const tags = await aiAutoTag(fileId); | |
| 16 | + return NextResponse.json({ tags }); | |
| 17 | + } catch (error) { | |
| 18 | + return NextResponse.json({ error: "Auto-tag failed", details: (error as Error).message }, { status: 500 }); | |
| 19 | + } | |
| 20 | +} | |
added
app/api/auth/2fa/disable/route.ts
+13 −0
@@ -0,0 +1,13 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + await prisma.user.update({ where: { id: session.userId }, data: { twoFactorEnabled: false, twoFactorSecret: null } }); | |
| 12 | + return NextResponse.json({ success: true }); | |
| 13 | +} | |
added
app/api/auth/2fa/setup/route.ts
+23 −0
@@ -0,0 +1,23 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | +import { authenticator } from "otplib"; | |
| 6 | +import QRCode from "qrcode"; | |
| 7 | + | |
| 8 | +export async function POST(request: NextRequest) { | |
| 9 | + const response = NextResponse.next(); | |
| 10 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 11 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 12 | + | |
| 13 | + const secret = authenticator.generateSecret(); | |
| 14 | + const user = await prisma.user.findUnique({ where: { id: session.userId } }); | |
| 15 | + if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 }); | |
| 16 | + | |
| 17 | + await prisma.user.update({ where: { id: session.userId }, data: { twoFactorSecret: secret } }); | |
| 18 | + | |
| 19 | + const otpauth = authenticator.keyuri(user.email || user.id, "SPB Cloud", secret); | |
| 20 | + const qrCodeUrl = await QRCode.toDataURL(otpauth); | |
| 21 | + | |
| 22 | + return NextResponse.json({ secret, qrCodeUrl }); | |
| 23 | +} | |
added
app/api/auth/2fa/verify/route.ts
+49 −0
@@ -0,0 +1,49 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | +import { authenticator } from "otplib"; | |
| 6 | +import { randomBytes } from "crypto"; | |
| 7 | +import { logActivity } from "@/lib/activity"; | |
| 8 | + | |
| 9 | +export async function POST(request: NextRequest) { | |
| 10 | + const body = await request.json(); | |
| 11 | + const { token, enable } = body; | |
| 12 | + | |
| 13 | + const response = NextResponse.json({ success: true }); | |
| 14 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 15 | + | |
| 16 | + // Enable 2FA flow (already logged in, setting up) | |
| 17 | + if (enable && session.isLoggedIn && session.userId) { | |
| 18 | + const user = await prisma.user.findUnique({ where: { id: session.userId } }); | |
| 19 | + if (!user?.twoFactorSecret) return NextResponse.json({ error: "Setup 2FA first" }, { status: 400 }); | |
| 20 | + | |
| 21 | + const valid = authenticator.verify({ token, secret: user.twoFactorSecret }); | |
| 22 | + if (!valid) return NextResponse.json({ error: "Code invalide" }, { status: 401 }); | |
| 23 | + | |
| 24 | + await prisma.user.update({ where: { id: session.userId }, data: { twoFactorEnabled: true } }); | |
| 25 | + return NextResponse.json({ success: true, message: "2FA activ\u00e9" }); | |
| 26 | + } | |
| 27 | + | |
| 28 | + // Login 2FA verification flow | |
| 29 | + if (!session.pendingTwoFactor || !session.pendingUserId) { | |
| 30 | + return NextResponse.json({ error: "No pending 2FA" }, { status: 400 }); | |
| 31 | + } | |
| 32 | + | |
| 33 | + const user = await prisma.user.findUnique({ where: { id: session.pendingUserId } }); | |
| 34 | + if (!user?.twoFactorSecret) return NextResponse.json({ error: "2FA not configured" }, { status: 400 }); | |
| 35 | + | |
| 36 | + const valid = authenticator.verify({ token, secret: user.twoFactorSecret }); | |
| 37 | + if (!valid) return NextResponse.json({ error: "Code invalide" }, { status: 401 }); | |
| 38 | + | |
| 39 | + session.isLoggedIn = true; | |
| 40 | + session.userId = user.id; | |
| 41 | + session.userRole = user.role; | |
| 42 | + session.pendingTwoFactor = false; | |
| 43 | + session.pendingUserId = undefined; | |
| 44 | + session.csrfToken = randomBytes(32).toString("hex"); | |
| 45 | + await session.save(); | |
| 46 | + | |
| 47 | + await logActivity("LOGIN", null, "Login with 2FA"); | |
| 48 | + return response; | |
| 49 | +} | |
added
app/api/auth/login/route.ts
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { verifyPassword } from "@/lib/auth"; | |
| 5 | +import { checkLoginRateLimit } from "@/lib/rate-limit"; | |
| 6 | +import { logActivity } from "@/lib/activity"; | |
| 7 | +import { randomBytes } from "crypto"; | |
| 8 | + | |
| 9 | +export async function POST(request: NextRequest) { | |
| 10 | + const ip = request.headers.get("x-forwarded-for") || "127.0.0.1"; | |
| 11 | + const allowed = await checkLoginRateLimit(ip); | |
| 12 | + if (!allowed) return NextResponse.json({ error: "Trop de tentatives. Réessayez dans 1 minute." }, { status: 429 }); | |
| 13 | + | |
| 14 | + const body = await request.json(); | |
| 15 | + const { password, email } = body; | |
| 16 | + if (!password) return NextResponse.json({ error: "Mot de passe requis" }, { status: 400 }); | |
| 17 | + | |
| 18 | + const { valid, user } = await verifyPassword(password, email || undefined); | |
| 19 | + if (!valid || !user) return NextResponse.json({ error: "Identifiants incorrects" }, { status: 401 }); | |
| 20 | + | |
| 21 | + const response = NextResponse.json({ success: true, requires2FA: user.twoFactorEnabled }); | |
| 22 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 23 | + | |
| 24 | + if (user.twoFactorEnabled) { | |
| 25 | + session.pendingTwoFactor = true; | |
| 26 | + session.pendingUserId = user.id; | |
| 27 | + await session.save(); | |
| 28 | + return response; | |
| 29 | + } | |
| 30 | + | |
| 31 | + session.isLoggedIn = true; | |
| 32 | + session.userId = user.id; | |
| 33 | + session.userRole = user.role; | |
| 34 | + session.csrfToken = randomBytes(32).toString("hex"); | |
| 35 | + await session.save(); | |
| 36 | + | |
| 37 | + await logActivity("LOGIN", null, `Login from ${ip}`); | |
| 38 | + return response; | |
| 39 | +} | |
added
app/api/auth/logout/route.ts
+14 −0
@@ -0,0 +1,14 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | + | |
| 6 | +export async function POST(request: NextRequest) { | |
| 7 | + const response = NextResponse.json({ success: true }); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + | |
| 10 | + await logActivity("LOGOUT"); | |
| 11 | + session.destroy(); | |
| 12 | + | |
| 13 | + return response; | |
| 14 | +} | |
added
app/api/auth/session/route.ts
+25 −0
@@ -0,0 +1,25 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | + | |
| 6 | +export async function GET(request: NextRequest) { | |
| 7 | + const response = NextResponse.json({}); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + | |
| 10 | + if (!session.isLoggedIn || !session.userId) { | |
| 11 | + return NextResponse.json({ isLoggedIn: false }); | |
| 12 | + } | |
| 13 | + | |
| 14 | + const user = await prisma.user.findUnique({ | |
| 15 | + where: { id: session.userId }, | |
| 16 | + select: { id: true, email: true, name: true, role: true, twoFactorEnabled: true }, | |
| 17 | + }); | |
| 18 | + | |
| 19 | + return NextResponse.json({ | |
| 20 | + isLoggedIn: true, | |
| 21 | + userId: session.userId, | |
| 22 | + userRole: session.userRole, | |
| 23 | + ...user, | |
| 24 | + }); | |
| 25 | +} | |
added
app/api/backup/route.ts
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { requireAdmin } from "@/lib/auth"; | |
| 5 | +import { createBackup, listBackups, deleteBackup } from "@/lib/backup"; | |
| 6 | + | |
| 7 | +export async function GET(request: NextRequest) { | |
| 8 | + const response = NextResponse.next(); | |
| 9 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 10 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 11 | + | |
| 12 | + const backups = await listBackups(); | |
| 13 | + return NextResponse.json(backups); | |
| 14 | +} | |
| 15 | + | |
| 16 | +export async function POST(request: NextRequest) { | |
| 17 | + const response = NextResponse.next(); | |
| 18 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 19 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 20 | + | |
| 21 | + const body = await request.json().catch(() => ({})); | |
| 22 | + try { | |
| 23 | + const result = await createBackup(body.type || "full"); | |
| 24 | + return NextResponse.json(result, { status: 201 }); | |
| 25 | + } catch (error) { | |
| 26 | + return NextResponse.json({ error: (error as Error).message }, { status: 500 }); | |
| 27 | + } | |
| 28 | +} | |
| 29 | + | |
| 30 | +export async function DELETE(request: NextRequest) { | |
| 31 | + const response = NextResponse.next(); | |
| 32 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 33 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 34 | + | |
| 35 | + const { id } = await request.json(); | |
| 36 | + const ok = await deleteBackup(id); | |
| 37 | + return NextResponse.json({ success: ok }); | |
| 38 | +} | |
added
app/api/files/[id]/annotations/route.ts
+31 −0
@@ -0,0 +1,31 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | + | |
| 5 | +export async function GET( | |
| 6 | + request: NextRequest, | |
| 7 | + { params }: { params: { id: string } } | |
| 8 | +) { | |
| 9 | + const metadata = await prisma.fileMetadata.findUnique({ | |
| 10 | + where: { fileId: params.id }, | |
| 11 | + }); | |
| 12 | + | |
| 13 | + return NextResponse.json({ annotations: metadata?.annotations || null }); | |
| 14 | +} | |
| 15 | + | |
| 16 | +export async function PUT( | |
| 17 | + request: NextRequest, | |
| 18 | + { params }: { params: { id: string } } | |
| 19 | +) { | |
| 20 | + const { annotations } = await request.json(); | |
| 21 | + | |
| 22 | + const metadata = await prisma.fileMetadata.upsert({ | |
| 23 | + where: { fileId: params.id }, | |
| 24 | + create: { fileId: params.id, annotations }, | |
| 25 | + update: { annotations }, | |
| 26 | + }); | |
| 27 | + | |
| 28 | + await logActivity("ANNOTATE", params.id, "Annotations updated"); | |
| 29 | + | |
| 30 | + return NextResponse.json(metadata); | |
| 31 | +} | |
added
app/api/files/[id]/content/route.ts
+61 −0
@@ -0,0 +1,61 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath, saveVersion } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | +import { writeFile, stat } from "fs/promises"; | |
| 6 | +import { MAX_VERSIONS_PER_FILE } from "@/lib/config"; | |
| 7 | + | |
| 8 | +export async function PUT( | |
| 9 | + request: NextRequest, | |
| 10 | + { params }: { params: { id: string } } | |
| 11 | +) { | |
| 12 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 13 | + | |
| 14 | + if (!file) { | |
| 15 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 16 | + } | |
| 17 | + | |
| 18 | + const content = await request.text(); | |
| 19 | + const filePath = getFilePath(file.storagePath); | |
| 20 | + | |
| 21 | + // Create version before overwriting | |
| 22 | + const versionCount = await prisma.fileVersion.count({ where: { fileId: file.id } }); | |
| 23 | + const versionResult = await saveVersion(file.storagePath, file.name); | |
| 24 | + | |
| 25 | + await prisma.fileVersion.create({ | |
| 26 | + data: { | |
| 27 | + fileId: file.id, | |
| 28 | + versionNum: versionCount + 1, | |
| 29 | + storagePath: versionResult.storagePath, | |
| 30 | + size: BigInt(versionResult.size), | |
| 31 | + changeNote: "Auto-save", | |
| 32 | + }, | |
| 33 | + }); | |
| 34 | + | |
| 35 | + // Cleanup old versions if over limit | |
| 36 | + const versions = await prisma.fileVersion.findMany({ | |
| 37 | + where: { fileId: file.id }, | |
| 38 | + orderBy: { versionNum: "asc" }, | |
| 39 | + }); | |
| 40 | + if (versions.length > MAX_VERSIONS_PER_FILE) { | |
| 41 | + const toDelete = versions.slice(0, versions.length - MAX_VERSIONS_PER_FILE); | |
| 42 | + for (const v of toDelete) { | |
| 43 | + const { deleteVersion } = await import("@/lib/storage"); | |
| 44 | + await deleteVersion(v.storagePath); | |
| 45 | + await prisma.fileVersion.delete({ where: { id: v.id } }); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + | |
| 49 | + // Write new content | |
| 50 | + await writeFile(filePath, content, "utf-8"); | |
| 51 | + const newStat = await stat(filePath); | |
| 52 | + | |
| 53 | + await prisma.file.update({ | |
| 54 | + where: { id: params.id }, | |
| 55 | + data: { size: BigInt(newStat.size) }, | |
| 56 | + }); | |
| 57 | + | |
| 58 | + await logActivity("EDIT", file.id, `Edited ${file.name}`); | |
| 59 | + | |
| 60 | + return NextResponse.json({ success: true, size: Number(newStat.size) }); | |
| 61 | +} | |
added
app/api/files/[id]/download/route.ts
+40 −0
@@ -0,0 +1,40 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | +import { stat } from "fs/promises"; | |
| 6 | +import { createReadStream } from "fs"; | |
| 7 | + | |
| 8 | +export async function GET( | |
| 9 | + request: NextRequest, | |
| 10 | + { params }: { params: { id: string } } | |
| 11 | +) { | |
| 12 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 13 | + | |
| 14 | + if (!file) { | |
| 15 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 16 | + } | |
| 17 | + | |
| 18 | + const filePath = getFilePath(file.storagePath); | |
| 19 | + const fileStat = await stat(filePath); | |
| 20 | + const fileSize = fileStat.size; | |
| 21 | + | |
| 22 | + await logActivity("DOWNLOAD", file.id, `Downloaded ${file.name}`); | |
| 23 | + | |
| 24 | + const stream = createReadStream(filePath); | |
| 25 | + const webStream = new ReadableStream({ | |
| 26 | + start(controller) { | |
| 27 | + stream.on("data", (chunk) => controller.enqueue(chunk)); | |
| 28 | + stream.on("end", () => controller.close()); | |
| 29 | + stream.on("error", (err) => controller.error(err)); | |
| 30 | + }, | |
| 31 | + }); | |
| 32 | + | |
| 33 | + return new Response(webStream, { | |
| 34 | + headers: { | |
| 35 | + "Content-Type": "application/octet-stream", | |
| 36 | + "Content-Disposition": `attachment; filename="${encodeURIComponent(file.name)}"`, | |
| 37 | + "Content-Length": String(fileSize), | |
| 38 | + }, | |
| 39 | + }); | |
| 40 | +} | |
added
app/api/files/[id]/duplicate/route.ts
+34 −0
@@ -0,0 +1,34 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { duplicateFile } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | + | |
| 6 | +export async function POST( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { id: string } } | |
| 9 | +) { | |
| 10 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 11 | + | |
| 12 | + if (!file) { | |
| 13 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 14 | + } | |
| 15 | + | |
| 16 | + const { storagePath, size } = await duplicateFile(file.storagePath, file.name); | |
| 17 | + | |
| 18 | + const duplicated = await prisma.file.create({ | |
| 19 | + data: { | |
| 20 | + name: `${file.name} (copy)`, | |
| 21 | + storagePath, | |
| 22 | + size: BigInt(size), | |
| 23 | + mimeType: file.mimeType, | |
| 24 | + folderId: file.folderId, | |
| 25 | + }, | |
| 26 | + }); | |
| 27 | + | |
| 28 | + await logActivity("UPLOAD", duplicated.id, `Duplicated ${file.name}`); | |
| 29 | + | |
| 30 | + return NextResponse.json( | |
| 31 | + { ...duplicated, size: Number(duplicated.size) }, | |
| 32 | + { status: 201 } | |
| 33 | + ); | |
| 34 | +} | |
added
app/api/files/[id]/favorite/route.ts
+24 −0
@@ -0,0 +1,24 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | + | |
| 5 | +export async function POST( | |
| 6 | + request: NextRequest, | |
| 7 | + { params }: { params: { id: string } } | |
| 8 | +) { | |
| 9 | + const existing = await prisma.fileMetadata.findUnique({ | |
| 10 | + where: { fileId: params.id }, | |
| 11 | + }); | |
| 12 | + | |
| 13 | + const newFavorite = !(existing?.isFavorite ?? false); | |
| 14 | + | |
| 15 | + const metadata = await prisma.fileMetadata.upsert({ | |
| 16 | + where: { fileId: params.id }, | |
| 17 | + create: { fileId: params.id, isFavorite: newFavorite }, | |
| 18 | + update: { isFavorite: newFavorite }, | |
| 19 | + }); | |
| 20 | + | |
| 21 | + await logActivity("FAVORITE", params.id, newFavorite ? "Added to favorites" : "Removed from favorites"); | |
| 22 | + | |
| 23 | + return NextResponse.json(metadata); | |
| 24 | +} | |
added
app/api/files/[id]/metadata/route.ts
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | + | |
| 4 | +export async function GET( | |
| 5 | + request: NextRequest, | |
| 6 | + { params }: { params: { id: string } } | |
| 7 | +) { | |
| 8 | + let metadata = await prisma.fileMetadata.findUnique({ | |
| 9 | + where: { fileId: params.id }, | |
| 10 | + }); | |
| 11 | + | |
| 12 | + if (!metadata) { | |
| 13 | + metadata = await prisma.fileMetadata.create({ | |
| 14 | + data: { fileId: params.id }, | |
| 15 | + }); | |
| 16 | + } | |
| 17 | + | |
| 18 | + return NextResponse.json(metadata); | |
| 19 | +} | |
| 20 | + | |
| 21 | +export async function PATCH( | |
| 22 | + request: NextRequest, | |
| 23 | + { params }: { params: { id: string } } | |
| 24 | +) { | |
| 25 | + const body = await request.json(); | |
| 26 | + const data: Record<string, unknown> = {}; | |
| 27 | + | |
| 28 | + if (body.description !== undefined) data.description = body.description; | |
| 29 | + if (body.isFavorite !== undefined) data.isFavorite = body.isFavorite; | |
| 30 | + if (body.customDates !== undefined) data.customDates = body.customDates; | |
| 31 | + | |
| 32 | + const metadata = await prisma.fileMetadata.upsert({ | |
| 33 | + where: { fileId: params.id }, | |
| 34 | + create: { fileId: params.id, ...data }, | |
| 35 | + update: data, | |
| 36 | + }); | |
| 37 | + | |
| 38 | + return NextResponse.json(metadata); | |
| 39 | +} | |
added
app/api/files/[id]/preview/route.ts
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath } from "@/lib/storage"; | |
| 4 | +import { stat } from "fs/promises"; | |
| 5 | +import { createReadStream } from "fs"; | |
| 6 | + | |
| 7 | +export async function GET( | |
| 8 | + request: NextRequest, | |
| 9 | + { params }: { params: { id: string } } | |
| 10 | +) { | |
| 11 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 12 | + | |
| 13 | + if (!file) { | |
| 14 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 15 | + } | |
| 16 | + | |
| 17 | + const filePath = getFilePath(file.storagePath); | |
| 18 | + const fileStat = await stat(filePath); | |
| 19 | + const fileSize = fileStat.size; | |
| 20 | + | |
| 21 | + const stream = createReadStream(filePath); | |
| 22 | + const webStream = new ReadableStream({ | |
| 23 | + start(controller) { | |
| 24 | + stream.on("data", (chunk) => controller.enqueue(chunk)); | |
| 25 | + stream.on("end", () => controller.close()); | |
| 26 | + stream.on("error", (err) => controller.error(err)); | |
| 27 | + }, | |
| 28 | + }); | |
| 29 | + | |
| 30 | + return new Response(webStream, { | |
| 31 | + headers: { | |
| 32 | + "Content-Type": file.mimeType, | |
| 33 | + "Content-Disposition": `inline; filename="${encodeURIComponent(file.name)}"`, | |
| 34 | + "Content-Length": String(fileSize), | |
| 35 | + "Cache-Control": "private, max-age=3600", | |
| 36 | + }, | |
| 37 | + }); | |
| 38 | +} | |
added
app/api/files/[id]/route.ts
+68 −0
@@ -0,0 +1,68 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { deleteFile } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | + | |
| 6 | +export async function GET( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { id: string } } | |
| 9 | +) { | |
| 10 | + const file = await prisma.file.findUnique({ | |
| 11 | + where: { id: params.id }, | |
| 12 | + include: { folder: true, sharedLinks: true }, | |
| 13 | + }); | |
| 14 | + | |
| 15 | + if (!file) { | |
| 16 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 17 | + } | |
| 18 | + | |
| 19 | + return NextResponse.json({ ...file, size: Number(file.size) }); | |
| 20 | +} | |
| 21 | + | |
| 22 | +export async function PATCH( | |
| 23 | + request: NextRequest, | |
| 24 | + { params }: { params: { id: string } } | |
| 25 | +) { | |
| 26 | + const body = await request.json(); | |
| 27 | + const { name, folderId } = body; | |
| 28 | + | |
| 29 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 30 | + if (!file) { | |
| 31 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 32 | + } | |
| 33 | + | |
| 34 | + const updateData: Record<string, unknown> = {}; | |
| 35 | + | |
| 36 | + if (name !== undefined) { | |
| 37 | + updateData.name = name; | |
| 38 | + await logActivity("RENAME", params.id, `Renamed to ${name}`); | |
| 39 | + } | |
| 40 | + | |
| 41 | + if (folderId !== undefined) { | |
| 42 | + updateData.folderId = folderId === "root" ? null : folderId; | |
| 43 | + await logActivity("MOVE", params.id, `Moved to folder ${folderId}`); | |
| 44 | + } | |
| 45 | + | |
| 46 | + const updated = await prisma.file.update({ | |
| 47 | + where: { id: params.id }, | |
| 48 | + data: updateData, | |
| 49 | + }); | |
| 50 | + | |
| 51 | + return NextResponse.json({ ...updated, size: Number(updated.size) }); | |
| 52 | +} | |
| 53 | + | |
| 54 | +export async function DELETE( | |
| 55 | + request: NextRequest, | |
| 56 | + { params }: { params: { id: string } } | |
| 57 | +) { | |
| 58 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 59 | + if (!file) { | |
| 60 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 61 | + } | |
| 62 | + | |
| 63 | + await deleteFile(file.storagePath); | |
| 64 | + await prisma.file.delete({ where: { id: params.id } }); | |
| 65 | + await logActivity("DELETE", null, `Deleted ${file.name}`); | |
| 66 | + | |
| 67 | + return NextResponse.json({ success: true }); | |
| 68 | +} | |
added
app/api/files/[id]/save-edit/route.ts
+78 −0
@@ -0,0 +1,78 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { saveFile, saveVersion } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | +import { MAX_VERSIONS_PER_FILE } from "@/lib/config"; | |
| 6 | + | |
| 7 | +export async function POST( | |
| 8 | + request: NextRequest, | |
| 9 | + { params }: { params: { id: string } } | |
| 10 | +) { | |
| 11 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 12 | + | |
| 13 | + if (!file) { | |
| 14 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 15 | + } | |
| 16 | + | |
| 17 | + const formData = await request.formData(); | |
| 18 | + const blob = formData.get("file") as File; | |
| 19 | + const asNewVersion = formData.get("asNewVersion") === "true"; | |
| 20 | + | |
| 21 | + if (!blob) { | |
| 22 | + return NextResponse.json({ error: "No file provided" }, { status: 400 }); | |
| 23 | + } | |
| 24 | + | |
| 25 | + const buffer = Buffer.from(await blob.arrayBuffer()); | |
| 26 | + | |
| 27 | + if (asNewVersion) { | |
| 28 | + // Save current as version | |
| 29 | + const versionCount = await prisma.fileVersion.count({ where: { fileId: file.id } }); | |
| 30 | + const versionResult = await saveVersion(file.storagePath, file.name); | |
| 31 | + | |
| 32 | + await prisma.fileVersion.create({ | |
| 33 | + data: { | |
| 34 | + fileId: file.id, | |
| 35 | + versionNum: versionCount + 1, | |
| 36 | + storagePath: versionResult.storagePath, | |
| 37 | + size: BigInt(versionResult.size), | |
| 38 | + changeNote: "Before edit", | |
| 39 | + }, | |
| 40 | + }); | |
| 41 | + | |
| 42 | + // Cleanup old versions | |
| 43 | + const versions = await prisma.fileVersion.findMany({ | |
| 44 | + where: { fileId: file.id }, | |
| 45 | + orderBy: { versionNum: "asc" }, | |
| 46 | + }); | |
| 47 | + if (versions.length > MAX_VERSIONS_PER_FILE) { | |
| 48 | + const toDelete = versions.slice(0, versions.length - MAX_VERSIONS_PER_FILE); | |
| 49 | + for (const v of toDelete) { | |
| 50 | + const { deleteVersion } = await import("@/lib/storage"); | |
| 51 | + await deleteVersion(v.storagePath); | |
| 52 | + await prisma.fileVersion.delete({ where: { id: v.id } }); | |
| 53 | + } | |
| 54 | + } | |
| 55 | + } | |
| 56 | + | |
| 57 | + // Save edited file | |
| 58 | + const result = await saveFile(buffer, file.name); | |
| 59 | + | |
| 60 | + // Delete old file and update record | |
| 61 | + const { deleteFile } = await import("@/lib/storage"); | |
| 62 | + await deleteFile(file.storagePath); | |
| 63 | + | |
| 64 | + const updated = await prisma.file.update({ | |
| 65 | + where: { id: params.id }, | |
| 66 | + data: { | |
| 67 | + storagePath: result.storagePath, | |
| 68 | + size: BigInt(result.size), | |
| 69 | + }, | |
| 70 | + }); | |
| 71 | + | |
| 72 | + await logActivity("EDIT", file.id, `Edited ${file.name}`); | |
| 73 | + | |
| 74 | + return NextResponse.json({ | |
| 75 | + ...updated, | |
| 76 | + size: Number(updated.size), | |
| 77 | + }); | |
| 78 | +} | |
added
app/api/files/[id]/stream/route.ts
+69 −0
@@ -0,0 +1,69 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath } from "@/lib/storage"; | |
| 4 | +import { stat } from "fs/promises"; | |
| 5 | +import { createReadStream } from "fs"; | |
| 6 | + | |
| 7 | +export async function GET( | |
| 8 | + request: NextRequest, | |
| 9 | + { params }: { params: { id: string } } | |
| 10 | +) { | |
| 11 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 12 | + | |
| 13 | + if (!file) { | |
| 14 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 15 | + } | |
| 16 | + | |
| 17 | + const filePath = getFilePath(file.storagePath); | |
| 18 | + const fileStat = await stat(filePath); | |
| 19 | + const fileSize = fileStat.size; | |
| 20 | + | |
| 21 | + const range = request.headers.get("range"); | |
| 22 | + | |
| 23 | + if (range) { | |
| 24 | + const parts = range.replace(/bytes=/, "").split("-"); | |
| 25 | + const start = parseInt(parts[0], 10); | |
| 26 | + const end = parts[1] | |
| 27 | + ? parseInt(parts[1], 10) | |
| 28 | + : Math.min(start + 1024 * 1024 - 1, fileSize - 1); | |
| 29 | + const chunkSize = end - start + 1; | |
| 30 | + | |
| 31 | + const stream = createReadStream(filePath, { start, end }); | |
| 32 | + const webStream = new ReadableStream({ | |
| 33 | + start(controller) { | |
| 34 | + stream.on("data", (chunk) => controller.enqueue(chunk)); | |
| 35 | + stream.on("end", () => controller.close()); | |
| 36 | + stream.on("error", (err) => controller.error(err)); | |
| 37 | + }, | |
| 38 | + }); | |
| 39 | + | |
| 40 | + return new Response(webStream, { | |
| 41 | + status: 206, | |
| 42 | + headers: { | |
| 43 | + "Content-Range": `bytes ${start}-${end}/${fileSize}`, | |
| 44 | + "Accept-Ranges": "bytes", | |
| 45 | + "Content-Length": String(chunkSize), | |
| 46 | + "Content-Type": file.mimeType, | |
| 47 | + "Cache-Control": "private, max-age=3600", | |
| 48 | + }, | |
| 49 | + }); | |
| 50 | + } | |
| 51 | + | |
| 52 | + const stream = createReadStream(filePath); | |
| 53 | + const webStream = new ReadableStream({ | |
| 54 | + start(controller) { | |
| 55 | + stream.on("data", (chunk) => controller.enqueue(chunk)); | |
| 56 | + stream.on("end", () => controller.close()); | |
| 57 | + stream.on("error", (err) => controller.error(err)); | |
| 58 | + }, | |
| 59 | + }); | |
| 60 | + | |
| 61 | + return new Response(webStream, { | |
| 62 | + headers: { | |
| 63 | + "Content-Length": String(fileSize), | |
| 64 | + "Content-Type": file.mimeType, | |
| 65 | + "Accept-Ranges": "bytes", | |
| 66 | + "Cache-Control": "private, max-age=3600", | |
| 67 | + }, | |
| 68 | + }); | |
| 69 | +} | |
added
app/api/files/[id]/tags/[tagId]/route.ts
+22 −0
@@ -0,0 +1,22 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | + | |
| 5 | +export async function DELETE( | |
| 6 | + request: NextRequest, | |
| 7 | + { params }: { params: { id: string; tagId: string } } | |
| 8 | +) { | |
| 9 | + const fileTag = await prisma.fileTag.findFirst({ | |
| 10 | + where: { fileId: params.id, tagId: params.tagId }, | |
| 11 | + include: { tag: true }, | |
| 12 | + }); | |
| 13 | + | |
| 14 | + if (!fileTag) { | |
| 15 | + return NextResponse.json({ error: "Tag not found" }, { status: 404 }); | |
| 16 | + } | |
| 17 | + | |
| 18 | + await prisma.fileTag.delete({ where: { id: fileTag.id } }); | |
| 19 | + await logActivity("TAG_REMOVE", params.id, `Tag removed: ${fileTag.tag.name}`); | |
| 20 | + | |
| 21 | + return NextResponse.json({ success: true }); | |
| 22 | +} | |
added
app/api/files/[id]/tags/route.ts
+33 −0
@@ -0,0 +1,33 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | + | |
| 5 | +export async function POST( | |
| 6 | + request: NextRequest, | |
| 7 | + { params }: { params: { id: string } } | |
| 8 | +) { | |
| 9 | + const { tagId } = await request.json(); | |
| 10 | + | |
| 11 | + if (!tagId) { | |
| 12 | + return NextResponse.json({ error: "tagId required" }, { status: 400 }); | |
| 13 | + } | |
| 14 | + | |
| 15 | + const fileTag = await prisma.fileTag.create({ | |
| 16 | + data: { fileId: params.id, tagId }, | |
| 17 | + include: { tag: true }, | |
| 18 | + }); | |
| 19 | + | |
| 20 | + await logActivity("TAG_ADD", params.id, `Tag added: ${fileTag.tag.name}`); | |
| 21 | + return NextResponse.json(fileTag, { status: 201 }); | |
| 22 | +} | |
| 23 | + | |
| 24 | +export async function GET( | |
| 25 | + request: NextRequest, | |
| 26 | + { params }: { params: { id: string } } | |
| 27 | +) { | |
| 28 | + const tags = await prisma.fileTag.findMany({ | |
| 29 | + where: { fileId: params.id }, | |
| 30 | + include: { tag: true }, | |
| 31 | + }); | |
| 32 | + return NextResponse.json(tags); | |
| 33 | +} | |
added
app/api/files/[id]/versions/[versionId]/diff/route.ts
+41 −0
@@ -0,0 +1,41 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath, getVersionPath } from "@/lib/storage"; | |
| 4 | +import { readFile } from "fs/promises"; | |
| 5 | + | |
| 6 | +export async function GET( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { id: string; versionId: string } } | |
| 9 | +) { | |
| 10 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 11 | + const version = await prisma.fileVersion.findUnique({ | |
| 12 | + where: { id: params.versionId }, | |
| 13 | + }); | |
| 14 | + | |
| 15 | + if (!file || !version || version.fileId !== params.id) { | |
| 16 | + return NextResponse.json({ error: "Not found" }, { status: 404 }); | |
| 17 | + } | |
| 18 | + | |
| 19 | + // Only text files can be diffed | |
| 20 | + if (!file.mimeType.startsWith("text/") && !file.mimeType.includes("json") && !file.mimeType.includes("xml") && !file.mimeType.includes("javascript") && !file.mimeType.includes("typescript")) { | |
| 21 | + return NextResponse.json({ error: "Not a text file" }, { status: 400 }); | |
| 22 | + } | |
| 23 | + | |
| 24 | + const currentContent = await readFile(getFilePath(file.storagePath), "utf-8"); | |
| 25 | + const versionContent = await readFile(getVersionPath(version.storagePath), "utf-8"); | |
| 26 | + | |
| 27 | + const Diff = await import("diff"); | |
| 28 | + const patch = Diff.createPatch( | |
| 29 | + file.name, | |
| 30 | + versionContent, | |
| 31 | + currentContent, | |
| 32 | + `Version ${version.versionNum}`, | |
| 33 | + "Current" | |
| 34 | + ); | |
| 35 | + | |
| 36 | + return NextResponse.json({ | |
| 37 | + current: currentContent, | |
| 38 | + version: versionContent, | |
| 39 | + diff: patch, | |
| 40 | + }); | |
| 41 | +} | |
added
app/api/files/[id]/versions/[versionId]/restore/route.ts
+45 −0
@@ -0,0 +1,45 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { saveVersion, restoreVersion } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | + | |
| 6 | +export async function POST( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { id: string; versionId: string } } | |
| 9 | +) { | |
| 10 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 11 | + const version = await prisma.fileVersion.findUnique({ | |
| 12 | + where: { id: params.versionId }, | |
| 13 | + }); | |
| 14 | + | |
| 15 | + if (!file || !version || version.fileId !== params.id) { | |
| 16 | + return NextResponse.json({ error: "Not found" }, { status: 404 }); | |
| 17 | + } | |
| 18 | + | |
| 19 | + // Save current as new version first | |
| 20 | + const versionCount = await prisma.fileVersion.count({ where: { fileId: file.id } }); | |
| 21 | + const currentVersion = await saveVersion(file.storagePath, file.name); | |
| 22 | + | |
| 23 | + await prisma.fileVersion.create({ | |
| 24 | + data: { | |
| 25 | + fileId: file.id, | |
| 26 | + versionNum: versionCount + 1, | |
| 27 | + storagePath: currentVersion.storagePath, | |
| 28 | + size: BigInt(currentVersion.size), | |
| 29 | + changeNote: "Before restore", | |
| 30 | + }, | |
| 31 | + }); | |
| 32 | + | |
| 33 | + // Restore the selected version | |
| 34 | + await restoreVersion(version.storagePath, file.storagePath); | |
| 35 | + | |
| 36 | + // Update file size | |
| 37 | + await prisma.file.update({ | |
| 38 | + where: { id: params.id }, | |
| 39 | + data: { size: version.size }, | |
| 40 | + }); | |
| 41 | + | |
| 42 | + await logActivity("VERSION_RESTORE", file.id, `Restored to version ${version.versionNum}`); | |
| 43 | + | |
| 44 | + return NextResponse.json({ success: true }); | |
| 45 | +} | |
added
app/api/files/[id]/versions/[versionId]/route.ts
+39 −0
@@ -0,0 +1,39 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getVersionPath } from "@/lib/storage"; | |
| 4 | +import { createReadStream } from "fs"; | |
| 5 | +import { stat } from "fs/promises"; | |
| 6 | + | |
| 7 | +export async function GET( | |
| 8 | + request: NextRequest, | |
| 9 | + { params }: { params: { id: string; versionId: string } } | |
| 10 | +) { | |
| 11 | + const version = await prisma.fileVersion.findUnique({ | |
| 12 | + where: { id: params.versionId }, | |
| 13 | + include: { file: true }, | |
| 14 | + }); | |
| 15 | + | |
| 16 | + if (!version || version.fileId !== params.id) { | |
| 17 | + return NextResponse.json({ error: "Version not found" }, { status: 404 }); | |
| 18 | + } | |
| 19 | + | |
| 20 | + const filePath = getVersionPath(version.storagePath); | |
| 21 | + const fileStat = await stat(filePath); | |
| 22 | + | |
| 23 | + const stream = createReadStream(filePath); | |
| 24 | + const webStream = new ReadableStream({ | |
| 25 | + start(controller) { | |
| 26 | + stream.on("data", (chunk) => controller.enqueue(chunk)); | |
| 27 | + stream.on("end", () => controller.close()); | |
| 28 | + stream.on("error", (err) => controller.error(err)); | |
| 29 | + }, | |
| 30 | + }); | |
| 31 | + | |
| 32 | + return new Response(webStream, { | |
| 33 | + headers: { | |
| 34 | + "Content-Type": "application/octet-stream", | |
| 35 | + "Content-Disposition": `attachment; filename="${encodeURIComponent(version.file.name)}"`, | |
| 36 | + "Content-Length": String(fileStat.size), | |
| 37 | + }, | |
| 38 | + }); | |
| 39 | +} | |
added
app/api/files/[id]/versions/route.ts
+49 −0
@@ -0,0 +1,49 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { saveVersion } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | + | |
| 6 | +export async function GET( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { id: string } } | |
| 9 | +) { | |
| 10 | + const versions = await prisma.fileVersion.findMany({ | |
| 11 | + where: { fileId: params.id }, | |
| 12 | + orderBy: { versionNum: "desc" }, | |
| 13 | + }); | |
| 14 | + | |
| 15 | + return NextResponse.json( | |
| 16 | + versions.map((v) => ({ ...v, size: Number(v.size) })) | |
| 17 | + ); | |
| 18 | +} | |
| 19 | + | |
| 20 | +export async function POST( | |
| 21 | + request: NextRequest, | |
| 22 | + { params }: { params: { id: string } } | |
| 23 | +) { | |
| 24 | + const file = await prisma.file.findUnique({ where: { id: params.id } }); | |
| 25 | + | |
| 26 | + if (!file) { | |
| 27 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 28 | + } | |
| 29 | + | |
| 30 | + const body = await request.json().catch(() => ({})); | |
| 31 | + const changeNote = body.changeNote || "Manual snapshot"; | |
| 32 | + | |
| 33 | + const versionCount = await prisma.fileVersion.count({ where: { fileId: file.id } }); | |
| 34 | + const result = await saveVersion(file.storagePath, file.name); | |
| 35 | + | |
| 36 | + const version = await prisma.fileVersion.create({ | |
| 37 | + data: { | |
| 38 | + fileId: file.id, | |
| 39 | + versionNum: versionCount + 1, | |
| 40 | + storagePath: result.storagePath, | |
| 41 | + size: BigInt(result.size), | |
| 42 | + changeNote, | |
| 43 | + }, | |
| 44 | + }); | |
| 45 | + | |
| 46 | + await logActivity("VERSION_CREATE", file.id, `Version ${version.versionNum} created`); | |
| 47 | + | |
| 48 | + return NextResponse.json({ ...version, size: Number(version.size) }, { status: 201 }); | |
| 49 | +} | |
added
app/api/files/bulk/route.ts
+74 −0
@@ -0,0 +1,74 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | +import { deleteFile, getFilePath } from "@/lib/storage"; | |
| 6 | +import { logActivity } from "@/lib/activity"; | |
| 7 | +import { encryptFile, removeEncryption } from "@/lib/encryption"; | |
| 8 | +import JSZip from "jszip"; | |
| 9 | +import { readFileSync, existsSync } from "fs"; | |
| 10 | + | |
| 11 | +export async function POST(request: NextRequest) { | |
| 12 | + const response = NextResponse.next(); | |
| 13 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 14 | + if (!session.isLoggedIn) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 15 | + | |
| 16 | + const body = await request.json(); | |
| 17 | + const { action, fileIds } = body as { action: string; fileIds: string[] }; | |
| 18 | + | |
| 19 | + if (!action || !fileIds?.length) return NextResponse.json({ error: "action and fileIds required" }, { status: 400 }); | |
| 20 | + | |
| 21 | + if (action === "delete") { | |
| 22 | + const files = await prisma.file.findMany({ where: { id: { in: fileIds } } }); | |
| 23 | + for (const file of files) { | |
| 24 | + await deleteFile(file.storagePath); | |
| 25 | + await prisma.file.delete({ where: { id: file.id } }); | |
| 26 | + await logActivity("DELETE", file.id, `Bulk delete: ${file.name}`); | |
| 27 | + } | |
| 28 | + return NextResponse.json({ success: true, count: files.length }); | |
| 29 | + } | |
| 30 | + | |
| 31 | + if (action === "move") { | |
| 32 | + const { targetFolderId } = body; | |
| 33 | + await prisma.file.updateMany({ | |
| 34 | + where: { id: { in: fileIds } }, | |
| 35 | + data: { folderId: targetFolderId === "root" ? null : targetFolderId }, | |
| 36 | + }); | |
| 37 | + return NextResponse.json({ success: true }); | |
| 38 | + } | |
| 39 | + | |
| 40 | + if (action === "download") { | |
| 41 | + const files = await prisma.file.findMany({ where: { id: { in: fileIds } } }); | |
| 42 | + const zip = new JSZip(); | |
| 43 | + for (const file of files) { | |
| 44 | + const filePath = getFilePath(file.storagePath); | |
| 45 | + if (existsSync(filePath)) { | |
| 46 | + zip.file(file.name, readFileSync(filePath)); | |
| 47 | + } | |
| 48 | + } | |
| 49 | + const buffer = await zip.generateAsync({ type: "nodebuffer" }); | |
| 50 | + return new NextResponse(buffer, { | |
| 51 | + headers: { "Content-Type": "application/zip", "Content-Disposition": `attachment; filename="files.zip"` }, | |
| 52 | + }); | |
| 53 | + } | |
| 54 | + | |
| 55 | + if (action === "encrypt") { | |
| 56 | + let count = 0; | |
| 57 | + for (const id of fileIds) { | |
| 58 | + const ok = await encryptFile(id); | |
| 59 | + if (ok) count++; | |
| 60 | + } | |
| 61 | + return NextResponse.json({ success: true, count }); | |
| 62 | + } | |
| 63 | + | |
| 64 | + if (action === "decrypt") { | |
| 65 | + let count = 0; | |
| 66 | + for (const id of fileIds) { | |
| 67 | + const ok = await removeEncryption(id); | |
| 68 | + if (ok) count++; | |
| 69 | + } | |
| 70 | + return NextResponse.json({ success: true, count }); | |
| 71 | + } | |
| 72 | + | |
| 73 | + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); | |
| 74 | +} | |
added
app/api/files/route.ts
+111 −0
@@ -0,0 +1,111 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { saveFile } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | +import mime from "mime-types"; | |
| 6 | + | |
| 7 | +export async function GET(request: NextRequest) { | |
| 8 | + const { searchParams } = new URL(request.url); | |
| 9 | + const folderId = searchParams.get("folderId"); | |
| 10 | + const search = searchParams.get("search"); | |
| 11 | + const type = searchParams.get("type"); | |
| 12 | + const sortBy = searchParams.get("sortBy") || "createdAt"; | |
| 13 | + const order = searchParams.get("order") || "desc"; | |
| 14 | + const dateFrom = searchParams.get("dateFrom"); | |
| 15 | + const dateTo = searchParams.get("dateTo"); | |
| 16 | + const sizeMin = searchParams.get("sizeMin"); | |
| 17 | + const sizeMax = searchParams.get("sizeMax"); | |
| 18 | + | |
| 19 | + const favorite = searchParams.get("favorite"); | |
| 20 | + const tagId = searchParams.get("tagId"); | |
| 21 | + | |
| 22 | + const where: Record<string, unknown> = {}; | |
| 23 | + | |
| 24 | + // When filtering by favorite or tag, don't filter by folder | |
| 25 | + if (favorite === "true") { | |
| 26 | + where.metadata = { isFavorite: true }; | |
| 27 | + } else if (tagId) { | |
| 28 | + where.tags = { some: { tagId } }; | |
| 29 | + } else if (folderId === "root" || folderId === null) { | |
| 30 | + where.folderId = null; | |
| 31 | + } else if (folderId) { | |
| 32 | + where.folderId = folderId; | |
| 33 | + } | |
| 34 | + | |
| 35 | + if (search) { | |
| 36 | + where.name = { contains: search, mode: "insensitive" }; | |
| 37 | + } | |
| 38 | + | |
| 39 | + if (type) { | |
| 40 | + where.mimeType = { startsWith: type }; | |
| 41 | + } | |
| 42 | + | |
| 43 | + if (dateFrom || dateTo) { | |
| 44 | + where.createdAt = {}; | |
| 45 | + if (dateFrom) (where.createdAt as Record<string, unknown>).gte = new Date(dateFrom); | |
| 46 | + if (dateTo) (where.createdAt as Record<string, unknown>).lte = new Date(dateTo); | |
| 47 | + } | |
| 48 | + | |
| 49 | + if (sizeMin || sizeMax) { | |
| 50 | + where.size = {}; | |
| 51 | + if (sizeMin) (where.size as Record<string, unknown>).gte = BigInt(sizeMin); | |
| 52 | + if (sizeMax) (where.size as Record<string, unknown>).lte = BigInt(sizeMax); | |
| 53 | + } | |
| 54 | + | |
| 55 | + const files = await prisma.file.findMany({ | |
| 56 | + where, | |
| 57 | + orderBy: { [sortBy]: order }, | |
| 58 | + include: { | |
| 59 | + folder: true, | |
| 60 | + metadata: true, | |
| 61 | + tags: { include: { tag: true } }, | |
| 62 | + _count: { select: { versions: true } }, | |
| 63 | + }, | |
| 64 | + }); | |
| 65 | + | |
| 66 | + const serialized = files.map((f) => ({ | |
| 67 | + ...f, | |
| 68 | + size: Number(f.size), | |
| 69 | + })); | |
| 70 | + | |
| 71 | + return NextResponse.json(serialized); | |
| 72 | +} | |
| 73 | + | |
| 74 | +export async function POST(request: NextRequest) { | |
| 75 | + const formData = await request.formData(); | |
| 76 | + const file = formData.get("file") as File | null; | |
| 77 | + const folderId = formData.get("folderId") as string | null; | |
| 78 | + | |
| 79 | + if (!file) { | |
| 80 | + return NextResponse.json({ error: "No file provided" }, { status: 400 }); | |
| 81 | + } | |
| 82 | + | |
| 83 | + const maxSize = parseInt(process.env.MAX_FILE_SIZE || "104857600"); | |
| 84 | + if (file.size > maxSize) { | |
| 85 | + return NextResponse.json( | |
| 86 | + { error: `File too large. Max ${Math.round(maxSize / 1024 / 1024)}MB` }, | |
| 87 | + { status: 413 } | |
| 88 | + ); | |
| 89 | + } | |
| 90 | + | |
| 91 | + const buffer = Buffer.from(await file.arrayBuffer()); | |
| 92 | + const mimeType = mime.lookup(file.name) || file.type || "application/octet-stream"; | |
| 93 | + const { storagePath, size } = await saveFile(buffer, file.name); | |
| 94 | + | |
| 95 | + const dbFile = await prisma.file.create({ | |
| 96 | + data: { | |
| 97 | + name: file.name, | |
| 98 | + storagePath, | |
| 99 | + size: BigInt(size), | |
| 100 | + mimeType, | |
| 101 | + folderId: folderId && folderId !== "root" ? folderId : null, | |
| 102 | + }, | |
| 103 | + }); | |
| 104 | + | |
| 105 | + await logActivity("UPLOAD", dbFile.id, `Uploaded ${file.name}`); | |
| 106 | + | |
| 107 | + return NextResponse.json( | |
| 108 | + { ...dbFile, size: Number(dbFile.size) }, | |
| 109 | + { status: 201 } | |
| 110 | + ); | |
| 111 | +} | |
added
app/api/files/upload-chunk/route.ts
+47 −0
@@ -0,0 +1,47 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { saveChunk, assembleChunks } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | +import mime from "mime-types"; | |
| 6 | + | |
| 7 | +export async function POST(request: NextRequest) { | |
| 8 | + const formData = await request.formData(); | |
| 9 | + const chunk = formData.get("chunk") as File | null; | |
| 10 | + const uploadId = formData.get("uploadId") as string; | |
| 11 | + const chunkIndex = parseInt(formData.get("chunkIndex") as string); | |
| 12 | + const totalChunks = parseInt(formData.get("totalChunks") as string); | |
| 13 | + const fileName = formData.get("fileName") as string; | |
| 14 | + const folderId = formData.get("folderId") as string | null; | |
| 15 | + | |
| 16 | + if (!chunk || !uploadId || isNaN(chunkIndex) || isNaN(totalChunks) || !fileName) { | |
| 17 | + return NextResponse.json({ error: "Missing chunk data" }, { status: 400 }); | |
| 18 | + } | |
| 19 | + | |
| 20 | + const buffer = Buffer.from(await chunk.arrayBuffer()); | |
| 21 | + await saveChunk(buffer, uploadId, chunkIndex); | |
| 22 | + | |
| 23 | + // If this is the last chunk, assemble | |
| 24 | + if (chunkIndex === totalChunks - 1) { | |
| 25 | + const { storagePath, size } = await assembleChunks(uploadId, totalChunks, fileName); | |
| 26 | + const mimeType = mime.lookup(fileName) || "application/octet-stream"; | |
| 27 | + | |
| 28 | + const dbFile = await prisma.file.create({ | |
| 29 | + data: { | |
| 30 | + name: fileName, | |
| 31 | + storagePath, | |
| 32 | + size: BigInt(size), | |
| 33 | + mimeType, | |
| 34 | + folderId: folderId && folderId !== "root" ? folderId : null, | |
| 35 | + }, | |
| 36 | + }); | |
| 37 | + | |
| 38 | + await logActivity("UPLOAD", dbFile.id, `Uploaded ${fileName} (chunked)`); | |
| 39 | + | |
| 40 | + return NextResponse.json( | |
| 41 | + { ...dbFile, size: Number(dbFile.size), complete: true }, | |
| 42 | + { status: 201 } | |
| 43 | + ); | |
| 44 | + } | |
| 45 | + | |
| 46 | + return NextResponse.json({ chunkIndex, received: true }); | |
| 47 | +} | |
added
app/api/folders/[id]/download/route.ts
+30 −0
@@ -0,0 +1,30 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath } from "@/lib/storage"; | |
| 4 | +import JSZip from "jszip"; | |
| 5 | +import { readFileSync, existsSync } from "fs"; | |
| 6 | + | |
| 7 | +async function addFolderToZip(zip: JSZip, folderId: string, prefix: string) { | |
| 8 | + const files = await prisma.file.findMany({ where: { folderId } }); | |
| 9 | + for (const file of files) { | |
| 10 | + const filePath = getFilePath(file.storagePath); | |
| 11 | + if (existsSync(filePath)) zip.file(prefix + file.name, readFileSync(filePath)); | |
| 12 | + } | |
| 13 | + const subfolders = await prisma.folder.findMany({ where: { parentId: folderId } }); | |
| 14 | + for (const sub of subfolders) { | |
| 15 | + await addFolderToZip(zip, sub.id, `${prefix}${sub.name}/`); | |
| 16 | + } | |
| 17 | +} | |
| 18 | + | |
| 19 | +export async function GET(_request: NextRequest, { params }: { params: { id: string } }) { | |
| 20 | + const folder = await prisma.folder.findUnique({ where: { id: params.id } }); | |
| 21 | + if (!folder) return NextResponse.json({ error: "Not found" }, { status: 404 }); | |
| 22 | + | |
| 23 | + const zip = new JSZip(); | |
| 24 | + await addFolderToZip(zip, folder.id, ""); | |
| 25 | + const buffer = await zip.generateAsync({ type: "nodebuffer" }); | |
| 26 | + | |
| 27 | + return new NextResponse(buffer, { | |
| 28 | + headers: { "Content-Type": "application/zip", "Content-Disposition": `attachment; filename="${folder.name}.zip"` }, | |
| 29 | + }); | |
| 30 | +} | |
added
app/api/folders/[id]/route.ts
+83 −0
@@ -0,0 +1,83 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { deleteFile } from "@/lib/storage"; | |
| 4 | +import { logActivity } from "@/lib/activity"; | |
| 5 | + | |
| 6 | +export async function GET( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { id: string } } | |
| 9 | +) { | |
| 10 | + const folder = await prisma.folder.findUnique({ | |
| 11 | + where: { id: params.id }, | |
| 12 | + include: { | |
| 13 | + children: { | |
| 14 | + include: { _count: { select: { files: true, children: true } } }, | |
| 15 | + orderBy: { name: "asc" }, | |
| 16 | + }, | |
| 17 | + files: { orderBy: { createdAt: "desc" } }, | |
| 18 | + parent: true, | |
| 19 | + }, | |
| 20 | + }); | |
| 21 | + | |
| 22 | + if (!folder) { | |
| 23 | + return NextResponse.json({ error: "Folder not found" }, { status: 404 }); | |
| 24 | + } | |
| 25 | + | |
| 26 | + return NextResponse.json({ | |
| 27 | + ...folder, | |
| 28 | + files: folder.files.map((f) => ({ ...f, size: Number(f.size) })), | |
| 29 | + }); | |
| 30 | +} | |
| 31 | + | |
| 32 | +export async function PATCH( | |
| 33 | + request: NextRequest, | |
| 34 | + { params }: { params: { id: string } } | |
| 35 | +) { | |
| 36 | + const body = await request.json(); | |
| 37 | + const { name } = body; | |
| 38 | + | |
| 39 | + if (!name || !name.trim()) { | |
| 40 | + return NextResponse.json({ error: "Name required" }, { status: 400 }); | |
| 41 | + } | |
| 42 | + | |
| 43 | + const folder = await prisma.folder.update({ | |
| 44 | + where: { id: params.id }, | |
| 45 | + data: { name: name.trim() }, | |
| 46 | + }); | |
| 47 | + | |
| 48 | + return NextResponse.json(folder); | |
| 49 | +} | |
| 50 | + | |
| 51 | +export async function DELETE( | |
| 52 | + request: NextRequest, | |
| 53 | + { params }: { params: { id: string } } | |
| 54 | +) { | |
| 55 | + // Recursively delete all files in this folder and subfolders | |
| 56 | + async function deleteRecursive(folderId: string) { | |
| 57 | + const folder = await prisma.folder.findUnique({ | |
| 58 | + where: { id: folderId }, | |
| 59 | + include: { files: true, children: true }, | |
| 60 | + }); | |
| 61 | + | |
| 62 | + if (!folder) return; | |
| 63 | + | |
| 64 | + // Delete files | |
| 65 | + for (const file of folder.files) { | |
| 66 | + await deleteFile(file.storagePath); | |
| 67 | + } | |
| 68 | + | |
| 69 | + // Recurse into children | |
| 70 | + for (const child of folder.children) { | |
| 71 | + await deleteRecursive(child.id); | |
| 72 | + } | |
| 73 | + } | |
| 74 | + | |
| 75 | + await deleteRecursive(params.id); | |
| 76 | + | |
| 77 | + // Prisma cascade will handle DB records | |
| 78 | + await prisma.folder.delete({ where: { id: params.id } }); | |
| 79 | + | |
| 80 | + await logActivity("DELETE_FOLDER", null, `Deleted folder ${params.id}`); | |
| 81 | + | |
| 82 | + return NextResponse.json({ success: true }); | |
| 83 | +} | |
added
app/api/folders/route.ts
+64 −0
@@ -0,0 +1,64 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | + | |
| 5 | +export async function GET(request: NextRequest) { | |
| 6 | + const { searchParams } = new URL(request.url); | |
| 7 | + const parentId = searchParams.get("parentId"); | |
| 8 | + const tree = searchParams.get("tree"); | |
| 9 | + | |
| 10 | + if (tree === "true") { | |
| 11 | + const folders = await prisma.folder.findMany({ | |
| 12 | + include: { | |
| 13 | + children: { | |
| 14 | + include: { | |
| 15 | + children: { | |
| 16 | + include: { children: true }, | |
| 17 | + }, | |
| 18 | + }, | |
| 19 | + }, | |
| 20 | + _count: { select: { files: true } }, | |
| 21 | + }, | |
| 22 | + where: { parentId: null }, | |
| 23 | + orderBy: { name: "asc" }, | |
| 24 | + }); | |
| 25 | + return NextResponse.json(folders); | |
| 26 | + } | |
| 27 | + | |
| 28 | + const where: Record<string, unknown> = {}; | |
| 29 | + if (parentId === "root" || parentId === null) { | |
| 30 | + where.parentId = null; | |
| 31 | + } else { | |
| 32 | + where.parentId = parentId; | |
| 33 | + } | |
| 34 | + | |
| 35 | + const folders = await prisma.folder.findMany({ | |
| 36 | + where, | |
| 37 | + include: { | |
| 38 | + _count: { select: { files: true, children: true } }, | |
| 39 | + }, | |
| 40 | + orderBy: { name: "asc" }, | |
| 41 | + }); | |
| 42 | + | |
| 43 | + return NextResponse.json(folders); | |
| 44 | +} | |
| 45 | + | |
| 46 | +export async function POST(request: NextRequest) { | |
| 47 | + const body = await request.json(); | |
| 48 | + const { name, parentId } = body; | |
| 49 | + | |
| 50 | + if (!name || !name.trim()) { | |
| 51 | + return NextResponse.json({ error: "Folder name required" }, { status: 400 }); | |
| 52 | + } | |
| 53 | + | |
| 54 | + const folder = await prisma.folder.create({ | |
| 55 | + data: { | |
| 56 | + name: name.trim(), | |
| 57 | + parentId: parentId && parentId !== "root" ? parentId : null, | |
| 58 | + }, | |
| 59 | + }); | |
| 60 | + | |
| 61 | + await logActivity("CREATE_FOLDER", null, `Created folder ${name}`); | |
| 62 | + | |
| 63 | + return NextResponse.json(folder, { status: 201 }); | |
| 64 | +} | |
added
app/api/keys/[id]/route.ts
+13 −0
@@ -0,0 +1,13 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | + | |
| 6 | +export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + await prisma.apiKey.delete({ where: { id: params.id, userId: session.userId } }); | |
| 12 | + return NextResponse.json({ success: true }); | |
| 13 | +} | |
added
app/api/keys/route.ts
+37 −0
@@ -0,0 +1,37 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | +import { randomBytes, createHash } from "crypto"; | |
| 6 | + | |
| 7 | +export async function GET(request: NextRequest) { | |
| 8 | + const response = NextResponse.next(); | |
| 9 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 10 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 11 | + | |
| 12 | + const keys = await prisma.apiKey.findMany({ | |
| 13 | + where: { userId: session.userId }, | |
| 14 | + select: { id: true, name: true, keyPrefix: true, permissions: true, lastUsedAt: true, expiresAt: true, createdAt: true }, | |
| 15 | + orderBy: { createdAt: "desc" }, | |
| 16 | + }); | |
| 17 | + return NextResponse.json(keys); | |
| 18 | +} | |
| 19 | + | |
| 20 | +export async function POST(request: NextRequest) { | |
| 21 | + const response = NextResponse.next(); | |
| 22 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 23 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 24 | + | |
| 25 | + const { name, permissions, expiresAt } = await request.json(); | |
| 26 | + if (!name) return NextResponse.json({ error: "Name required" }, { status: 400 }); | |
| 27 | + | |
| 28 | + const rawKey = `spb_${randomBytes(32).toString("hex")}`; | |
| 29 | + const keyHash = createHash("sha256").update(rawKey).digest("hex"); | |
| 30 | + const keyPrefix = rawKey.substring(0, 12); | |
| 31 | + | |
| 32 | + await prisma.apiKey.create({ | |
| 33 | + data: { userId: session.userId, name, keyHash, keyPrefix, permissions: permissions || "read", expiresAt: expiresAt ? new Date(expiresAt) : null }, | |
| 34 | + }); | |
| 35 | + | |
| 36 | + return NextResponse.json({ key: rawKey, prefix: keyPrefix }, { status: 201 }); | |
| 37 | +} | |
added
app/api/notifications/[id]/route.ts
+7 −0
@@ -0,0 +1,7 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { markAsRead } from "@/lib/notifications"; | |
| 3 | + | |
| 4 | +export async function PATCH(_request: NextRequest, { params }: { params: { id: string } }) { | |
| 5 | + await markAsRead(params.id); | |
| 6 | + return NextResponse.json({ success: true }); | |
| 7 | +} | |
added
app/api/notifications/route.ts
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { getNotifications, markAllAsRead, getUnreadCount } from "@/lib/notifications"; | |
| 5 | + | |
| 6 | +export async function GET(request: NextRequest) { | |
| 7 | + const response = NextResponse.next(); | |
| 8 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 9 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 10 | + | |
| 11 | + const { searchParams } = new URL(request.url); | |
| 12 | + const unreadOnly = searchParams.get("unread") === "true"; | |
| 13 | + const countOnly = searchParams.get("count") === "true"; | |
| 14 | + | |
| 15 | + if (countOnly) { | |
| 16 | + const count = await getUnreadCount(session.userId); | |
| 17 | + return NextResponse.json({ count }); | |
| 18 | + } | |
| 19 | + | |
| 20 | + const notifications = await getNotifications(session.userId, unreadOnly); | |
| 21 | + return NextResponse.json(notifications); | |
| 22 | +} | |
| 23 | + | |
| 24 | +export async function POST(request: NextRequest) { | |
| 25 | + const response = NextResponse.next(); | |
| 26 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 27 | + if (!session.isLoggedIn || !session.userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 28 | + | |
| 29 | + const { action } = await request.json(); | |
| 30 | + if (action === "mark_all_read") { | |
| 31 | + await markAllAsRead(session.userId); | |
| 32 | + return NextResponse.json({ success: true }); | |
| 33 | + } | |
| 34 | + return NextResponse.json({ error: "Unknown action" }, { status: 400 }); | |
| 35 | +} | |
added
app/api/shared/[token]/route.ts
+57 −0
@@ -0,0 +1,57 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath } from "@/lib/storage"; | |
| 4 | +import { readFile } from "fs/promises"; | |
| 5 | + | |
| 6 | +export async function GET( | |
| 7 | + request: NextRequest, | |
| 8 | + { params }: { params: { token: string } } | |
| 9 | +) { | |
| 10 | + const share = await prisma.sharedLink.findUnique({ | |
| 11 | + where: { token: params.token }, | |
| 12 | + include: { file: true }, | |
| 13 | + }); | |
| 14 | + | |
| 15 | + if (!share || !share.active) { | |
| 16 | + return NextResponse.json({ error: "Link not found or inactive" }, { status: 404 }); | |
| 17 | + } | |
| 18 | + | |
| 19 | + if (share.expiresAt && new Date() > share.expiresAt) { | |
| 20 | + return NextResponse.json({ error: "Link expired" }, { status: 410 }); | |
| 21 | + } | |
| 22 | + | |
| 23 | + if (share.passwordHash) { | |
| 24 | + return NextResponse.json({ | |
| 25 | + requiresPassword: true, | |
| 26 | + fileName: share.file.name, | |
| 27 | + fileType: share.file.mimeType, | |
| 28 | + mode: share.mode, | |
| 29 | + }); | |
| 30 | + } | |
| 31 | + | |
| 32 | + const { searchParams } = new URL(request.url); | |
| 33 | + const action = searchParams.get("action"); | |
| 34 | + | |
| 35 | + if (action === "download" || share.mode === "DOWNLOAD") { | |
| 36 | + const filePath = getFilePath(share.file.storagePath); | |
| 37 | + const buffer = await readFile(filePath); | |
| 38 | + | |
| 39 | + return new NextResponse(buffer, { | |
| 40 | + headers: { | |
| 41 | + "Content-Type": "application/octet-stream", | |
| 42 | + "Content-Disposition": `attachment; filename="${encodeURIComponent(share.file.name)}"`, | |
| 43 | + }, | |
| 44 | + }); | |
| 45 | + } | |
| 46 | + | |
| 47 | + // Preview mode | |
| 48 | + const filePath = getFilePath(share.file.storagePath); | |
| 49 | + const buffer = await readFile(filePath); | |
| 50 | + | |
| 51 | + return new NextResponse(buffer, { | |
| 52 | + headers: { | |
| 53 | + "Content-Type": share.file.mimeType, | |
| 54 | + "Content-Disposition": `inline; filename="${encodeURIComponent(share.file.name)}"`, | |
| 55 | + }, | |
| 56 | + }); | |
| 57 | +} | |
added
app/api/shared/[token]/verify/route.ts
+47 −0
@@ -0,0 +1,47 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { getFilePath } from "@/lib/storage"; | |
| 4 | +import { readFile } from "fs/promises"; | |
| 5 | +import bcrypt from "bcryptjs"; | |
| 6 | + | |
| 7 | +export async function POST( | |
| 8 | + request: NextRequest, | |
| 9 | + { params }: { params: { token: string } } | |
| 10 | +) { | |
| 11 | + const body = await request.json(); | |
| 12 | + const { password } = body; | |
| 13 | + | |
| 14 | + const share = await prisma.sharedLink.findUnique({ | |
| 15 | + where: { token: params.token }, | |
| 16 | + include: { file: true }, | |
| 17 | + }); | |
| 18 | + | |
| 19 | + if (!share || !share.active) { | |
| 20 | + return NextResponse.json({ error: "Link not found" }, { status: 404 }); | |
| 21 | + } | |
| 22 | + | |
| 23 | + if (share.expiresAt && new Date() > share.expiresAt) { | |
| 24 | + return NextResponse.json({ error: "Link expired" }, { status: 410 }); | |
| 25 | + } | |
| 26 | + | |
| 27 | + if (!share.passwordHash) { | |
| 28 | + return NextResponse.json({ error: "No password required" }, { status: 400 }); | |
| 29 | + } | |
| 30 | + | |
| 31 | + const valid = await bcrypt.compare(password, share.passwordHash); | |
| 32 | + if (!valid) { | |
| 33 | + return NextResponse.json({ error: "Wrong password" }, { status: 401 }); | |
| 34 | + } | |
| 35 | + | |
| 36 | + const filePath = getFilePath(share.file.storagePath); | |
| 37 | + const buffer = await readFile(filePath); | |
| 38 | + | |
| 39 | + const isDownload = share.mode === "DOWNLOAD"; | |
| 40 | + | |
| 41 | + return new NextResponse(buffer, { | |
| 42 | + headers: { | |
| 43 | + "Content-Type": isDownload ? "application/octet-stream" : share.file.mimeType, | |
| 44 | + "Content-Disposition": `${isDownload ? "attachment" : "inline"}; filename="${encodeURIComponent(share.file.name)}"`, | |
| 45 | + }, | |
| 46 | + }); | |
| 47 | +} | |
added
app/api/shares/[id]/route.ts
+37 −0
@@ -0,0 +1,37 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | + | |
| 5 | +export async function PATCH( | |
| 6 | + request: NextRequest, | |
| 7 | + { params }: { params: { id: string } } | |
| 8 | +) { | |
| 9 | + const body = await request.json(); | |
| 10 | + const { active } = body; | |
| 11 | + | |
| 12 | + const share = await prisma.sharedLink.update({ | |
| 13 | + where: { id: params.id }, | |
| 14 | + data: { active }, | |
| 15 | + }); | |
| 16 | + | |
| 17 | + if (!active) { | |
| 18 | + await logActivity("UNSHARE", share.fileId, "Link deactivated"); | |
| 19 | + } | |
| 20 | + | |
| 21 | + return NextResponse.json(share); | |
| 22 | +} | |
| 23 | + | |
| 24 | +export async function DELETE( | |
| 25 | + request: NextRequest, | |
| 26 | + { params }: { params: { id: string } } | |
| 27 | +) { | |
| 28 | + const share = await prisma.sharedLink.findUnique({ where: { id: params.id } }); | |
| 29 | + if (!share) { | |
| 30 | + return NextResponse.json({ error: "Share not found" }, { status: 404 }); | |
| 31 | + } | |
| 32 | + | |
| 33 | + await prisma.sharedLink.delete({ where: { id: params.id } }); | |
| 34 | + await logActivity("UNSHARE", share.fileId, "Link deleted"); | |
| 35 | + | |
| 36 | + return NextResponse.json({ success: true }); | |
| 37 | +} | |
added
app/api/shares/route.ts
+56 −0
@@ -0,0 +1,56 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { logActivity } from "@/lib/activity"; | |
| 4 | +import bcrypt from "bcryptjs"; | |
| 5 | + | |
| 6 | +export async function GET() { | |
| 7 | + const shares = await prisma.sharedLink.findMany({ | |
| 8 | + include: { | |
| 9 | + file: { select: { name: true, mimeType: true, size: true } }, | |
| 10 | + }, | |
| 11 | + orderBy: { createdAt: "desc" }, | |
| 12 | + }); | |
| 13 | + | |
| 14 | + const serialized = shares.map((s) => ({ | |
| 15 | + ...s, | |
| 16 | + file: s.file ? { ...s.file, size: Number(s.file.size) } : null, | |
| 17 | + })); | |
| 18 | + | |
| 19 | + return NextResponse.json(serialized); | |
| 20 | +} | |
| 21 | + | |
| 22 | +export async function POST(request: NextRequest) { | |
| 23 | + const body = await request.json(); | |
| 24 | + const { fileId, expiresAt, password, mode } = body; | |
| 25 | + | |
| 26 | + if (!fileId) { | |
| 27 | + return NextResponse.json({ error: "fileId required" }, { status: 400 }); | |
| 28 | + } | |
| 29 | + | |
| 30 | + const file = await prisma.file.findUnique({ where: { id: fileId } }); | |
| 31 | + if (!file) { | |
| 32 | + return NextResponse.json({ error: "File not found" }, { status: 404 }); | |
| 33 | + } | |
| 34 | + | |
| 35 | + const data: Record<string, unknown> = { | |
| 36 | + fileId, | |
| 37 | + mode: mode || "DOWNLOAD", | |
| 38 | + active: true, | |
| 39 | + }; | |
| 40 | + | |
| 41 | + if (expiresAt) { | |
| 42 | + data.expiresAt = new Date(expiresAt); | |
| 43 | + } | |
| 44 | + | |
| 45 | + if (password) { | |
| 46 | + data.passwordHash = await bcrypt.hash(password, 10); | |
| 47 | + } | |
| 48 | + | |
| 49 | + const share = await prisma.sharedLink.create({ | |
| 50 | + data: data as Parameters<typeof prisma.sharedLink.create>[0]["data"], | |
| 51 | + }); | |
| 52 | + | |
| 53 | + await logActivity("SHARE", fileId, `Shared ${file.name}`); | |
| 54 | + | |
| 55 | + return NextResponse.json(share, { status: 201 }); | |
| 56 | +} | |
added
app/api/tags/[id]/route.ts
+27 −0
@@ -0,0 +1,27 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | + | |
| 4 | +export async function DELETE( | |
| 5 | + request: NextRequest, | |
| 6 | + { params }: { params: { id: string } } | |
| 7 | +) { | |
| 8 | + await prisma.tag.delete({ where: { id: params.id } }); | |
| 9 | + return NextResponse.json({ success: true }); | |
| 10 | +} | |
| 11 | + | |
| 12 | +export async function PATCH( | |
| 13 | + request: NextRequest, | |
| 14 | + { params }: { params: { id: string } } | |
| 15 | +) { | |
| 16 | + const { name, color } = await request.json(); | |
| 17 | + const data: Record<string, string> = {}; | |
| 18 | + if (name) data.name = name.trim(); | |
| 19 | + if (color) data.color = color; | |
| 20 | + | |
| 21 | + const tag = await prisma.tag.update({ | |
| 22 | + where: { id: params.id }, | |
| 23 | + data, | |
| 24 | + }); | |
| 25 | + | |
| 26 | + return NextResponse.json(tag); | |
| 27 | +} | |
added
app/api/tags/route.ts
+24 −0
@@ -0,0 +1,24 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | + | |
| 4 | +export async function GET() { | |
| 5 | + const tags = await prisma.tag.findMany({ | |
| 6 | + orderBy: { name: "asc" }, | |
| 7 | + include: { _count: { select: { files: true } } }, | |
| 8 | + }); | |
| 9 | + return NextResponse.json(tags); | |
| 10 | +} | |
| 11 | + | |
| 12 | +export async function POST(request: NextRequest) { | |
| 13 | + const { name, color } = await request.json(); | |
| 14 | + | |
| 15 | + if (!name || typeof name !== "string" || !name.trim()) { | |
| 16 | + return NextResponse.json({ error: "Name required" }, { status: 400 }); | |
| 17 | + } | |
| 18 | + | |
| 19 | + const tag = await prisma.tag.create({ | |
| 20 | + data: { name: name.trim(), color: color || "#6b7280" }, | |
| 21 | + }); | |
| 22 | + | |
| 23 | + return NextResponse.json(tag, { status: 201 }); | |
| 24 | +} | |
added
app/api/users/[id]/route.ts
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | +import { hashPassword, requireAdmin } from "@/lib/auth"; | |
| 6 | + | |
| 7 | +export async function PATCH(request: NextRequest, { params }: { params: { id: string } }) { | |
| 8 | + const response = NextResponse.next(); | |
| 9 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 10 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 11 | + | |
| 12 | + const body = await request.json(); | |
| 13 | + const data: Record<string, unknown> = {}; | |
| 14 | + if (body.name !== undefined) data.name = body.name; | |
| 15 | + if (body.email !== undefined) data.email = body.email; | |
| 16 | + if (body.role !== undefined) data.role = body.role; | |
| 17 | + if (body.password) data.passwordHash = await hashPassword(body.password); | |
| 18 | + | |
| 19 | + const user = await prisma.user.update({ | |
| 20 | + where: { id: params.id }, | |
| 21 | + data, | |
| 22 | + select: { id: true, email: true, name: true, role: true }, | |
| 23 | + }); | |
| 24 | + return NextResponse.json(user); | |
| 25 | +} | |
| 26 | + | |
| 27 | +export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) { | |
| 28 | + const response = NextResponse.next(); | |
| 29 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 30 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 31 | + if (params.id === session.userId) return NextResponse.json({ error: "Cannot delete yourself" }, { status: 400 }); | |
| 32 | + | |
| 33 | + await prisma.user.delete({ where: { id: params.id } }); | |
| 34 | + return NextResponse.json({ success: true }); | |
| 35 | +} | |
added
app/api/users/route.ts
+36 −0
@@ -0,0 +1,36 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "@/lib/session"; | |
| 4 | +import { prisma } from "@/lib/prisma"; | |
| 5 | +import { hashPassword, requireAdmin } from "@/lib/auth"; | |
| 6 | + | |
| 7 | +export async function GET(request: NextRequest) { | |
| 8 | + const response = NextResponse.next(); | |
| 9 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 10 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 11 | + | |
| 12 | + const users = await prisma.user.findMany({ | |
| 13 | + select: { id: true, email: true, name: true, role: true, twoFactorEnabled: true, createdAt: true, _count: { select: { files: true, folders: true } } }, | |
| 14 | + orderBy: { createdAt: "asc" }, | |
| 15 | + }); | |
| 16 | + return NextResponse.json(users); | |
| 17 | +} | |
| 18 | + | |
| 19 | +export async function POST(request: NextRequest) { | |
| 20 | + const response = NextResponse.next(); | |
| 21 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 22 | + if (!session.userId || !(await requireAdmin(session.userId))) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | |
| 23 | + | |
| 24 | + const body = await request.json(); | |
| 25 | + const { email, name, password, role } = body; | |
| 26 | + if (!email || !password) return NextResponse.json({ error: "Email et mot de passe requis" }, { status: 400 }); | |
| 27 | + | |
| 28 | + const existing = await prisma.user.findUnique({ where: { email } }); | |
| 29 | + if (existing) return NextResponse.json({ error: "Email d\u00e9j\u00e0 utilis\u00e9" }, { status: 409 }); | |
| 30 | + | |
| 31 | + const user = await prisma.user.create({ | |
| 32 | + data: { email, name: name || null, passwordHash: await hashPassword(password), role: role || "user" }, | |
| 33 | + select: { id: true, email: true, name: true, role: true, createdAt: true }, | |
| 34 | + }); | |
| 35 | + return NextResponse.json(user, { status: 201 }); | |
| 36 | +} | |
added
app/api/v1/files/route.ts
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { prisma } from "@/lib/prisma"; | |
| 3 | +import { createHash } from "crypto"; | |
| 4 | + | |
| 5 | +async function validateApiKey(request: NextRequest) { | |
| 6 | + const auth = request.headers.get("authorization"); | |
| 7 | + if (!auth?.startsWith("Bearer ")) return null; | |
| 8 | + const rawKey = auth.substring(7); | |
| 9 | + const keyHash = createHash("sha256").update(rawKey).digest("hex"); | |
| 10 | + const apiKey = await prisma.apiKey.findUnique({ where: { keyHash } }); | |
| 11 | + if (!apiKey) return null; | |
| 12 | + if (apiKey.expiresAt && apiKey.expiresAt < new Date()) return null; | |
| 13 | + await prisma.apiKey.update({ where: { id: apiKey.id }, data: { lastUsedAt: new Date() } }); | |
| 14 | + return apiKey; | |
| 15 | +} | |
| 16 | + | |
| 17 | +export async function GET(request: NextRequest) { | |
| 18 | + const apiKey = await validateApiKey(request); | |
| 19 | + if (!apiKey) return NextResponse.json({ error: "Invalid API key" }, { status: 401 }); | |
| 20 | + | |
| 21 | + const { searchParams } = new URL(request.url); | |
| 22 | + const folderId = searchParams.get("folderId"); | |
| 23 | + const where: Record<string, unknown> = {}; | |
| 24 | + if (folderId && folderId !== "root") where.folderId = folderId; | |
| 25 | + else where.folderId = null; | |
| 26 | + | |
| 27 | + const files = await prisma.file.findMany({ | |
| 28 | + where, | |
| 29 | + select: { id: true, name: true, size: true, mimeType: true, createdAt: true, folderId: true }, | |
| 30 | + orderBy: { createdAt: "desc" }, | |
| 31 | + take: 100, | |
| 32 | + }); | |
| 33 | + | |
| 34 | + return NextResponse.json({ files: files.map((f) => ({ ...f, size: Number(f.size) })) }); | |
| 35 | +} | |
added
app/dashboard/favorites/page.tsx
+80 −0
@@ -0,0 +1,80 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useCallback } from "react"; | |
| 4 | +import { Star } from "lucide-react"; | |
| 5 | +import AppShell from "@/components/layout/AppShell"; | |
| 6 | +import FileCard from "@/components/files/FileCard"; | |
| 7 | +import PreviewEngine from "@/components/preview/PreviewEngine"; | |
| 8 | +import type { FileData } from "@/types/files"; | |
| 9 | + | |
| 10 | +export default function FavoritesPage() { | |
| 11 | + const [files, setFiles] = useState<FileData[]>([]); | |
| 12 | + const [previewFile, setPreviewFile] = useState<FileData | null>(null); | |
| 13 | + const [loading, setLoading] = useState(true); | |
| 14 | + | |
| 15 | + const fetchFavorites = useCallback(async () => { | |
| 16 | + setLoading(true); | |
| 17 | + try { | |
| 18 | + const res = await fetch("/api/files?favorite=true"); | |
| 19 | + if (res.ok) setFiles(await res.json()); | |
| 20 | + } catch {} | |
| 21 | + setLoading(false); | |
| 22 | + }, []); | |
| 23 | + | |
| 24 | + useEffect(() => { | |
| 25 | + fetchFavorites(); | |
| 26 | + }, [fetchFavorites]); | |
| 27 | + | |
| 28 | + return ( | |
| 29 | + <AppShell> | |
| 30 | + <div className="flex flex-col h-full lg:flex-row"> | |
| 31 | + <div className="flex-1 flex flex-col min-w-0"> | |
| 32 | + <div className="px-3 sm:px-4 lg:px-6 py-3 sm:py-4 border-b border-gray-200 dark:border-white/10"> | |
| 33 | + <h1 className="text-lg font-medium text-gray-900 dark:text-white flex items-center gap-2"> | |
| 34 | + <Star className="w-5 h-5 text-yellow-400" /> | |
| 35 | + Favoris | |
| 36 | + </h1> | |
| 37 | + <p className="text-xs text-gray-500 mt-1">{files.length} fichier{files.length !== 1 ? "s" : ""}</p> | |
| 38 | + </div> | |
| 39 | + | |
| 40 | + <div className="flex-1 overflow-auto px-3 sm:px-4 lg:px-6 py-3 sm:py-4"> | |
| 41 | + {loading ? ( | |
| 42 | + <div className="flex items-center justify-center h-32"> | |
| 43 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 44 | + </div> | |
| 45 | + ) : files.length === 0 ? ( | |
| 46 | + <div className="flex flex-col items-center justify-center h-64 text-gray-500"> | |
| 47 | + <Star className="w-12 h-12 mb-4 text-gray-300 dark:text-gray-600" /> | |
| 48 | + <p className="text-lg">Aucun favori</p> | |
| 49 | + <p className="text-sm mt-1 text-center px-4">Ajoutez des fichiers aux favoris pour les retrouver ici</p> | |
| 50 | + </div> | |
| 51 | + ) : ( | |
| 52 | + <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2 sm:gap-3"> | |
| 53 | + {files.map((file) => ( | |
| 54 | + <FileCard | |
| 55 | + key={file.id} | |
| 56 | + file={file} | |
| 57 | + onPreview={setPreviewFile} | |
| 58 | + onRefresh={fetchFavorites} | |
| 59 | + viewMode="grid" | |
| 60 | + /> | |
| 61 | + ))} | |
| 62 | + </div> | |
| 63 | + )} | |
| 64 | + </div> | |
| 65 | + </div> | |
| 66 | + | |
| 67 | + {previewFile && ( | |
| 68 | + <div className="fixed inset-0 z-50 lg:relative lg:inset-auto lg:w-[400px] lg:border-l lg:border-gray-200 dark:lg:border-white/10"> | |
| 69 | + <PreviewEngine | |
| 70 | + file={previewFile} | |
| 71 | + files={files} | |
| 72 | + onClose={() => setPreviewFile(null)} | |
| 73 | + onNavigate={setPreviewFile} | |
| 74 | + /> | |
| 75 | + </div> | |
| 76 | + )} | |
| 77 | + </div> | |
| 78 | + </AppShell> | |
| 79 | + ); | |
| 80 | +} | |
added
app/dashboard/folder/[id]/page.tsx
+49 −0
@@ -0,0 +1,49 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | +import { useParams } from "next/navigation"; | |
| 5 | +import AppShell from "@/components/layout/AppShell"; | |
| 6 | +import FileExplorer from "@/components/files/FileExplorer"; | |
| 7 | +import Breadcrumb from "@/components/layout/Breadcrumb"; | |
| 8 | + | |
| 9 | +interface BreadcrumbItem { | |
| 10 | + id: string; | |
| 11 | + name: string; | |
| 12 | +} | |
| 13 | + | |
| 14 | +export default function FolderPage() { | |
| 15 | + const params = useParams(); | |
| 16 | + const folderId = params.id as string; | |
| 17 | + const [breadcrumbs, setBreadcrumbs] = useState<BreadcrumbItem[]>([]); | |
| 18 | + | |
| 19 | + useEffect(() => { | |
| 20 | + async function fetchBreadcrumbs() { | |
| 21 | + const res = await fetch(`/api/folders/${folderId}`); | |
| 22 | + if (res.ok) { | |
| 23 | + const data = await res.json(); | |
| 24 | + const crumbs: BreadcrumbItem[] = []; | |
| 25 | + | |
| 26 | + // Build breadcrumb path | |
| 27 | + let current = data; | |
| 28 | + const visited = new Set<string>(); | |
| 29 | + while (current) { | |
| 30 | + if (visited.has(current.id)) break; | |
| 31 | + visited.add(current.id); | |
| 32 | + crumbs.unshift({ id: current.id, name: current.name }); | |
| 33 | + current = current.parent; | |
| 34 | + } | |
| 35 | + setBreadcrumbs(crumbs); | |
| 36 | + } | |
| 37 | + } | |
| 38 | + fetchBreadcrumbs(); | |
| 39 | + }, [folderId]); | |
| 40 | + | |
| 41 | + return ( | |
| 42 | + <AppShell> | |
| 43 | + <div className="px-4 lg:px-6 pt-4"> | |
| 44 | + <Breadcrumb items={breadcrumbs} /> | |
| 45 | + </div> | |
| 46 | + <FileExplorer folderId={folderId} /> | |
| 47 | + </AppShell> | |
| 48 | + ); | |
| 49 | +} | |
added
app/dashboard/page.tsx
+12 −0
@@ -0,0 +1,12 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import AppShell from "@/components/layout/AppShell"; | |
| 4 | +import FileExplorer from "@/components/files/FileExplorer"; | |
| 5 | + | |
| 6 | +export default function DashboardPage() { | |
| 7 | + return ( | |
| 8 | + <AppShell> | |
| 9 | + <FileExplorer /> | |
| 10 | + </AppShell> | |
| 11 | + ); | |
| 12 | +} | |
added
app/fonts/GeistMonoVF.woff
+0 −0
Binary file not shown.
added
app/fonts/GeistVF.woff
+0 −0
Binary file not shown.
added
app/globals.css
+151 −0
@@ -0,0 +1,151 @@ | ||
| 1 | +@tailwind base; | |
| 2 | +@tailwind components; | |
| 3 | +@tailwind utilities; | |
| 4 | + | |
| 5 | +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| 6 | + | |
| 7 | +/* ===== LIGHT THEME (default) ===== */ | |
| 8 | +:root { | |
| 9 | + --background: #ffffff; | |
| 10 | + --foreground: #111827; | |
| 11 | + --bg-sidebar: #f9fafb; | |
| 12 | + --bg-card: #f3f4f6; | |
| 13 | + --bg-card-hover: #e5e7eb; | |
| 14 | + --bg-input: #f3f4f6; | |
| 15 | + --bg-overlay: rgba(0, 0, 0, 0.4); | |
| 16 | + --border-primary: #e5e7eb; | |
| 17 | + --border-secondary: #d1d5db; | |
| 18 | + --text-primary: #111827; | |
| 19 | + --text-secondary: #374151; | |
| 20 | + --text-muted: #6b7280; | |
| 21 | + --text-dimmed: #9ca3af; | |
| 22 | + --scrollbar-track: #f3f4f6; | |
| 23 | + --scrollbar-thumb: #d1d5db; | |
| 24 | + --scrollbar-thumb-hover: #9ca3af; | |
| 25 | + --toast-bg: #ffffff; | |
| 26 | + --toast-color: #111827; | |
| 27 | + --toast-border: rgba(0, 0, 0, 0.1); | |
| 28 | +} | |
| 29 | + | |
| 30 | +/* ===== DARK THEME ===== */ | |
| 31 | +.dark { | |
| 32 | + --background: #0a0a0a; | |
| 33 | + --foreground: #ededed; | |
| 34 | + --bg-sidebar: #0a0a0a; | |
| 35 | + --bg-card: rgba(255, 255, 255, 0.05); | |
| 36 | + --bg-card-hover: rgba(255, 255, 255, 0.1); | |
| 37 | + --bg-input: rgba(255, 255, 255, 0.05); | |
| 38 | + --bg-overlay: rgba(0, 0, 0, 0.6); | |
| 39 | + --border-primary: rgba(255, 255, 255, 0.1); | |
| 40 | + --border-secondary: rgba(255, 255, 255, 0.2); | |
| 41 | + --text-primary: #ffffff; | |
| 42 | + --text-secondary: #e5e7eb; | |
| 43 | + --text-muted: #9ca3af; | |
| 44 | + --text-dimmed: #6b7280; | |
| 45 | + --scrollbar-track: #111827; | |
| 46 | + --scrollbar-thumb: #374151; | |
| 47 | + --scrollbar-thumb-hover: #4b5563; | |
| 48 | + --toast-bg: #1f2937; | |
| 49 | + --toast-color: #e5e7eb; | |
| 50 | + --toast-border: rgba(255, 255, 255, 0.1); | |
| 51 | +} | |
| 52 | + | |
| 53 | +body { | |
| 54 | + color: var(--foreground); | |
| 55 | + background: var(--background); | |
| 56 | + font-family: "Inter", system-ui, sans-serif; | |
| 57 | + transition: background-color 0.3s ease, color 0.3s ease; | |
| 58 | + -webkit-tap-highlight-color: transparent; | |
| 59 | + -webkit-text-size-adjust: 100%; | |
| 60 | +} | |
| 61 | + | |
| 62 | +/* ===== MOBILE SAFE AREAS ===== */ | |
| 63 | +.safe-top { | |
| 64 | + padding-top: env(safe-area-inset-top, 0); | |
| 65 | +} | |
| 66 | + | |
| 67 | +.pb-safe { | |
| 68 | + padding-bottom: env(safe-area-inset-bottom, 0); | |
| 69 | +} | |
| 70 | + | |
| 71 | +/* ===== TOUCH OPTIMIZATION ===== */ | |
| 72 | +@layer utilities { | |
| 73 | + .touch-target { | |
| 74 | + min-width: 44px; | |
| 75 | + min-height: 44px; | |
| 76 | + } | |
| 77 | +} | |
| 78 | + | |
| 79 | +/* Prevent text selection on interactive elements on mobile */ | |
| 80 | +button, a, [role="button"] { | |
| 81 | + -webkit-touch-callout: none; | |
| 82 | + user-select: none; | |
| 83 | +} | |
| 84 | + | |
| 85 | +/* Active state feedback for mobile */ | |
| 86 | +button:active, a:active, [role="button"]:active { | |
| 87 | + opacity: 0.85; | |
| 88 | +} | |
| 89 | + | |
| 90 | +/* Custom scrollbar */ | |
| 91 | +::-webkit-scrollbar { | |
| 92 | + width: 6px; | |
| 93 | + height: 6px; | |
| 94 | +} | |
| 95 | + | |
| 96 | +@media (max-width: 768px) { | |
| 97 | + ::-webkit-scrollbar { | |
| 98 | + width: 3px; | |
| 99 | + height: 3px; | |
| 100 | + } | |
| 101 | +} | |
| 102 | + | |
| 103 | +::-webkit-scrollbar-track { | |
| 104 | + background: var(--scrollbar-track); | |
| 105 | +} | |
| 106 | + | |
| 107 | +::-webkit-scrollbar-thumb { | |
| 108 | + background: var(--scrollbar-thumb); | |
| 109 | + border-radius: 3px; | |
| 110 | +} | |
| 111 | + | |
| 112 | +::-webkit-scrollbar-thumb:hover { | |
| 113 | + background: var(--scrollbar-thumb-hover); | |
| 114 | +} | |
| 115 | + | |
| 116 | +/* File drop zone animations */ | |
| 117 | +.drop-active { | |
| 118 | + border-color: #3b82f6 !important; | |
| 119 | + background-color: rgba(59, 130, 246, 0.05) !important; | |
| 120 | +} | |
| 121 | + | |
| 122 | +/* Mobile modal - full viewport */ | |
| 123 | +@media (max-width: 640px) { | |
| 124 | + .mobile-full-modal { | |
| 125 | + width: 100vw !important; | |
| 126 | + max-width: 100vw !important; | |
| 127 | + min-height: 50vh; | |
| 128 | + border-radius: 1rem 1rem 0 0 !important; | |
| 129 | + margin: 0; | |
| 130 | + position: fixed; | |
| 131 | + bottom: 0; | |
| 132 | + left: 0; | |
| 133 | + right: 0; | |
| 134 | + } | |
| 135 | +} | |
| 136 | + | |
| 137 | +/* Smooth overscroll behavior */ | |
| 138 | +html { | |
| 139 | + overscroll-behavior: none; | |
| 140 | +} | |
| 141 | + | |
| 142 | +/* Better select inputs on mobile */ | |
| 143 | +select { | |
| 144 | + -webkit-appearance: none; | |
| 145 | + appearance: none; | |
| 146 | + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); | |
| 147 | + background-position: right 0.5rem center; | |
| 148 | + background-repeat: no-repeat; | |
| 149 | + background-size: 1.5em 1.5em; | |
| 150 | + padding-right: 2.5rem; | |
| 151 | +} | |
added
app/layout.tsx
+43 −0
@@ -0,0 +1,43 @@ | ||
| 1 | +import type { Metadata } from "next"; | |
| 2 | +import "./globals.css"; | |
| 3 | +import ThemeProvider from "@/components/theme/ThemeProvider"; | |
| 4 | +import { DynamicToaster } from "@/components/theme/DynamicToaster"; | |
| 5 | + | |
| 6 | +export const metadata: Metadata = { | |
| 7 | + title: "SPB Cloud", | |
| 8 | + description: "Cloud personnel privé", | |
| 9 | +}; | |
| 10 | + | |
| 11 | +export default function RootLayout({ | |
| 12 | + children, | |
| 13 | +}: Readonly<{ | |
| 14 | + children: React.ReactNode; | |
| 15 | +}>) { | |
| 16 | + return ( | |
| 17 | + <html lang="fr" className="dark" suppressHydrationWarning> | |
| 18 | + <head> | |
| 19 | + <script | |
| 20 | + dangerouslySetInnerHTML={{ | |
| 21 | + __html: ` | |
| 22 | + (function() { | |
| 23 | + try { | |
| 24 | + var theme = localStorage.getItem('spb-theme'); | |
| 25 | + if (theme === 'light') { | |
| 26 | + document.documentElement.classList.remove('dark'); | |
| 27 | + document.documentElement.classList.add('light'); | |
| 28 | + } | |
| 29 | + } catch(e) {} | |
| 30 | + })(); | |
| 31 | + `, | |
| 32 | + }} | |
| 33 | + /> | |
| 34 | + </head> | |
| 35 | + <body className="antialiased"> | |
| 36 | + <ThemeProvider> | |
| 37 | + {children} | |
| 38 | + <DynamicToaster /> | |
| 39 | + </ThemeProvider> | |
| 40 | + </body> | |
| 41 | + </html> | |
| 42 | + ); | |
| 43 | +} | |
added
app/login/page.tsx
+199 −0
@@ -0,0 +1,199 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import { useRouter } from "next/navigation"; | |
| 5 | +import { motion } from "framer-motion"; | |
| 6 | +import { Cloud, Lock, Eye, EyeOff, Loader2, Mail, Shield } from "lucide-react"; | |
| 7 | +import ThemeToggle from "@/components/theme/ThemeToggle"; | |
| 8 | + | |
| 9 | +export default function LoginPage() { | |
| 10 | + const [email, setEmail] = useState(""); | |
| 11 | + const [password, setPassword] = useState(""); | |
| 12 | + const [totpCode, setTotpCode] = useState(""); | |
| 13 | + const [error, setError] = useState(""); | |
| 14 | + const [loading, setLoading] = useState(false); | |
| 15 | + const [showPassword, setShowPassword] = useState(false); | |
| 16 | + const [needs2FA, setNeeds2FA] = useState(false); | |
| 17 | + const router = useRouter(); | |
| 18 | + | |
| 19 | + const inputClasses = "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl px-10 py-3.5 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:border-gray-400 dark:focus:border-white/30 transition-colors shadow-sm dark:shadow-none"; | |
| 20 | + | |
| 21 | + const handleSubmit = async (e: React.FormEvent) => { | |
| 22 | + e.preventDefault(); | |
| 23 | + setError(""); | |
| 24 | + setLoading(true); | |
| 25 | + | |
| 26 | + try { | |
| 27 | + const res = await fetch("/api/auth/login", { | |
| 28 | + method: "POST", | |
| 29 | + headers: { "Content-Type": "application/json" }, | |
| 30 | + body: JSON.stringify({ password, email: email || undefined }), | |
| 31 | + }); | |
| 32 | + | |
| 33 | + const data = await res.json(); | |
| 34 | + | |
| 35 | + if (res.ok) { | |
| 36 | + if (data.requires2FA) { | |
| 37 | + setNeeds2FA(true); | |
| 38 | + } else { | |
| 39 | + router.push("/dashboard"); | |
| 40 | + } | |
| 41 | + } else { | |
| 42 | + setError(data.error || "Erreur de connexion"); | |
| 43 | + } | |
| 44 | + } catch { | |
| 45 | + setError("Erreur réseau"); | |
| 46 | + } finally { | |
| 47 | + setLoading(false); | |
| 48 | + } | |
| 49 | + }; | |
| 50 | + | |
| 51 | + const handleVerify2FA = async (e: React.FormEvent) => { | |
| 52 | + e.preventDefault(); | |
| 53 | + setError(""); | |
| 54 | + setLoading(true); | |
| 55 | + | |
| 56 | + try { | |
| 57 | + const res = await fetch("/api/auth/2fa/verify", { | |
| 58 | + method: "POST", | |
| 59 | + headers: { "Content-Type": "application/json" }, | |
| 60 | + body: JSON.stringify({ token: totpCode }), | |
| 61 | + }); | |
| 62 | + | |
| 63 | + if (res.ok) { | |
| 64 | + router.push("/dashboard"); | |
| 65 | + } else { | |
| 66 | + const data = await res.json(); | |
| 67 | + setError(data.error || "Code invalide"); | |
| 68 | + } | |
| 69 | + } catch { | |
| 70 | + setError("Erreur réseau"); | |
| 71 | + } finally { | |
| 72 | + setLoading(false); | |
| 73 | + } | |
| 74 | + }; | |
| 75 | + | |
| 76 | + return ( | |
| 77 | + <div className="min-h-screen bg-gray-50 dark:bg-[#0a0a0a] flex items-center justify-center px-4 py-8 transition-colors"> | |
| 78 | + <div className="fixed top-3 right-3 sm:top-4 sm:right-4 z-10"> | |
| 79 | + <ThemeToggle /> | |
| 80 | + </div> | |
| 81 | + | |
| 82 | + <motion.div | |
| 83 | + initial={{ opacity: 0, y: 20 }} | |
| 84 | + animate={{ opacity: 1, y: 0 }} | |
| 85 | + transition={{ duration: 0.5 }} | |
| 86 | + className="w-full max-w-sm px-4 sm:px-0" | |
| 87 | + > | |
| 88 | + <div className="text-center mb-8"> | |
| 89 | + <motion.div | |
| 90 | + initial={{ scale: 0 }} | |
| 91 | + animate={{ scale: 1 }} | |
| 92 | + transition={{ delay: 0.2, type: "spring", stiffness: 200 }} | |
| 93 | + className="inline-flex items-center justify-center w-14 h-14 sm:w-16 sm:h-16 rounded-2xl bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 mb-4" | |
| 94 | + > | |
| 95 | + <Cloud className="w-7 h-7 sm:w-8 sm:h-8 text-gray-700 dark:text-white" /> | |
| 96 | + </motion.div> | |
| 97 | + <h1 className="text-2xl font-semibold text-gray-900 dark:text-white">SPB Cloud</h1> | |
| 98 | + <p className="text-gray-500 mt-1 text-sm"> | |
| 99 | + {needs2FA ? "Vérification 2FA" : "Cloud personnel privé"} | |
| 100 | + </p> | |
| 101 | + </div> | |
| 102 | + | |
| 103 | + {!needs2FA ? ( | |
| 104 | + <form onSubmit={handleSubmit} className="space-y-4"> | |
| 105 | + <div className="relative"> | |
| 106 | + <Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500" /> | |
| 107 | + <input | |
| 108 | + type="email" | |
| 109 | + value={email} | |
| 110 | + onChange={(e) => setEmail(e.target.value)} | |
| 111 | + placeholder="Email (optionnel)" | |
| 112 | + className={inputClasses} | |
| 113 | + /> | |
| 114 | + </div> | |
| 115 | + | |
| 116 | + <div className="relative"> | |
| 117 | + <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500" /> | |
| 118 | + <input | |
| 119 | + type={showPassword ? "text" : "password"} | |
| 120 | + value={password} | |
| 121 | + onChange={(e) => setPassword(e.target.value)} | |
| 122 | + placeholder="Mot de passe" | |
| 123 | + className={inputClasses} | |
| 124 | + autoFocus | |
| 125 | + /> | |
| 126 | + <button | |
| 127 | + type="button" | |
| 128 | + onClick={() => setShowPassword(!showPassword)} | |
| 129 | + className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors" | |
| 130 | + > | |
| 131 | + {showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />} | |
| 132 | + </button> | |
| 133 | + </div> | |
| 134 | + | |
| 135 | + {error && ( | |
| 136 | + <motion.p initial={{ opacity: 0, y: -5 }} animate={{ opacity: 1, y: 0 }} className="text-red-500 dark:text-red-400 text-sm text-center"> | |
| 137 | + {error} | |
| 138 | + </motion.p> | |
| 139 | + )} | |
| 140 | + | |
| 141 | + <motion.button | |
| 142 | + whileTap={{ scale: 0.98 }} | |
| 143 | + type="submit" | |
| 144 | + disabled={loading || !password} | |
| 145 | + className="w-full bg-gray-900 dark:bg-white text-white dark:text-black font-medium py-3.5 rounded-xl hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2" | |
| 146 | + > | |
| 147 | + {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Connexion"} | |
| 148 | + </motion.button> | |
| 149 | + </form> | |
| 150 | + ) : ( | |
| 151 | + <form onSubmit={handleVerify2FA} className="space-y-4"> | |
| 152 | + <div className="flex justify-center mb-4"> | |
| 153 | + <Shield className="w-12 h-12 text-purple-500" /> | |
| 154 | + </div> | |
| 155 | + <p className="text-sm text-gray-500 dark:text-gray-400 text-center"> | |
| 156 | + Entrez le code à 6 chiffres de votre app authenticator | |
| 157 | + </p> | |
| 158 | + <input | |
| 159 | + type="text" | |
| 160 | + value={totpCode} | |
| 161 | + onChange={(e) => setTotpCode(e.target.value.replace(/\D/g, "").slice(0, 6))} | |
| 162 | + placeholder="000000" | |
| 163 | + maxLength={6} | |
| 164 | + className="w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl px-4 py-3.5 text-gray-900 dark:text-white text-center text-2xl tracking-[0.3em] sm:tracking-[0.5em] font-mono focus:outline-none focus:border-gray-400 dark:focus:border-white/30 shadow-sm dark:shadow-none" | |
| 165 | + autoFocus | |
| 166 | + /> | |
| 167 | + | |
| 168 | + {error && ( | |
| 169 | + <motion.p initial={{ opacity: 0, y: -5 }} animate={{ opacity: 1, y: 0 }} className="text-red-500 dark:text-red-400 text-sm text-center"> | |
| 170 | + {error} | |
| 171 | + </motion.p> | |
| 172 | + )} | |
| 173 | + | |
| 174 | + <motion.button | |
| 175 | + whileTap={{ scale: 0.98 }} | |
| 176 | + type="submit" | |
| 177 | + disabled={loading || totpCode.length !== 6} | |
| 178 | + className="w-full bg-gray-900 dark:bg-white text-white dark:text-black font-medium py-3.5 rounded-xl hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2" | |
| 179 | + > | |
| 180 | + {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Vérifier"} | |
| 181 | + </motion.button> | |
| 182 | + | |
| 183 | + <button | |
| 184 | + type="button" | |
| 185 | + onClick={() => { setNeeds2FA(false); setTotpCode(""); setError(""); }} | |
| 186 | + className="w-full text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 py-2" | |
| 187 | + > | |
| 188 | + Retour | |
| 189 | + </button> | |
| 190 | + </form> | |
| 191 | + )} | |
| 192 | + | |
| 193 | + <p className="text-center text-gray-400 dark:text-gray-600 text-xs mt-6"> | |
| 194 | + SPB Private Cloud v2.0 | |
| 195 | + </p> | |
| 196 | + </motion.div> | |
| 197 | + </div> | |
| 198 | + ); | |
| 199 | +} | |
added
app/page.tsx
+5 −0
@@ -0,0 +1,5 @@ | ||
| 1 | +import { redirect } from "next/navigation"; | |
| 2 | + | |
| 3 | +export default function Home() { | |
| 4 | + redirect("/dashboard"); | |
| 5 | +} | |
added
app/shared/[token]/page.tsx
+169 −0
@@ -0,0 +1,169 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useState } from "react"; | |
| 4 | +import { useParams } from "next/navigation"; | |
| 5 | +import { motion } from "framer-motion"; | |
| 6 | +import { Cloud, Download, Lock, AlertCircle, Loader2 } from "lucide-react"; | |
| 7 | +import Button from "@/components/ui/Button"; | |
| 8 | + | |
| 9 | +export default function SharedPage() { | |
| 10 | + const params = useParams(); | |
| 11 | + const token = params.token as string; | |
| 12 | + | |
| 13 | + const [state, setState] = useState<"loading" | "password" | "preview" | "error">("loading"); | |
| 14 | + const [error, setError] = useState(""); | |
| 15 | + const [fileInfo, setFileInfo] = useState<{ | |
| 16 | + fileName: string; | |
| 17 | + fileType: string; | |
| 18 | + mode: string; | |
| 19 | + } | null>(null); | |
| 20 | + const [password, setPassword] = useState(""); | |
| 21 | + const [passwordLoading, setPasswordLoading] = useState(false); | |
| 22 | + | |
| 23 | + useEffect(() => { | |
| 24 | + fetch(`/api/shared/${token}`) | |
| 25 | + .then(async (res) => { | |
| 26 | + if (!res.ok) { | |
| 27 | + const data = await res.json(); | |
| 28 | + setError(data.error || "Lien invalide"); | |
| 29 | + setState("error"); | |
| 30 | + return; | |
| 31 | + } | |
| 32 | + | |
| 33 | + const contentType = res.headers.get("content-type") || ""; | |
| 34 | + if (contentType.includes("application/json")) { | |
| 35 | + const data = await res.json(); | |
| 36 | + if (data.requiresPassword) { | |
| 37 | + setFileInfo(data); | |
| 38 | + setState("password"); | |
| 39 | + } | |
| 40 | + } else { | |
| 41 | + // Direct file response — redirect to download/preview | |
| 42 | + setState("preview"); | |
| 43 | + } | |
| 44 | + }) | |
| 45 | + .catch(() => { | |
| 46 | + setError("Erreur réseau"); | |
| 47 | + setState("error"); | |
| 48 | + }); | |
| 49 | + }, [token]); | |
| 50 | + | |
| 51 | + const handlePasswordSubmit = async (e: React.FormEvent) => { | |
| 52 | + e.preventDefault(); | |
| 53 | + setPasswordLoading(true); | |
| 54 | + try { | |
| 55 | + const res = await fetch(`/api/shared/${token}/verify`, { | |
| 56 | + method: "POST", | |
| 57 | + headers: { "Content-Type": "application/json" }, | |
| 58 | + body: JSON.stringify({ password }), | |
| 59 | + }); | |
| 60 | + | |
| 61 | + if (!res.ok) { | |
| 62 | + const data = await res.json(); | |
| 63 | + setError(data.error || "Mot de passe incorrect"); | |
| 64 | + setPasswordLoading(false); | |
| 65 | + return; | |
| 66 | + } | |
| 67 | + | |
| 68 | + // Download the file | |
| 69 | + const blob = await res.blob(); | |
| 70 | + const url = URL.createObjectURL(blob); | |
| 71 | + const a = document.createElement("a"); | |
| 72 | + a.href = url; | |
| 73 | + a.download = fileInfo?.fileName || "file"; | |
| 74 | + a.click(); | |
| 75 | + URL.revokeObjectURL(url); | |
| 76 | + setPasswordLoading(false); | |
| 77 | + } catch { | |
| 78 | + setError("Erreur réseau"); | |
| 79 | + setPasswordLoading(false); | |
| 80 | + } | |
| 81 | + }; | |
| 82 | + | |
| 83 | + if (state === "loading") { | |
| 84 | + return ( | |
| 85 | + <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center"> | |
| 86 | + <Loader2 className="w-8 h-8 text-white animate-spin" /> | |
| 87 | + </div> | |
| 88 | + ); | |
| 89 | + } | |
| 90 | + | |
| 91 | + if (state === "error") { | |
| 92 | + return ( | |
| 93 | + <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center p-4"> | |
| 94 | + <motion.div | |
| 95 | + initial={{ opacity: 0, y: 20 }} | |
| 96 | + animate={{ opacity: 1, y: 0 }} | |
| 97 | + className="text-center" | |
| 98 | + > | |
| 99 | + <AlertCircle className="w-16 h-16 text-red-400 mx-auto mb-4" /> | |
| 100 | + <h1 className="text-xl font-semibold text-white mb-2">Lien invalide</h1> | |
| 101 | + <p className="text-gray-400">{error}</p> | |
| 102 | + </motion.div> | |
| 103 | + </div> | |
| 104 | + ); | |
| 105 | + } | |
| 106 | + | |
| 107 | + if (state === "password") { | |
| 108 | + return ( | |
| 109 | + <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center p-4"> | |
| 110 | + <motion.div | |
| 111 | + initial={{ opacity: 0, y: 20 }} | |
| 112 | + animate={{ opacity: 1, y: 0 }} | |
| 113 | + className="w-full max-w-sm" | |
| 114 | + > | |
| 115 | + <div className="text-center mb-6"> | |
| 116 | + <Lock className="w-12 h-12 text-gray-400 mx-auto mb-3" /> | |
| 117 | + <h1 className="text-xl font-semibold text-white"> | |
| 118 | + Fichier protégé | |
| 119 | + </h1> | |
| 120 | + <p className="text-sm text-gray-500 mt-1">{fileInfo?.fileName}</p> | |
| 121 | + </div> | |
| 122 | + | |
| 123 | + <form onSubmit={handlePasswordSubmit} className="space-y-4"> | |
| 124 | + <input | |
| 125 | + type="password" | |
| 126 | + value={password} | |
| 127 | + onChange={(e) => setPassword(e.target.value)} | |
| 128 | + placeholder="Mot de passe" | |
| 129 | + className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-white/20" | |
| 130 | + autoFocus | |
| 131 | + /> | |
| 132 | + {error && <p className="text-red-400 text-sm text-center">{error}</p>} | |
| 133 | + <Button type="submit" className="w-full" loading={passwordLoading}> | |
| 134 | + Accéder | |
| 135 | + </Button> | |
| 136 | + </form> | |
| 137 | + </motion.div> | |
| 138 | + </div> | |
| 139 | + ); | |
| 140 | + } | |
| 141 | + | |
| 142 | + // Preview state — show the file | |
| 143 | + return ( | |
| 144 | + <div className="min-h-screen bg-[#0a0a0a] flex flex-col"> | |
| 145 | + <header className="flex items-center justify-between px-4 py-3 border-b border-white/10"> | |
| 146 | + <div className="flex items-center gap-2"> | |
| 147 | + <Cloud className="w-5 h-5 text-white" /> | |
| 148 | + <span className="text-sm text-white font-medium">SPB Cloud</span> | |
| 149 | + </div> | |
| 150 | + <Button | |
| 151 | + size="sm" | |
| 152 | + onClick={() => | |
| 153 | + window.open(`/api/shared/${token}?action=download`, "_blank") | |
| 154 | + } | |
| 155 | + > | |
| 156 | + <Download className="w-4 h-4" /> | |
| 157 | + Télécharger | |
| 158 | + </Button> | |
| 159 | + </header> | |
| 160 | + <main className="flex-1 flex items-center justify-center p-4"> | |
| 161 | + <iframe | |
| 162 | + src={`/api/shared/${token}`} | |
| 163 | + className="w-full h-full min-h-[80vh] border-0 rounded-lg" | |
| 164 | + title="Shared file" | |
| 165 | + /> | |
| 166 | + </main> | |
| 167 | + </div> | |
| 168 | + ); | |
| 169 | +} | |
added
components/admin/ActivityLog.tsx
+99 −0
@@ -0,0 +1,99 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion } from "framer-motion"; | |
| 4 | +import { formatDate } from "@/lib/utils"; | |
| 5 | +import { | |
| 6 | + Upload, | |
| 7 | + Download, | |
| 8 | + Trash2, | |
| 9 | + Pencil, | |
| 10 | + FolderInput, | |
| 11 | + Share2, | |
| 12 | + LogIn, | |
| 13 | + LogOut, | |
| 14 | + FolderPlus, | |
| 15 | + FolderMinus, | |
| 16 | + Link2Off, | |
| 17 | +} from "lucide-react"; | |
| 18 | + | |
| 19 | +interface Activity { | |
| 20 | + id: string; | |
| 21 | + action: string; | |
| 22 | + details: string | null; | |
| 23 | + createdAt: string; | |
| 24 | + file: { name: string } | null; | |
| 25 | +} | |
| 26 | + | |
| 27 | +interface ActivityLogProps { | |
| 28 | + activities: Activity[]; | |
| 29 | +} | |
| 30 | + | |
| 31 | +const actionIcons: Record<string, React.ComponentType<{ className?: string }>> = { | |
| 32 | + UPLOAD: Upload, | |
| 33 | + DOWNLOAD: Download, | |
| 34 | + DELETE: Trash2, | |
| 35 | + RENAME: Pencil, | |
| 36 | + MOVE: FolderInput, | |
| 37 | + SHARE: Share2, | |
| 38 | + UNSHARE: Link2Off, | |
| 39 | + LOGIN: LogIn, | |
| 40 | + LOGOUT: LogOut, | |
| 41 | + CREATE_FOLDER: FolderPlus, | |
| 42 | + DELETE_FOLDER: FolderMinus, | |
| 43 | +}; | |
| 44 | + | |
| 45 | +const actionLabels: Record<string, string> = { | |
| 46 | + UPLOAD: "Upload", | |
| 47 | + DOWNLOAD: "Téléchargement", | |
| 48 | + DELETE: "Suppression", | |
| 49 | + RENAME: "Renommage", | |
| 50 | + MOVE: "Déplacement", | |
| 51 | + SHARE: "Partage", | |
| 52 | + UNSHARE: "Partage retiré", | |
| 53 | + LOGIN: "Connexion", | |
| 54 | + LOGOUT: "Déconnexion", | |
| 55 | + CREATE_FOLDER: "Dossier créé", | |
| 56 | + DELETE_FOLDER: "Dossier supprimé", | |
| 57 | +}; | |
| 58 | + | |
| 59 | +export default function ActivityLog({ activities }: ActivityLogProps) { | |
| 60 | + if (activities.length === 0) { | |
| 61 | + return ( | |
| 62 | + <div className="text-center text-gray-500 py-8"> | |
| 63 | + Aucune activité récente | |
| 64 | + </div> | |
| 65 | + ); | |
| 66 | + } | |
| 67 | + | |
| 68 | + return ( | |
| 69 | + <div className="space-y-1"> | |
| 70 | + {activities.map((activity, i) => { | |
| 71 | + const Icon = actionIcons[activity.action] || Upload; | |
| 72 | + return ( | |
| 73 | + <motion.div | |
| 74 | + key={activity.id} | |
| 75 | + initial={{ opacity: 0, x: -10 }} | |
| 76 | + animate={{ opacity: 1, x: 0 }} | |
| 77 | + transition={{ delay: i * 0.03 }} | |
| 78 | + className="flex items-center gap-3 px-3 py-3.5 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg transition-colors" | |
| 79 | + > | |
| 80 | + <Icon className="w-4 h-4 text-gray-400 dark:text-gray-500 flex-shrink-0" /> | |
| 81 | + <div className="flex-1 min-w-0"> | |
| 82 | + <p className="text-sm text-gray-600 dark:text-gray-300"> | |
| 83 | + <span className="text-gray-500 dark:text-gray-400"> | |
| 84 | + {actionLabels[activity.action] || activity.action} | |
| 85 | + </span> | |
| 86 | + {activity.details && ( | |
| 87 | + <span className="text-gray-400 dark:text-gray-500 ml-1">— {activity.details}</span> | |
| 88 | + )} | |
| 89 | + </p> | |
| 90 | + </div> | |
| 91 | + <span className="text-xs text-gray-400 dark:text-gray-600 flex-shrink-0 hidden sm:block"> | |
| 92 | + {formatDate(activity.createdAt)} | |
| 93 | + </span> | |
| 94 | + </motion.div> | |
| 95 | + ); | |
| 96 | + })} | |
| 97 | + </div> | |
| 98 | + ); | |
| 99 | +} | |
added
components/admin/ApiKeyManager.tsx
+158 −0
@@ -0,0 +1,158 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect } from "react"; | |
| 4 | +import { motion } from "framer-motion"; | |
| 5 | +import { Key, Trash2, Copy, Plus } from "lucide-react"; | |
| 6 | +import Button from "../ui/Button"; | |
| 7 | +import Modal from "../ui/Modal"; | |
| 8 | +import { formatDate } from "@/lib/utils"; | |
| 9 | +import toast from "react-hot-toast"; | |
| 10 | + | |
| 11 | +interface ApiKeyData { | |
| 12 | + id: string; | |
| 13 | + name: string; | |
| 14 | + keyPrefix: string; | |
| 15 | + permissions: string; | |
| 16 | + lastUsedAt: string | null; | |
| 17 | + expiresAt: string | null; | |
| 18 | + createdAt: string; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export default function ApiKeyManager() { | |
| 22 | + const [keys, setKeys] = useState<ApiKeyData[]>([]); | |
| 23 | + const [loading, setLoading] = useState(true); | |
| 24 | + const [showCreate, setShowCreate] = useState(false); | |
| 25 | + const [newKey, setNewKey] = useState<string | null>(null); | |
| 26 | + const [form, setForm] = useState({ name: "", permissions: "read" }); | |
| 27 | + | |
| 28 | + const fetchKeys = async () => { | |
| 29 | + const res = await fetch("/api/keys"); | |
| 30 | + if (res.ok) setKeys(await res.json()); | |
| 31 | + setLoading(false); | |
| 32 | + }; | |
| 33 | + | |
| 34 | + useEffect(() => { fetchKeys(); }, []); | |
| 35 | + | |
| 36 | + const createKey = async () => { | |
| 37 | + const res = await fetch("/api/keys", { | |
| 38 | + method: "POST", | |
| 39 | + headers: { "Content-Type": "application/json" }, | |
| 40 | + body: JSON.stringify(form), | |
| 41 | + }); | |
| 42 | + if (res.ok) { | |
| 43 | + const data = await res.json(); | |
| 44 | + setNewKey(data.key); | |
| 45 | + setForm({ name: "", permissions: "read" }); | |
| 46 | + fetchKeys(); | |
| 47 | + } | |
| 48 | + }; | |
| 49 | + | |
| 50 | + const deleteKey = async (id: string) => { | |
| 51 | + if (!confirm("Supprimer cette clé API ?")) return; | |
| 52 | + await fetch(`/api/keys/${id}`, { method: "DELETE" }); | |
| 53 | + toast.success("Clé supprimée"); | |
| 54 | + fetchKeys(); | |
| 55 | + }; | |
| 56 | + | |
| 57 | + const copyKey = (key: string) => { | |
| 58 | + navigator.clipboard.writeText(key); | |
| 59 | + toast.success("Clé copiée"); | |
| 60 | + }; | |
| 61 | + | |
| 62 | + const inputClasses = "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none"; | |
| 63 | + | |
| 64 | + return ( | |
| 65 | + <div className="space-y-4"> | |
| 66 | + <div className="flex items-center justify-between"> | |
| 67 | + <h2 className="text-lg font-medium text-gray-900 dark:text-white flex items-center gap-2"> | |
| 68 | + <Key className="w-5 h-5" /> Clés API | |
| 69 | + </h2> | |
| 70 | + <Button size="sm" onClick={() => setShowCreate(true)}> | |
| 71 | + <Plus className="w-4 h-4" /> Nouvelle clé | |
| 72 | + </Button> | |
| 73 | + </div> | |
| 74 | + | |
| 75 | + <p className="text-xs text-gray-500 break-words"> | |
| 76 | + Utilisez les clés API pour accéder à l'API REST : <code className="bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded inline-block overflow-x-auto max-w-full">GET /api/v1/files</code> avec header <code className="bg-gray-100 dark:bg-white/10 px-1.5 py-0.5 rounded inline-block overflow-x-auto max-w-full">Authorization: Bearer spb_...</code> | |
| 77 | + </p> | |
| 78 | + | |
| 79 | + {loading ? ( | |
| 80 | + <div className="flex justify-center py-8"> | |
| 81 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 82 | + </div> | |
| 83 | + ) : keys.length === 0 ? ( | |
| 84 | + <div className="text-center text-gray-500 py-8"> | |
| 85 | + <Key className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" /> | |
| 86 | + <p>Aucune clé API</p> | |
| 87 | + </div> | |
| 88 | + ) : ( | |
| 89 | + <div className="space-y-2"> | |
| 90 | + {keys.map((k, i) => ( | |
| 91 | + <motion.div | |
| 92 | + key={k.id} | |
| 93 | + initial={{ opacity: 0, y: 5 }} | |
| 94 | + animate={{ opacity: 1, y: 0 }} | |
| 95 | + transition={{ delay: i * 0.03 }} | |
| 96 | + className="flex flex-col sm:flex-row sm:items-center gap-3 p-3 bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl transition-colors" | |
| 97 | + > | |
| 98 | + <div className="flex items-start sm:items-center gap-3 flex-1 min-w-0"> | |
| 99 | + <Key className="w-4 h-4 text-gray-400 flex-shrink-0 mt-0.5 sm:mt-0" /> | |
| 100 | + <div className="flex-1 min-w-0"> | |
| 101 | + <p className="text-sm font-medium text-gray-900 dark:text-white">{k.name}</p> | |
| 102 | + <p className="text-xs text-gray-500 break-all"> | |
| 103 | + <code>{k.keyPrefix}...</code> • {k.permissions} | |
| 104 | + {k.lastUsedAt && ` • Dernier usage: ${formatDate(k.lastUsedAt)}`} | |
| 105 | + </p> | |
| 106 | + </div> | |
| 107 | + </div> | |
| 108 | + <button | |
| 109 | + onClick={() => deleteKey(k.id)} | |
| 110 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 text-gray-400 hover:text-red-500 rounded-lg transition-colors self-end sm:self-auto" | |
| 111 | + > | |
| 112 | + <Trash2 className="w-4 h-4" /> | |
| 113 | + </button> | |
| 114 | + </motion.div> | |
| 115 | + ))} | |
| 116 | + </div> | |
| 117 | + )} | |
| 118 | + | |
| 119 | + {/* Create Modal */} | |
| 120 | + <Modal isOpen={showCreate} onClose={() => { setShowCreate(false); setNewKey(null); }} title="Nouvelle clé API"> | |
| 121 | + {newKey ? ( | |
| 122 | + <div className="space-y-4"> | |
| 123 | + <p className="text-sm text-gray-600 dark:text-gray-400"> | |
| 124 | + Voici votre clé API. Copiez-la maintenant, elle ne sera plus affichée. | |
| 125 | + </p> | |
| 126 | + <div className="flex items-center gap-2"> | |
| 127 | + <code className="flex-1 bg-gray-100 dark:bg-white/10 px-3 py-2 rounded-lg text-xs font-mono text-gray-700 dark:text-gray-300 break-all"> | |
| 128 | + {newKey} | |
| 129 | + </code> | |
| 130 | + <button | |
| 131 | + onClick={() => copyKey(newKey)} | |
| 132 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 text-gray-400 hover:text-gray-700 dark:hover:text-white rounded-lg transition-colors" | |
| 133 | + > | |
| 134 | + <Copy className="w-4 h-4" /> | |
| 135 | + </button> | |
| 136 | + </div> | |
| 137 | + <Button onClick={() => { setShowCreate(false); setNewKey(null); }} className="w-full"> | |
| 138 | + Fermer | |
| 139 | + </Button> | |
| 140 | + </div> | |
| 141 | + ) : ( | |
| 142 | + <div className="space-y-3"> | |
| 143 | + <input type="text" placeholder="Nom de la clé" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} className={inputClasses} /> | |
| 144 | + <select value={form.permissions} onChange={(e) => setForm({ ...form, permissions: e.target.value })} className={inputClasses}> | |
| 145 | + <option value="read">Lecture seule</option> | |
| 146 | + <option value="write">Lecture + Écriture</option> | |
| 147 | + <option value="admin">Admin</option> | |
| 148 | + </select> | |
| 149 | + <div className="flex gap-2 justify-end pt-2"> | |
| 150 | + <Button variant="ghost" onClick={() => setShowCreate(false)}>Annuler</Button> | |
| 151 | + <Button onClick={createKey} disabled={!form.name}>Créer</Button> | |
| 152 | + </div> | |
| 153 | + </div> | |
| 154 | + )} | |
| 155 | + </Modal> | |
| 156 | + </div> | |
| 157 | + ); | |
| 158 | +} | |
added
components/admin/BackupPanel.tsx
+139 −0
@@ -0,0 +1,139 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect } from "react"; | |
| 4 | +import { motion } from "framer-motion"; | |
| 5 | +import { HardDrive, Download, Trash2, RefreshCw, CheckCircle, XCircle, Loader2 } from "lucide-react"; | |
| 6 | +import Button from "../ui/Button"; | |
| 7 | +import { formatFileSize, formatDate } from "@/lib/utils"; | |
| 8 | +import toast from "react-hot-toast"; | |
| 9 | + | |
| 10 | +interface BackupData { | |
| 11 | + id: string; | |
| 12 | + status: string; | |
| 13 | + type: string; | |
| 14 | + size: number; | |
| 15 | + path: string | null; | |
| 16 | + startedAt: string; | |
| 17 | + completedAt: string | null; | |
| 18 | +} | |
| 19 | + | |
| 20 | +export default function BackupPanel() { | |
| 21 | + const [backups, setBackups] = useState<BackupData[]>([]); | |
| 22 | + const [loading, setLoading] = useState(true); | |
| 23 | + const [creating, setCreating] = useState(false); | |
| 24 | + | |
| 25 | + const fetchBackups = async () => { | |
| 26 | + const res = await fetch("/api/backup"); | |
| 27 | + if (res.ok) setBackups(await res.json()); | |
| 28 | + setLoading(false); | |
| 29 | + }; | |
| 30 | + | |
| 31 | + useEffect(() => { fetchBackups(); }, []); | |
| 32 | + | |
| 33 | + const createBackup = async (type: "full" | "incremental") => { | |
| 34 | + setCreating(true); | |
| 35 | + try { | |
| 36 | + const res = await fetch("/api/backup", { | |
| 37 | + method: "POST", | |
| 38 | + headers: { "Content-Type": "application/json" }, | |
| 39 | + body: JSON.stringify({ type }), | |
| 40 | + }); | |
| 41 | + if (res.ok) { | |
| 42 | + toast.success("Backup créé avec succès"); | |
| 43 | + fetchBackups(); | |
| 44 | + } else { | |
| 45 | + toast.error("Échec du backup"); | |
| 46 | + } | |
| 47 | + } catch { | |
| 48 | + toast.error("Erreur"); | |
| 49 | + } | |
| 50 | + setCreating(false); | |
| 51 | + }; | |
| 52 | + | |
| 53 | + const deleteBackup = async (id: string) => { | |
| 54 | + if (!confirm("Supprimer ce backup ?")) return; | |
| 55 | + const res = await fetch("/api/backup", { | |
| 56 | + method: "DELETE", | |
| 57 | + headers: { "Content-Type": "application/json" }, | |
| 58 | + body: JSON.stringify({ id }), | |
| 59 | + }); | |
| 60 | + if (res.ok) { | |
| 61 | + toast.success("Backup supprimé"); | |
| 62 | + fetchBackups(); | |
| 63 | + } | |
| 64 | + }; | |
| 65 | + | |
| 66 | + const statusIcon = (status: string) => { | |
| 67 | + if (status === "completed") return <CheckCircle className="w-4 h-4 text-green-500 flex-shrink-0" />; | |
| 68 | + if (status === "failed") return <XCircle className="w-4 h-4 text-red-500 flex-shrink-0" />; | |
| 69 | + return <Loader2 className="w-4 h-4 text-blue-500 animate-spin flex-shrink-0" />; | |
| 70 | + }; | |
| 71 | + | |
| 72 | + return ( | |
| 73 | + <div className="space-y-4"> | |
| 74 | + <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3"> | |
| 75 | + <h2 className="text-lg font-medium text-gray-900 dark:text-white flex items-center gap-2"> | |
| 76 | + <HardDrive className="w-5 h-5" /> Backups | |
| 77 | + </h2> | |
| 78 | + <div className="flex flex-col sm:flex-row gap-2"> | |
| 79 | + <Button size="sm" variant="secondary" onClick={() => createBackup("incremental")} loading={creating}> | |
| 80 | + Incrémental | |
| 81 | + </Button> | |
| 82 | + <Button size="sm" onClick={() => createBackup("full")} loading={creating}> | |
| 83 | + <Download className="w-4 h-4" /> Backup complet | |
| 84 | + </Button> | |
| 85 | + </div> | |
| 86 | + </div> | |
| 87 | + | |
| 88 | + {loading ? ( | |
| 89 | + <div className="flex justify-center py-8"> | |
| 90 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 91 | + </div> | |
| 92 | + ) : backups.length === 0 ? ( | |
| 93 | + <div className="text-center text-gray-500 py-8"> | |
| 94 | + <HardDrive className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" /> | |
| 95 | + <p>Aucun backup</p> | |
| 96 | + </div> | |
| 97 | + ) : ( | |
| 98 | + <div className="space-y-2"> | |
| 99 | + {backups.map((backup, i) => ( | |
| 100 | + <motion.div | |
| 101 | + key={backup.id} | |
| 102 | + initial={{ opacity: 0, y: 5 }} | |
| 103 | + animate={{ opacity: 1, y: 0 }} | |
| 104 | + transition={{ delay: i * 0.03 }} | |
| 105 | + className="flex flex-col sm:flex-row sm:items-center gap-3 p-3 bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl transition-colors" | |
| 106 | + > | |
| 107 | + <div className="flex items-start sm:items-center gap-3 flex-1 min-w-0"> | |
| 108 | + {statusIcon(backup.status)} | |
| 109 | + <div className="flex-1 min-w-0"> | |
| 110 | + <p className="text-sm text-gray-900 dark:text-white break-words"> | |
| 111 | + Backup {backup.type} - {formatDate(backup.startedAt)} | |
| 112 | + </p> | |
| 113 | + <p className="text-xs text-gray-500 break-words"> | |
| 114 | + {backup.size > 0 ? formatFileSize(backup.size) : "—"} | |
| 115 | + {backup.completedAt && ` • Terminé: ${formatDate(backup.completedAt)}`} | |
| 116 | + </p> | |
| 117 | + </div> | |
| 118 | + </div> | |
| 119 | + <div className="flex gap-1 pl-7 sm:pl-0"> | |
| 120 | + <button | |
| 121 | + onClick={() => fetchBackups()} | |
| 122 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 text-gray-400 hover:text-gray-700 dark:hover:text-white rounded-lg transition-colors" | |
| 123 | + > | |
| 124 | + <RefreshCw className="w-4 h-4" /> | |
| 125 | + </button> | |
| 126 | + <button | |
| 127 | + onClick={() => deleteBackup(backup.id)} | |
| 128 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 text-gray-400 hover:text-red-500 rounded-lg transition-colors" | |
| 129 | + > | |
| 130 | + <Trash2 className="w-4 h-4" /> | |
| 131 | + </button> | |
| 132 | + </div> | |
| 133 | + </motion.div> | |
| 134 | + ))} | |
| 135 | + </div> | |
| 136 | + )} | |
| 137 | + </div> | |
| 138 | + ); | |
| 139 | +} | |
added
components/admin/StatsCards.tsx
+79 −0
@@ -0,0 +1,79 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion } from "framer-motion"; | |
| 4 | +import { HardDrive, FileText, Share2, Folder } from "lucide-react"; | |
| 5 | +import { formatFileSize } from "@/lib/utils"; | |
| 6 | + | |
| 7 | +interface StatsData { | |
| 8 | + fileCount: number; | |
| 9 | + folderCount: number; | |
| 10 | + shareCount: number; | |
| 11 | + storageUsed: number; | |
| 12 | +} | |
| 13 | + | |
| 14 | +interface StatsCardsProps { | |
| 15 | + stats: StatsData; | |
| 16 | +} | |
| 17 | + | |
| 18 | +const cards = [ | |
| 19 | + { | |
| 20 | + key: "storageUsed", | |
| 21 | + label: "Stockage utilisé", | |
| 22 | + icon: HardDrive, | |
| 23 | + format: (v: number) => formatFileSize(v), | |
| 24 | + color: "text-blue-500 dark:text-blue-400", | |
| 25 | + bgColor: "bg-blue-100 dark:bg-blue-500/10", | |
| 26 | + }, | |
| 27 | + { | |
| 28 | + key: "fileCount", | |
| 29 | + label: "Fichiers", | |
| 30 | + icon: FileText, | |
| 31 | + format: (v: number) => String(v), | |
| 32 | + color: "text-green-500 dark:text-green-400", | |
| 33 | + bgColor: "bg-green-100 dark:bg-green-500/10", | |
| 34 | + }, | |
| 35 | + { | |
| 36 | + key: "folderCount", | |
| 37 | + label: "Dossiers", | |
| 38 | + icon: Folder, | |
| 39 | + format: (v: number) => String(v), | |
| 40 | + color: "text-purple-500 dark:text-purple-400", | |
| 41 | + bgColor: "bg-purple-100 dark:bg-purple-500/10", | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + key: "shareCount", | |
| 45 | + label: "Liens actifs", | |
| 46 | + icon: Share2, | |
| 47 | + format: (v: number) => String(v), | |
| 48 | + color: "text-orange-500 dark:text-orange-400", | |
| 49 | + bgColor: "bg-orange-100 dark:bg-orange-500/10", | |
| 50 | + }, | |
| 51 | +]; | |
| 52 | + | |
| 53 | +export default function StatsCards({ stats }: StatsCardsProps) { | |
| 54 | + return ( | |
| 55 | + <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4"> | |
| 56 | + {cards.map((card, i) => ( | |
| 57 | + <motion.div | |
| 58 | + key={card.key} | |
| 59 | + initial={{ opacity: 0, y: 10 }} | |
| 60 | + animate={{ opacity: 1, y: 0 }} | |
| 61 | + transition={{ delay: i * 0.1 }} | |
| 62 | + className="bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl p-3.5 sm:p-4 shadow-sm dark:shadow-none transition-colors" | |
| 63 | + > | |
| 64 | + <div className="flex items-center gap-3"> | |
| 65 | + <div className={`p-2 sm:p-2.5 rounded-lg ${card.bgColor}`}> | |
| 66 | + <card.icon className={`w-4 h-4 sm:w-5 sm:h-5 ${card.color}`} /> | |
| 67 | + </div> | |
| 68 | + <div> | |
| 69 | + <p className="text-xl sm:text-2xl font-semibold text-gray-900 dark:text-white"> | |
| 70 | + {card.format(stats[card.key as keyof StatsData] as number)} | |
| 71 | + </p> | |
| 72 | + <p className="text-[11px] sm:text-xs text-gray-500">{card.label}</p> | |
| 73 | + </div> | |
| 74 | + </div> | |
| 75 | + </motion.div> | |
| 76 | + ))} | |
| 77 | + </div> | |
| 78 | + ); | |
| 79 | +} | |
added
components/admin/StorageChart.tsx
+97 −0
@@ -0,0 +1,97 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion } from "framer-motion"; | |
| 4 | +import { formatFileSize } from "@/lib/utils"; | |
| 5 | + | |
| 6 | +interface TypeBreakdown { | |
| 7 | + type: string; | |
| 8 | + count: number; | |
| 9 | + size: number; | |
| 10 | +} | |
| 11 | + | |
| 12 | +interface StorageChartProps { | |
| 13 | + data: TypeBreakdown[]; | |
| 14 | + totalSize: number; | |
| 15 | +} | |
| 16 | + | |
| 17 | +const typeColors: Record<string, string> = { | |
| 18 | + "image/": "bg-blue-500", | |
| 19 | + "video/": "bg-purple-500", | |
| 20 | + "audio/": "bg-pink-500", | |
| 21 | + "application/pdf": "bg-red-500", | |
| 22 | + "text/": "bg-green-500", | |
| 23 | + "application/": "bg-yellow-500", | |
| 24 | +}; | |
| 25 | + | |
| 26 | +function getColor(type: string): string { | |
| 27 | + for (const [prefix, color] of Object.entries(typeColors)) { | |
| 28 | + if (type.startsWith(prefix)) return color; | |
| 29 | + } | |
| 30 | + return "bg-gray-500"; | |
| 31 | +} | |
| 32 | + | |
| 33 | +function getTypeLabel(type: string): string { | |
| 34 | + if (type.startsWith("image/")) return "Images"; | |
| 35 | + if (type.startsWith("video/")) return "Vidéos"; | |
| 36 | + if (type.startsWith("audio/")) return "Audio"; | |
| 37 | + if (type === "application/pdf") return "PDF"; | |
| 38 | + if (type.startsWith("text/")) return "Texte"; | |
| 39 | + return type.split("/")[1]?.toUpperCase() || "Autre"; | |
| 40 | +} | |
| 41 | + | |
| 42 | +export default function StorageChart({ data, totalSize }: StorageChartProps) { | |
| 43 | + // Group by category | |
| 44 | + const grouped = data.reduce<Record<string, { size: number; count: number }>>( | |
| 45 | + (acc, item) => { | |
| 46 | + const label = getTypeLabel(item.type); | |
| 47 | + if (!acc[label]) acc[label] = { size: 0, count: 0 }; | |
| 48 | + acc[label].size += item.size; | |
| 49 | + acc[label].count += item.count; | |
| 50 | + return acc; | |
| 51 | + }, | |
| 52 | + {} | |
| 53 | + ); | |
| 54 | + | |
| 55 | + const entries = Object.entries(grouped).sort(([, a], [, b]) => b.size - a.size); | |
| 56 | + | |
| 57 | + return ( | |
| 58 | + <div className="space-y-4"> | |
| 59 | + {/* Bar */} | |
| 60 | + <div className="h-4 rounded-full bg-gray-200 dark:bg-white/5 overflow-hidden flex"> | |
| 61 | + {entries.map(([label, { size }]) => { | |
| 62 | + const pct = totalSize > 0 ? (size / totalSize) * 100 : 0; | |
| 63 | + if (pct < 1) return null; | |
| 64 | + const matchType = data.find((d) => getTypeLabel(d.type) === label)?.type || ""; | |
| 65 | + return ( | |
| 66 | + <motion.div | |
| 67 | + key={label} | |
| 68 | + initial={{ width: 0 }} | |
| 69 | + animate={{ width: `${pct}%` }} | |
| 70 | + transition={{ duration: 0.5, delay: 0.2 }} | |
| 71 | + className={`h-full ${getColor(matchType)}`} | |
| 72 | + title={`${label}: ${formatFileSize(size)}`} | |
| 73 | + /> | |
| 74 | + ); | |
| 75 | + })} | |
| 76 | + </div> | |
| 77 | + | |
| 78 | + {/* Legend */} | |
| 79 | + <div className="grid grid-cols-2 sm:grid-cols-3 gap-3"> | |
| 80 | + {entries.map(([label, { size, count }]) => { | |
| 81 | + const matchType = data.find((d) => getTypeLabel(d.type) === label)?.type || ""; | |
| 82 | + return ( | |
| 83 | + <div key={label} className="flex items-center gap-2"> | |
| 84 | + <div className={`w-3 h-3 rounded-full ${getColor(matchType)}`} /> | |
| 85 | + <div> | |
| 86 | + <p className="text-xs text-gray-600 dark:text-gray-300">{label}</p> | |
| 87 | + <p className="text-xs text-gray-500"> | |
| 88 | + {formatFileSize(size)} ({count}) | |
| 89 | + </p> | |
| 90 | + </div> | |
| 91 | + </div> | |
| 92 | + ); | |
| 93 | + })} | |
| 94 | + </div> | |
| 95 | + </div> | |
| 96 | + ); | |
| 97 | +} | |
added
components/admin/TwoFactorSetup.tsx
+149 −0
@@ -0,0 +1,149 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import { Shield, ShieldOff, ShieldCheck } from "lucide-react"; | |
| 5 | +import Button from "../ui/Button"; | |
| 6 | +import toast from "react-hot-toast"; | |
| 7 | + | |
| 8 | +interface TwoFactorSetupProps { | |
| 9 | + enabled: boolean; | |
| 10 | + onStatusChange: () => void; | |
| 11 | +} | |
| 12 | + | |
| 13 | +export default function TwoFactorSetup({ enabled, onStatusChange }: TwoFactorSetupProps) { | |
| 14 | + const [qrCode, setQrCode] = useState<string | null>(null); | |
| 15 | + const [secret, setSecret] = useState<string | null>(null); | |
| 16 | + const [token, setToken] = useState(""); | |
| 17 | + const [loading, setLoading] = useState(false); | |
| 18 | + const [step, setStep] = useState<"idle" | "setup" | "verify">("idle"); | |
| 19 | + | |
| 20 | + const startSetup = async () => { | |
| 21 | + setLoading(true); | |
| 22 | + try { | |
| 23 | + const res = await fetch("/api/auth/2fa/setup", { method: "POST" }); | |
| 24 | + if (res.ok) { | |
| 25 | + const data = await res.json(); | |
| 26 | + setQrCode(data.qrCodeUrl); | |
| 27 | + setSecret(data.secret); | |
| 28 | + setStep("setup"); | |
| 29 | + } | |
| 30 | + } catch { | |
| 31 | + toast.error("Erreur"); | |
| 32 | + } | |
| 33 | + setLoading(false); | |
| 34 | + }; | |
| 35 | + | |
| 36 | + const verifyAndEnable = async () => { | |
| 37 | + setLoading(true); | |
| 38 | + try { | |
| 39 | + const res = await fetch("/api/auth/2fa/verify", { | |
| 40 | + method: "POST", | |
| 41 | + headers: { "Content-Type": "application/json" }, | |
| 42 | + body: JSON.stringify({ token, enable: true }), | |
| 43 | + }); | |
| 44 | + if (res.ok) { | |
| 45 | + toast.success("2FA activé !"); | |
| 46 | + setStep("idle"); | |
| 47 | + setQrCode(null); | |
| 48 | + setToken(""); | |
| 49 | + onStatusChange(); | |
| 50 | + } else { | |
| 51 | + const data = await res.json(); | |
| 52 | + toast.error(data.error || "Code invalide"); | |
| 53 | + } | |
| 54 | + } catch { | |
| 55 | + toast.error("Erreur"); | |
| 56 | + } | |
| 57 | + setLoading(false); | |
| 58 | + }; | |
| 59 | + | |
| 60 | + const disable2FA = async () => { | |
| 61 | + if (!confirm("Désactiver l'authentification à deux facteurs ?")) return; | |
| 62 | + const res = await fetch("/api/auth/2fa/disable", { method: "POST" }); | |
| 63 | + if (res.ok) { | |
| 64 | + toast.success("2FA désactivé"); | |
| 65 | + onStatusChange(); | |
| 66 | + } | |
| 67 | + }; | |
| 68 | + | |
| 69 | + const inputClasses = "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none text-center tracking-[0.3em] sm:tracking-[0.5em] text-xl"; | |
| 70 | + | |
| 71 | + return ( | |
| 72 | + <div className="bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl p-4 sm:p-6 transition-colors"> | |
| 73 | + <div className="flex items-center gap-3 mb-4"> | |
| 74 | + {enabled ? ( | |
| 75 | + <ShieldCheck className="w-6 h-6 text-green-500 flex-shrink-0" /> | |
| 76 | + ) : ( | |
| 77 | + <Shield className="w-6 h-6 text-gray-400 flex-shrink-0" /> | |
| 78 | + )} | |
| 79 | + <div> | |
| 80 | + <h3 className="text-sm font-medium text-gray-900 dark:text-white"> | |
| 81 | + Authentification à deux facteurs (2FA) | |
| 82 | + </h3> | |
| 83 | + <p className="text-xs text-gray-500 mt-0.5"> | |
| 84 | + {enabled ? "Activé - Votre compte est protégé" : "Désactivé - Activez pour plus de sécurité"} | |
| 85 | + </p> | |
| 86 | + </div> | |
| 87 | + </div> | |
| 88 | + | |
| 89 | + {step === "idle" && !enabled && ( | |
| 90 | + <Button size="sm" onClick={startSetup} loading={loading} className="w-full sm:w-auto"> | |
| 91 | + <Shield className="w-4 h-4" /> Activer 2FA | |
| 92 | + </Button> | |
| 93 | + )} | |
| 94 | + | |
| 95 | + {step === "idle" && enabled && ( | |
| 96 | + <Button size="sm" variant="danger" onClick={disable2FA} className="w-full sm:w-auto"> | |
| 97 | + <ShieldOff className="w-4 h-4" /> Désactiver 2FA | |
| 98 | + </Button> | |
| 99 | + )} | |
| 100 | + | |
| 101 | + {step === "setup" && qrCode && ( | |
| 102 | + <div className="space-y-4"> | |
| 103 | + <p className="text-sm text-gray-600 dark:text-gray-400"> | |
| 104 | + Scannez ce QR code avec votre app authenticator (Google Authenticator, Authy, etc.) | |
| 105 | + </p> | |
| 106 | + <div className="flex justify-center"> | |
| 107 | + <img src={qrCode} alt="QR Code 2FA" className="w-44 h-44 sm:w-48 sm:h-48 rounded-lg" /> | |
| 108 | + </div> | |
| 109 | + {secret && ( | |
| 110 | + <div className="text-center"> | |
| 111 | + <p className="text-xs text-gray-500 mb-1">Clé manuelle :</p> | |
| 112 | + <code className="text-xs bg-gray-100 dark:bg-white/10 px-3 py-1 rounded font-mono text-gray-700 dark:text-gray-300 break-all inline-block max-w-full"> | |
| 113 | + {secret} | |
| 114 | + </code> | |
| 115 | + </div> | |
| 116 | + )} | |
| 117 | + <Button size="sm" onClick={() => setStep("verify")} className="w-full"> | |
| 118 | + Suivant | |
| 119 | + </Button> | |
| 120 | + </div> | |
| 121 | + )} | |
| 122 | + | |
| 123 | + {step === "verify" && ( | |
| 124 | + <div className="space-y-4"> | |
| 125 | + <p className="text-sm text-gray-600 dark:text-gray-400"> | |
| 126 | + Entrez le code à 6 chiffres de votre app authenticator | |
| 127 | + </p> | |
| 128 | + <input | |
| 129 | + type="text" | |
| 130 | + value={token} | |
| 131 | + onChange={(e) => setToken(e.target.value.replace(/\D/g, "").slice(0, 6))} | |
| 132 | + placeholder="000000" | |
| 133 | + maxLength={6} | |
| 134 | + className={inputClasses} | |
| 135 | + autoFocus | |
| 136 | + /> | |
| 137 | + <div className="flex flex-col sm:flex-row gap-2"> | |
| 138 | + <Button variant="ghost" onClick={() => { setStep("idle"); setQrCode(null); }} className="w-full sm:flex-1"> | |
| 139 | + Annuler | |
| 140 | + </Button> | |
| 141 | + <Button onClick={verifyAndEnable} loading={loading} disabled={token.length !== 6} className="w-full sm:flex-1"> | |
| 142 | + Vérifier & Activer | |
| 143 | + </Button> | |
| 144 | + </div> | |
| 145 | + </div> | |
| 146 | + )} | |
| 147 | + </div> | |
| 148 | + ); | |
| 149 | +} | |
added
components/admin/UserManagement.tsx
+197 −0
@@ -0,0 +1,197 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect } from "react"; | |
| 4 | +import { motion } from "framer-motion"; | |
| 5 | +import { UserPlus, Trash2, Shield, Edit2, Users } from "lucide-react"; | |
| 6 | +import Button from "../ui/Button"; | |
| 7 | +import Modal from "../ui/Modal"; | |
| 8 | +import toast from "react-hot-toast"; | |
| 9 | + | |
| 10 | +interface UserData { | |
| 11 | + id: string; | |
| 12 | + email: string | null; | |
| 13 | + name: string | null; | |
| 14 | + role: string; | |
| 15 | + twoFactorEnabled: boolean; | |
| 16 | + createdAt: string; | |
| 17 | + _count?: { files: number; folders: number }; | |
| 18 | +} | |
| 19 | + | |
| 20 | +export default function UserManagement() { | |
| 21 | + const [users, setUsers] = useState<UserData[]>([]); | |
| 22 | + const [loading, setLoading] = useState(true); | |
| 23 | + const [showCreate, setShowCreate] = useState(false); | |
| 24 | + const [editUser, setEditUser] = useState<UserData | null>(null); | |
| 25 | + const [form, setForm] = useState({ email: "", name: "", password: "", role: "user" }); | |
| 26 | + | |
| 27 | + const fetchUsers = async () => { | |
| 28 | + const res = await fetch("/api/users"); | |
| 29 | + if (res.ok) setUsers(await res.json()); | |
| 30 | + setLoading(false); | |
| 31 | + }; | |
| 32 | + | |
| 33 | + useEffect(() => { fetchUsers(); }, []); | |
| 34 | + | |
| 35 | + const handleCreate = async () => { | |
| 36 | + const res = await fetch("/api/users", { | |
| 37 | + method: "POST", | |
| 38 | + headers: { "Content-Type": "application/json" }, | |
| 39 | + body: JSON.stringify(form), | |
| 40 | + }); | |
| 41 | + if (res.ok) { | |
| 42 | + toast.success("Utilisateur créé"); | |
| 43 | + setShowCreate(false); | |
| 44 | + setForm({ email: "", name: "", password: "", role: "user" }); | |
| 45 | + fetchUsers(); | |
| 46 | + } else { | |
| 47 | + const data = await res.json(); | |
| 48 | + toast.error(data.error || "Erreur"); | |
| 49 | + } | |
| 50 | + }; | |
| 51 | + | |
| 52 | + const handleUpdate = async () => { | |
| 53 | + if (!editUser) return; | |
| 54 | + const data: Record<string, string> = {}; | |
| 55 | + if (form.name) data.name = form.name; | |
| 56 | + if (form.email) data.email = form.email; | |
| 57 | + if (form.role) data.role = form.role; | |
| 58 | + if (form.password) data.password = form.password; | |
| 59 | + | |
| 60 | + const res = await fetch(`/api/users/${editUser.id}`, { | |
| 61 | + method: "PATCH", | |
| 62 | + headers: { "Content-Type": "application/json" }, | |
| 63 | + body: JSON.stringify(data), | |
| 64 | + }); | |
| 65 | + if (res.ok) { | |
| 66 | + toast.success("Utilisateur modifié"); | |
| 67 | + setEditUser(null); | |
| 68 | + fetchUsers(); | |
| 69 | + } | |
| 70 | + }; | |
| 71 | + | |
| 72 | + const handleDelete = async (id: string) => { | |
| 73 | + if (!confirm("Supprimer cet utilisateur ?")) return; | |
| 74 | + const res = await fetch(`/api/users/${id}`, { method: "DELETE" }); | |
| 75 | + if (res.ok) { | |
| 76 | + toast.success("Utilisateur supprimé"); | |
| 77 | + fetchUsers(); | |
| 78 | + } else { | |
| 79 | + const data = await res.json(); | |
| 80 | + toast.error(data.error || "Erreur"); | |
| 81 | + } | |
| 82 | + }; | |
| 83 | + | |
| 84 | + const inputClasses = "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-gray-400 dark:focus:border-white/20"; | |
| 85 | + | |
| 86 | + const roleColors: Record<string, string> = { | |
| 87 | + admin: "bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400", | |
| 88 | + user: "bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400", | |
| 89 | + viewer: "bg-gray-100 text-gray-700 dark:bg-gray-500/20 dark:text-gray-400", | |
| 90 | + }; | |
| 91 | + | |
| 92 | + return ( | |
| 93 | + <div className="space-y-4"> | |
| 94 | + <div className="flex items-center justify-between"> | |
| 95 | + <h2 className="text-lg font-medium text-gray-900 dark:text-white flex items-center gap-2"> | |
| 96 | + <Users className="w-5 h-5" /> Utilisateurs | |
| 97 | + </h2> | |
| 98 | + <Button size="sm" onClick={() => setShowCreate(true)}> | |
| 99 | + <UserPlus className="w-4 h-4" /> Ajouter | |
| 100 | + </Button> | |
| 101 | + </div> | |
| 102 | + | |
| 103 | + {loading ? ( | |
| 104 | + <div className="flex justify-center py-8"> | |
| 105 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 106 | + </div> | |
| 107 | + ) : ( | |
| 108 | + <div className="space-y-2"> | |
| 109 | + {users.map((user, i) => ( | |
| 110 | + <motion.div | |
| 111 | + key={user.id} | |
| 112 | + initial={{ opacity: 0, y: 5 }} | |
| 113 | + animate={{ opacity: 1, y: 0 }} | |
| 114 | + transition={{ delay: i * 0.05 }} | |
| 115 | + className="flex flex-col sm:flex-row sm:items-center gap-3 p-4 bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl transition-colors" | |
| 116 | + > | |
| 117 | + {/* First line: avatar + name/email */} | |
| 118 | + <div className="flex items-center gap-3 flex-1 min-w-0"> | |
| 119 | + <div className="w-10 h-10 rounded-full bg-gray-200 dark:bg-white/10 flex items-center justify-center text-sm font-medium text-gray-600 dark:text-gray-300 flex-shrink-0"> | |
| 120 | + {(user.name || user.email || "U")[0].toUpperCase()} | |
| 121 | + </div> | |
| 122 | + <div className="flex-1 min-w-0"> | |
| 123 | + <p className="text-sm font-medium text-gray-900 dark:text-white truncate"> | |
| 124 | + {user.name || user.email || user.id} | |
| 125 | + </p> | |
| 126 | + <p className="text-xs text-gray-500 truncate">{user.email}</p> | |
| 127 | + </div> | |
| 128 | + </div> | |
| 129 | + {/* Second line on mobile: role badge + file count + actions */} | |
| 130 | + <div className="flex items-center gap-2 sm:gap-3 pl-13 sm:pl-0"> | |
| 131 | + <span className={`px-2 py-0.5 rounded-full text-xs font-medium ${roleColors[user.role] || roleColors.user}`}> | |
| 132 | + {user.role} | |
| 133 | + </span> | |
| 134 | + {user.twoFactorEnabled && ( | |
| 135 | + <Shield className="w-4 h-4 text-green-500 flex-shrink-0" title="2FA activé" /> | |
| 136 | + )} | |
| 137 | + <span className="text-xs text-gray-500 hidden sm:block"> | |
| 138 | + {user._count?.files || 0} fichiers | |
| 139 | + </span> | |
| 140 | + <div className="flex gap-1 ml-auto"> | |
| 141 | + <button | |
| 142 | + onClick={() => { setEditUser(user); setForm({ email: user.email || "", name: user.name || "", password: "", role: user.role }); }} | |
| 143 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 text-gray-400 hover:text-gray-700 dark:hover:text-white rounded-lg transition-colors" | |
| 144 | + > | |
| 145 | + <Edit2 className="w-4 h-4" /> | |
| 146 | + </button> | |
| 147 | + <button | |
| 148 | + onClick={() => handleDelete(user.id)} | |
| 149 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 text-gray-400 hover:text-red-500 rounded-lg transition-colors" | |
| 150 | + > | |
| 151 | + <Trash2 className="w-4 h-4" /> | |
| 152 | + </button> | |
| 153 | + </div> | |
| 154 | + </div> | |
| 155 | + </motion.div> | |
| 156 | + ))} | |
| 157 | + </div> | |
| 158 | + )} | |
| 159 | + | |
| 160 | + {/* Create Modal */} | |
| 161 | + <Modal isOpen={showCreate} onClose={() => setShowCreate(false)} title="Nouvel utilisateur"> | |
| 162 | + <div className="space-y-3"> | |
| 163 | + <input type="email" placeholder="Email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} className={inputClasses} /> | |
| 164 | + <input type="text" placeholder="Nom" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} className={inputClasses} /> | |
| 165 | + <input type="password" placeholder="Mot de passe" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} className={inputClasses} /> | |
| 166 | + <select value={form.role} onChange={(e) => setForm({ ...form, role: e.target.value })} className={inputClasses}> | |
| 167 | + <option value="user">Utilisateur</option> | |
| 168 | + <option value="viewer">Lecteur</option> | |
| 169 | + <option value="admin">Admin</option> | |
| 170 | + </select> | |
| 171 | + <div className="flex gap-2 justify-end pt-2"> | |
| 172 | + <Button variant="ghost" onClick={() => setShowCreate(false)}>Annuler</Button> | |
| 173 | + <Button onClick={handleCreate}>Créer</Button> | |
| 174 | + </div> | |
| 175 | + </div> | |
| 176 | + </Modal> | |
| 177 | + | |
| 178 | + {/* Edit Modal */} | |
| 179 | + <Modal isOpen={!!editUser} onClose={() => setEditUser(null)} title="Modifier utilisateur"> | |
| 180 | + <div className="space-y-3"> | |
| 181 | + <input type="email" placeholder="Email" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} className={inputClasses} /> | |
| 182 | + <input type="text" placeholder="Nom" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} className={inputClasses} /> | |
| 183 | + <input type="password" placeholder="Nouveau mot de passe (vide = inchangé)" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} className={inputClasses} /> | |
| 184 | + <select value={form.role} onChange={(e) => setForm({ ...form, role: e.target.value })} className={inputClasses}> | |
| 185 | + <option value="user">Utilisateur</option> | |
| 186 | + <option value="viewer">Lecteur</option> | |
| 187 | + <option value="admin">Admin</option> | |
| 188 | + </select> | |
| 189 | + <div className="flex gap-2 justify-end pt-2"> | |
| 190 | + <Button variant="ghost" onClick={() => setEditUser(null)}>Annuler</Button> | |
| 191 | + <Button onClick={handleUpdate}>Sauvegarder</Button> | |
| 192 | + </div> | |
| 193 | + </div> | |
| 194 | + </Modal> | |
| 195 | + </div> | |
| 196 | + ); | |
| 197 | +} | |
added
components/ai/AiPanel.tsx
+240 −0
@@ -0,0 +1,240 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 5 | +import { Brain, Search, Tag, FileText, MessageSquare, Sparkles, X, Loader2 } from "lucide-react"; | |
| 6 | +import Button from "../ui/Button"; | |
| 7 | + | |
| 8 | +interface AiResult { | |
| 9 | + fileId: string; | |
| 10 | + name: string; | |
| 11 | + relevance: string; | |
| 12 | + snippet: string; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export default function AiPanel() { | |
| 16 | + const [open, setOpen] = useState(false); | |
| 17 | + const [tab, setTab] = useState<"search" | "chat">("search"); | |
| 18 | + const [query, setQuery] = useState(""); | |
| 19 | + const [chatMessage, setChatMessage] = useState(""); | |
| 20 | + const [searching, setSearching] = useState(false); | |
| 21 | + const [chatting, setChatting] = useState(false); | |
| 22 | + const [results, setResults] = useState<AiResult[]>([]); | |
| 23 | + const [answer, setAnswer] = useState(""); | |
| 24 | + const [chatHistory, setChatHistory] = useState<Array<{ role: string; content: string }>>([]); | |
| 25 | + | |
| 26 | + const handleSearch = async () => { | |
| 27 | + if (!query.trim()) return; | |
| 28 | + setSearching(true); | |
| 29 | + setResults([]); | |
| 30 | + setAnswer(""); | |
| 31 | + try { | |
| 32 | + const res = await fetch("/api/ai/search", { | |
| 33 | + method: "POST", | |
| 34 | + headers: { "Content-Type": "application/json" }, | |
| 35 | + body: JSON.stringify({ query }), | |
| 36 | + }); | |
| 37 | + if (res.ok) { | |
| 38 | + const data = await res.json(); | |
| 39 | + setResults(data.results || []); | |
| 40 | + setAnswer(data.answer || ""); | |
| 41 | + } | |
| 42 | + } catch {} | |
| 43 | + setSearching(false); | |
| 44 | + }; | |
| 45 | + | |
| 46 | + const handleChat = async () => { | |
| 47 | + if (!chatMessage.trim()) return; | |
| 48 | + const msg = chatMessage; | |
| 49 | + setChatMessage(""); | |
| 50 | + setChatHistory((prev) => [...prev, { role: "user", content: msg }]); | |
| 51 | + setChatting(true); | |
| 52 | + try { | |
| 53 | + const res = await fetch("/api/ai/chat", { | |
| 54 | + method: "POST", | |
| 55 | + headers: { "Content-Type": "application/json" }, | |
| 56 | + body: JSON.stringify({ message: msg }), | |
| 57 | + }); | |
| 58 | + if (res.ok) { | |
| 59 | + const data = await res.json(); | |
| 60 | + setChatHistory((prev) => [...prev, { role: "assistant", content: data.reply }]); | |
| 61 | + } | |
| 62 | + } catch {} | |
| 63 | + setChatting(false); | |
| 64 | + }; | |
| 65 | + | |
| 66 | + const relevanceColors: Record<string, string> = { | |
| 67 | + high: "bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-400", | |
| 68 | + medium: "bg-yellow-100 text-yellow-700 dark:bg-yellow-500/20 dark:text-yellow-400", | |
| 69 | + low: "bg-gray-100 text-gray-600 dark:bg-gray-500/20 dark:text-gray-400", | |
| 70 | + }; | |
| 71 | + | |
| 72 | + const inputClasses = "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-gray-400 dark:focus:border-white/20 placeholder-gray-400 dark:placeholder-gray-500"; | |
| 73 | + | |
| 74 | + return ( | |
| 75 | + <> | |
| 76 | + {/* AI Toggle Button - positioned above mobile safe area */} | |
| 77 | + <button | |
| 78 | + onClick={() => setOpen(true)} | |
| 79 | + className="fixed bottom-20 right-4 sm:bottom-6 sm:right-6 z-40 w-14 h-14 rounded-full bg-purple-600 hover:bg-purple-700 text-white shadow-lg flex items-center justify-center transition-colors" | |
| 80 | + title="Assistant IA" | |
| 81 | + > | |
| 82 | + <Brain className="w-6 h-6" /> | |
| 83 | + </button> | |
| 84 | + | |
| 85 | + {/* AI Panel */} | |
| 86 | + <AnimatePresence> | |
| 87 | + {open && ( | |
| 88 | + <> | |
| 89 | + <motion.div | |
| 90 | + initial={{ opacity: 0 }} | |
| 91 | + animate={{ opacity: 1 }} | |
| 92 | + exit={{ opacity: 0 }} | |
| 93 | + className="fixed inset-0 bg-black/30 z-40 lg:hidden" | |
| 94 | + onClick={() => setOpen(false)} | |
| 95 | + /> | |
| 96 | + <motion.div | |
| 97 | + initial={{ x: 400, opacity: 0 }} | |
| 98 | + animate={{ x: 0, opacity: 1 }} | |
| 99 | + exit={{ x: 400, opacity: 0 }} | |
| 100 | + transition={{ type: "spring", damping: 25, stiffness: 300 }} | |
| 101 | + className="fixed right-0 top-0 bottom-0 w-full sm:w-96 z-50 bg-white dark:bg-gray-900 border-l border-gray-200 dark:border-white/10 flex flex-col shadow-2xl pb-[env(safe-area-inset-bottom)]" | |
| 102 | + > | |
| 103 | + {/* Header */} | |
| 104 | + <div className="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-white/10 pt-[max(0.75rem,env(safe-area-inset-top))]"> | |
| 105 | + <h2 className="text-sm font-semibold text-gray-900 dark:text-white flex items-center gap-2"> | |
| 106 | + <Sparkles className="w-4 h-4 text-purple-500" /> Assistant IA | |
| 107 | + </h2> | |
| 108 | + <button | |
| 109 | + onClick={() => setOpen(false)} | |
| 110 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center p-2 text-gray-400 hover:text-gray-700 dark:hover:text-white" | |
| 111 | + > | |
| 112 | + <X className="w-5 h-5" /> | |
| 113 | + </button> | |
| 114 | + </div> | |
| 115 | + | |
| 116 | + {/* Tabs */} | |
| 117 | + <div className="flex border-b border-gray-200 dark:border-white/10"> | |
| 118 | + <button | |
| 119 | + onClick={() => setTab("search")} | |
| 120 | + className={`flex-1 px-4 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-1.5 ${tab === "search" ? "text-purple-600 dark:text-purple-400 border-b-2 border-purple-500" : "text-gray-500"}`} | |
| 121 | + > | |
| 122 | + <Search className="w-4 h-4" /> Recherche IA | |
| 123 | + </button> | |
| 124 | + <button | |
| 125 | + onClick={() => setTab("chat")} | |
| 126 | + className={`flex-1 px-4 py-3 text-sm font-medium transition-colors flex items-center justify-center gap-1.5 ${tab === "chat" ? "text-purple-600 dark:text-purple-400 border-b-2 border-purple-500" : "text-gray-500"}`} | |
| 127 | + > | |
| 128 | + <MessageSquare className="w-4 h-4" /> Chat | |
| 129 | + </button> | |
| 130 | + </div> | |
| 131 | + | |
| 132 | + {/* Content */} | |
| 133 | + <div className="flex-1 overflow-auto p-4"> | |
| 134 | + {tab === "search" ? ( | |
| 135 | + <div className="space-y-4"> | |
| 136 | + <div className="flex gap-2"> | |
| 137 | + <input | |
| 138 | + type="text" | |
| 139 | + value={query} | |
| 140 | + onChange={(e) => setQuery(e.target.value)} | |
| 141 | + onKeyDown={(e) => e.key === "Enter" && handleSearch()} | |
| 142 | + placeholder="Rechercher avec l'IA..." | |
| 143 | + className={`${inputClasses} flex-1`} | |
| 144 | + /> | |
| 145 | + <Button size="sm" onClick={handleSearch} loading={searching}> | |
| 146 | + <Search className="w-4 h-4" /> | |
| 147 | + </Button> | |
| 148 | + </div> | |
| 149 | + | |
| 150 | + {answer && ( | |
| 151 | + <div className="bg-purple-50 dark:bg-purple-500/10 border border-purple-200 dark:border-purple-500/20 rounded-lg p-3.5"> | |
| 152 | + <p className="text-sm text-purple-800 dark:text-purple-300">{answer}</p> | |
| 153 | + </div> | |
| 154 | + )} | |
| 155 | + | |
| 156 | + {results.map((r) => ( | |
| 157 | + <div key={r.fileId} className="flex items-start gap-3 p-3.5 bg-gray-50 dark:bg-white/5 rounded-lg"> | |
| 158 | + <FileText className="w-4 h-4 text-gray-400 mt-0.5 flex-shrink-0" /> | |
| 159 | + <div className="flex-1 min-w-0"> | |
| 160 | + <div className="flex items-center gap-2"> | |
| 161 | + <p className="text-sm font-medium text-gray-900 dark:text-white truncate">{r.name}</p> | |
| 162 | + <span className={`px-1.5 py-0.5 rounded text-[10px] font-medium flex-shrink-0 ${relevanceColors[r.relevance] || relevanceColors.low}`}> | |
| 163 | + {r.relevance} | |
| 164 | + </span> | |
| 165 | + </div> | |
| 166 | + <p className="text-xs text-gray-500 mt-0.5">{r.snippet}</p> | |
| 167 | + </div> | |
| 168 | + </div> | |
| 169 | + ))} | |
| 170 | + | |
| 171 | + {/* Quick actions */} | |
| 172 | + <div className="pt-2 space-y-2"> | |
| 173 | + <p className="text-xs text-gray-500 font-medium uppercase tracking-wider">Actions rapides</p> | |
| 174 | + <div className="grid grid-cols-2 gap-2"> | |
| 175 | + <button onClick={() => { setQuery("documents récents"); handleSearch(); }} className="text-left p-3 text-xs text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-white/5 rounded-lg hover:bg-gray-100 dark:hover:bg-white/10"> | |
| 176 | + <FileText className="w-3 h-3 mb-1" /> Documents récents | |
| 177 | + </button> | |
| 178 | + <button onClick={() => { setQuery("images et photos"); handleSearch(); }} className="text-left p-3 text-xs text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-white/5 rounded-lg hover:bg-gray-100 dark:hover:bg-white/10"> | |
| 179 | + <Tag className="w-3 h-3 mb-1" /> Images & Photos | |
| 180 | + </button> | |
| 181 | + </div> | |
| 182 | + </div> | |
| 183 | + </div> | |
| 184 | + ) : ( | |
| 185 | + <div className="flex flex-col h-full"> | |
| 186 | + <div className="flex-1 space-y-3 mb-4"> | |
| 187 | + {chatHistory.length === 0 && ( | |
| 188 | + <div className="text-center text-gray-500 py-8"> | |
| 189 | + <Brain className="w-8 h-8 mx-auto mb-2 text-gray-300 dark:text-gray-600" /> | |
| 190 | + <p className="text-sm">Posez une question sur vos fichiers</p> | |
| 191 | + </div> | |
| 192 | + )} | |
| 193 | + {chatHistory.map((msg, i) => ( | |
| 194 | + <div key={i} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}> | |
| 195 | + <div className={`max-w-[80%] px-3 py-2 rounded-lg text-sm ${ | |
| 196 | + msg.role === "user" | |
| 197 | + ? "bg-purple-600 text-white" | |
| 198 | + : "bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-gray-200" | |
| 199 | + }`}> | |
| 200 | + {msg.content} | |
| 201 | + </div> | |
| 202 | + </div> | |
| 203 | + ))} | |
| 204 | + {chatting && ( | |
| 205 | + <div className="flex justify-start"> | |
| 206 | + <div className="bg-gray-100 dark:bg-white/10 px-3 py-2 rounded-lg"> | |
| 207 | + <Loader2 className="w-4 h-4 animate-spin text-purple-500" /> | |
| 208 | + </div> | |
| 209 | + </div> | |
| 210 | + )} | |
| 211 | + </div> | |
| 212 | + </div> | |
| 213 | + )} | |
| 214 | + </div> | |
| 215 | + | |
| 216 | + {/* Chat input */} | |
| 217 | + {tab === "chat" && ( | |
| 218 | + <div className="p-4 border-t border-gray-200 dark:border-white/10"> | |
| 219 | + <div className="flex gap-2"> | |
| 220 | + <input | |
| 221 | + type="text" | |
| 222 | + value={chatMessage} | |
| 223 | + onChange={(e) => setChatMessage(e.target.value)} | |
| 224 | + onKeyDown={(e) => e.key === "Enter" && handleChat()} | |
| 225 | + placeholder="Votre message..." | |
| 226 | + className={`${inputClasses} flex-1`} | |
| 227 | + /> | |
| 228 | + <Button size="sm" onClick={handleChat} loading={chatting}> | |
| 229 | + <MessageSquare className="w-4 h-4" /> | |
| 230 | + </Button> | |
| 231 | + </div> | |
| 232 | + </div> | |
| 233 | + )} | |
| 234 | + </motion.div> | |
| 235 | + </> | |
| 236 | + )} | |
| 237 | + </AnimatePresence> | |
| 238 | + </> | |
| 239 | + ); | |
| 240 | +} | |
added
components/files/BulkActions.tsx
+127 −0
@@ -0,0 +1,127 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import { Trash2, FolderInput, Download, Lock, Unlock, X, Sparkles } from "lucide-react"; | |
| 5 | +import Button from "../ui/Button"; | |
| 6 | +import Modal from "../ui/Modal"; | |
| 7 | +import toast from "react-hot-toast"; | |
| 8 | + | |
| 9 | +interface BulkActionsProps { | |
| 10 | + selectedIds: string[]; | |
| 11 | + onClear: () => void; | |
| 12 | + onRefresh: () => void; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export default function BulkActions({ selectedIds, onClear, onRefresh }: BulkActionsProps) { | |
| 16 | + const [moveModal, setMoveModal] = useState(false); | |
| 17 | + const [folders, setFolders] = useState<Array<{ id: string; name: string }>>([]); | |
| 18 | + const [loading, setLoading] = useState(false); | |
| 19 | + | |
| 20 | + const bulkAction = async (action: string, extra?: Record<string, unknown>) => { | |
| 21 | + setLoading(true); | |
| 22 | + try { | |
| 23 | + const res = await fetch("/api/files/bulk", { | |
| 24 | + method: "POST", | |
| 25 | + headers: { "Content-Type": "application/json" }, | |
| 26 | + body: JSON.stringify({ action, fileIds: selectedIds, ...extra }), | |
| 27 | + }); | |
| 28 | + | |
| 29 | + if (action === "download" && res.ok) { | |
| 30 | + const blob = await res.blob(); | |
| 31 | + const url = URL.createObjectURL(blob); | |
| 32 | + const a = document.createElement("a"); | |
| 33 | + a.href = url; | |
| 34 | + a.download = "files.zip"; | |
| 35 | + a.click(); | |
| 36 | + URL.revokeObjectURL(url); | |
| 37 | + toast.success("Téléchargement lancé"); | |
| 38 | + } else if (res.ok) { | |
| 39 | + const data = await res.json(); | |
| 40 | + toast.success(`Action terminée (${data.count || selectedIds.length} fichiers)`); | |
| 41 | + onRefresh(); | |
| 42 | + onClear(); | |
| 43 | + } else { | |
| 44 | + toast.error("Erreur"); | |
| 45 | + } | |
| 46 | + } catch { | |
| 47 | + toast.error("Erreur"); | |
| 48 | + } | |
| 49 | + setLoading(false); | |
| 50 | + }; | |
| 51 | + | |
| 52 | + const openMoveModal = async () => { | |
| 53 | + const res = await fetch("/api/folders?tree=true"); | |
| 54 | + if (res.ok) setFolders(await res.json()); | |
| 55 | + setMoveModal(true); | |
| 56 | + }; | |
| 57 | + | |
| 58 | + const handleMove = async (targetFolderId: string) => { | |
| 59 | + await bulkAction("move", { targetFolderId }); | |
| 60 | + setMoveModal(false); | |
| 61 | + }; | |
| 62 | + | |
| 63 | + const aiTagAll = async () => { | |
| 64 | + setLoading(true); | |
| 65 | + for (const id of selectedIds) { | |
| 66 | + try { | |
| 67 | + await fetch("/api/ai/tag", { | |
| 68 | + method: "POST", | |
| 69 | + headers: { "Content-Type": "application/json" }, | |
| 70 | + body: JSON.stringify({ fileId: id }), | |
| 71 | + }); | |
| 72 | + } catch {} | |
| 73 | + } | |
| 74 | + toast.success("Tags IA générés"); | |
| 75 | + setLoading(false); | |
| 76 | + onRefresh(); | |
| 77 | + }; | |
| 78 | + | |
| 79 | + if (selectedIds.length === 0) return null; | |
| 80 | + | |
| 81 | + return ( | |
| 82 | + <> | |
| 83 | + <div className="flex items-center gap-1.5 sm:gap-2 px-4 py-2 bg-blue-50 dark:bg-blue-500/10 border-b border-blue-200 dark:border-blue-500/20 overflow-x-auto"> | |
| 84 | + <span className="text-sm text-blue-700 dark:text-blue-300 font-medium whitespace-nowrap flex-shrink-0"> | |
| 85 | + {selectedIds.length} sélectionné{selectedIds.length > 1 ? "s" : ""} | |
| 86 | + </span> | |
| 87 | + <div className="flex-1 min-w-0" /> | |
| 88 | + <div className="flex items-center gap-1.5 flex-nowrap flex-shrink-0"> | |
| 89 | + <Button size="sm" variant="ghost" onClick={() => bulkAction("download")} loading={loading} className="min-h-[40px] whitespace-nowrap"> | |
| 90 | + <Download className="w-3.5 h-3.5" /> <span className="hidden sm:inline">ZIP</span> | |
| 91 | + </Button> | |
| 92 | + <Button size="sm" variant="ghost" onClick={openMoveModal} className="min-h-[40px] whitespace-nowrap"> | |
| 93 | + <FolderInput className="w-3.5 h-3.5" /> <span className="hidden sm:inline">Déplacer</span> | |
| 94 | + </Button> | |
| 95 | + <Button size="sm" variant="ghost" onClick={() => bulkAction("encrypt")} loading={loading} className="min-h-[40px] whitespace-nowrap"> | |
| 96 | + <Lock className="w-3.5 h-3.5" /> <span className="hidden sm:inline">Chiffrer</span> | |
| 97 | + </Button> | |
| 98 | + <Button size="sm" variant="ghost" onClick={() => bulkAction("decrypt")} loading={loading} className="min-h-[40px] whitespace-nowrap"> | |
| 99 | + <Unlock className="w-3.5 h-3.5" /> <span className="hidden sm:inline">Déchiffrer</span> | |
| 100 | + </Button> | |
| 101 | + <Button size="sm" variant="ghost" onClick={aiTagAll} loading={loading} className="min-h-[40px] whitespace-nowrap"> | |
| 102 | + <Sparkles className="w-3.5 h-3.5" /> <span className="hidden sm:inline">Tags IA</span> | |
| 103 | + </Button> | |
| 104 | + <Button size="sm" variant="danger" onClick={() => { if (confirm(`Supprimer ${selectedIds.length} fichier(s) ?`)) bulkAction("delete"); }} loading={loading} className="min-h-[40px] whitespace-nowrap"> | |
| 105 | + <Trash2 className="w-3.5 h-3.5" /> <span className="hidden sm:inline">Supprimer</span> | |
| 106 | + </Button> | |
| 107 | + </div> | |
| 108 | + <button onClick={onClear} className="min-w-[44px] min-h-[44px] flex items-center justify-center text-gray-400 hover:text-gray-700 dark:hover:text-white flex-shrink-0"> | |
| 109 | + <X className="w-4 h-4" /> | |
| 110 | + </button> | |
| 111 | + </div> | |
| 112 | + | |
| 113 | + <Modal isOpen={moveModal} onClose={() => setMoveModal(false)} title="Déplacer vers"> | |
| 114 | + <div className="space-y-1 max-h-60 overflow-y-auto"> | |
| 115 | + <button onClick={() => handleMove("root")} className="w-full text-left px-3 py-2.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg"> | |
| 116 | + Racine | |
| 117 | + </button> | |
| 118 | + {folders.map((f: { id: string; name: string }) => ( | |
| 119 | + <button key={f.id} onClick={() => handleMove(f.id)} className="w-full text-left px-3 py-2.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg"> | |
| 120 | + {f.name} | |
| 121 | + </button> | |
| 122 | + ))} | |
| 123 | + </div> | |
| 124 | + </Modal> | |
| 125 | + </> | |
| 126 | + ); | |
| 127 | +} | |
added
components/files/FileActions.tsx
+348 −0
@@ -0,0 +1,348 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import { motion } from "framer-motion"; | |
| 5 | +import { | |
| 6 | + Download, | |
| 7 | + Pencil, | |
| 8 | + Trash2, | |
| 9 | + Copy, | |
| 10 | + Share2, | |
| 11 | + FolderInput, | |
| 12 | +} from "lucide-react"; | |
| 13 | +import toast from "react-hot-toast"; | |
| 14 | +import Modal from "../ui/Modal"; | |
| 15 | +import Button from "../ui/Button"; | |
| 16 | + | |
| 17 | +interface FileData { | |
| 18 | + id: string; | |
| 19 | + name: string; | |
| 20 | + size: number; | |
| 21 | + mimeType: string; | |
| 22 | + folderId?: string | null; | |
| 23 | +} | |
| 24 | + | |
| 25 | +interface FileActionsProps { | |
| 26 | + file: FileData; | |
| 27 | + onClose: () => void; | |
| 28 | + onRefresh: () => void; | |
| 29 | +} | |
| 30 | + | |
| 31 | +export default function FileActions({ file, onClose, onRefresh }: FileActionsProps) { | |
| 32 | + const [renameModal, setRenameModal] = useState(false); | |
| 33 | + const [shareModal, setShareModal] = useState(false); | |
| 34 | + const [moveModal, setMoveModal] = useState(false); | |
| 35 | + const [deleteModal, setDeleteModal] = useState(false); | |
| 36 | + const [newName, setNewName] = useState(file.name); | |
| 37 | + const [sharePassword, setSharePassword] = useState(""); | |
| 38 | + const [shareExpiry, setShareExpiry] = useState(""); | |
| 39 | + const [shareMode, setShareMode] = useState<"DOWNLOAD" | "PREVIEW">("DOWNLOAD"); | |
| 40 | + const [loading, setLoading] = useState(false); | |
| 41 | + const [folders, setFolders] = useState<{ id: string; name: string }[]>([]); | |
| 42 | + | |
| 43 | + const handleDownload = () => { | |
| 44 | + window.open(`/api/files/${file.id}/download`, "_blank"); | |
| 45 | + onClose(); | |
| 46 | + }; | |
| 47 | + | |
| 48 | + const handleRename = async () => { | |
| 49 | + setLoading(true); | |
| 50 | + try { | |
| 51 | + const res = await fetch(`/api/files/${file.id}`, { | |
| 52 | + method: "PATCH", | |
| 53 | + headers: { "Content-Type": "application/json" }, | |
| 54 | + body: JSON.stringify({ name: newName }), | |
| 55 | + }); | |
| 56 | + if (res.ok) { | |
| 57 | + toast.success("Fichier renommé"); | |
| 58 | + onRefresh(); | |
| 59 | + } | |
| 60 | + } catch { | |
| 61 | + toast.error("Erreur"); | |
| 62 | + } finally { | |
| 63 | + setLoading(false); | |
| 64 | + setRenameModal(false); | |
| 65 | + onClose(); | |
| 66 | + } | |
| 67 | + }; | |
| 68 | + | |
| 69 | + const handleDuplicate = async () => { | |
| 70 | + try { | |
| 71 | + const res = await fetch(`/api/files/${file.id}/duplicate`, { | |
| 72 | + method: "POST", | |
| 73 | + }); | |
| 74 | + if (res.ok) { | |
| 75 | + toast.success("Fichier dupliqué"); | |
| 76 | + onRefresh(); | |
| 77 | + } | |
| 78 | + } catch { | |
| 79 | + toast.error("Erreur"); | |
| 80 | + } | |
| 81 | + onClose(); | |
| 82 | + }; | |
| 83 | + | |
| 84 | + const handleDelete = async () => { | |
| 85 | + setLoading(true); | |
| 86 | + try { | |
| 87 | + const res = await fetch(`/api/files/${file.id}`, { | |
| 88 | + method: "DELETE", | |
| 89 | + }); | |
| 90 | + if (res.ok) { | |
| 91 | + toast.success("Fichier supprimé"); | |
| 92 | + onRefresh(); | |
| 93 | + } | |
| 94 | + } catch { | |
| 95 | + toast.error("Erreur"); | |
| 96 | + } finally { | |
| 97 | + setLoading(false); | |
| 98 | + setDeleteModal(false); | |
| 99 | + onClose(); | |
| 100 | + } | |
| 101 | + }; | |
| 102 | + | |
| 103 | + const [shareLink, setShareLink] = useState<string | null>(null); | |
| 104 | + | |
| 105 | + const copyToClipboard = async (text: string) => { | |
| 106 | + try { | |
| 107 | + await navigator.clipboard.writeText(text); | |
| 108 | + return true; | |
| 109 | + } catch { | |
| 110 | + // Fallback for non-HTTPS contexts | |
| 111 | + const textarea = document.createElement("textarea"); | |
| 112 | + textarea.value = text; | |
| 113 | + textarea.style.position = "fixed"; | |
| 114 | + textarea.style.opacity = "0"; | |
| 115 | + document.body.appendChild(textarea); | |
| 116 | + textarea.select(); | |
| 117 | + try { | |
| 118 | + document.execCommand("copy"); | |
| 119 | + return true; | |
| 120 | + } catch { | |
| 121 | + return false; | |
| 122 | + } finally { | |
| 123 | + document.body.removeChild(textarea); | |
| 124 | + } | |
| 125 | + } | |
| 126 | + }; | |
| 127 | + | |
| 128 | + const handleShare = async () => { | |
| 129 | + setLoading(true); | |
| 130 | + try { | |
| 131 | + const res = await fetch("/api/shares", { | |
| 132 | + method: "POST", | |
| 133 | + headers: { "Content-Type": "application/json" }, | |
| 134 | + body: JSON.stringify({ | |
| 135 | + fileId: file.id, | |
| 136 | + password: sharePassword || undefined, | |
| 137 | + expiresAt: shareExpiry || undefined, | |
| 138 | + mode: shareMode, | |
| 139 | + }), | |
| 140 | + }); | |
| 141 | + if (res.ok) { | |
| 142 | + const data = await res.json(); | |
| 143 | + const url = `${window.location.origin}/shared/${data.token}`; | |
| 144 | + setShareLink(url); | |
| 145 | + const copied = await copyToClipboard(url); | |
| 146 | + if (copied) { | |
| 147 | + toast.success("Lien copié dans le presse-papiers"); | |
| 148 | + } else { | |
| 149 | + toast.success("Lien de partage créé"); | |
| 150 | + } | |
| 151 | + } else { | |
| 152 | + const data = await res.json(); | |
| 153 | + toast.error(data.error || "Erreur lors du partage"); | |
| 154 | + } | |
| 155 | + } catch (err) { | |
| 156 | + toast.error("Erreur réseau: " + (err as Error).message); | |
| 157 | + } finally { | |
| 158 | + setLoading(false); | |
| 159 | + } | |
| 160 | + }; | |
| 161 | + | |
| 162 | + const handleMove = async (targetFolderId: string) => { | |
| 163 | + try { | |
| 164 | + const res = await fetch(`/api/files/${file.id}`, { | |
| 165 | + method: "PATCH", | |
| 166 | + headers: { "Content-Type": "application/json" }, | |
| 167 | + body: JSON.stringify({ folderId: targetFolderId }), | |
| 168 | + }); | |
| 169 | + if (res.ok) { | |
| 170 | + toast.success("Fichier déplacé"); | |
| 171 | + onRefresh(); | |
| 172 | + } | |
| 173 | + } catch { | |
| 174 | + toast.error("Erreur"); | |
| 175 | + } | |
| 176 | + setMoveModal(false); | |
| 177 | + onClose(); | |
| 178 | + }; | |
| 179 | + | |
| 180 | + const loadFolders = async () => { | |
| 181 | + const res = await fetch("/api/folders?tree=true"); | |
| 182 | + if (res.ok) { | |
| 183 | + const data = await res.json(); | |
| 184 | + setFolders(data); | |
| 185 | + } | |
| 186 | + }; | |
| 187 | + | |
| 188 | + const actions = [ | |
| 189 | + { icon: Download, label: "Télécharger", action: handleDownload }, | |
| 190 | + { icon: Pencil, label: "Renommer", action: () => setRenameModal(true) }, | |
| 191 | + { icon: Copy, label: "Dupliquer", action: handleDuplicate }, | |
| 192 | + { | |
| 193 | + icon: FolderInput, | |
| 194 | + label: "Déplacer", | |
| 195 | + action: () => { | |
| 196 | + loadFolders(); | |
| 197 | + setMoveModal(true); | |
| 198 | + }, | |
| 199 | + }, | |
| 200 | + { icon: Share2, label: "Partager", action: () => setShareModal(true) }, | |
| 201 | + { icon: Trash2, label: "Supprimer", action: () => setDeleteModal(true), danger: true }, | |
| 202 | + ]; | |
| 203 | + | |
| 204 | + const inputClasses = "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none focus:border-gray-400 dark:focus:border-white/20"; | |
| 205 | + | |
| 206 | + return ( | |
| 207 | + <> | |
| 208 | + <motion.div | |
| 209 | + initial={{ opacity: 0, scale: 0.95 }} | |
| 210 | + animate={{ opacity: 1, scale: 1 }} | |
| 211 | + className="absolute right-0 top-full mt-1 w-48 bg-white dark:bg-gray-900 border border-gray-200 dark:border-white/10 rounded-xl shadow-2xl z-50 py-1 overflow-hidden max-h-[70vh] overflow-auto" | |
| 212 | + onClick={(e) => e.stopPropagation()} | |
| 213 | + > | |
| 214 | + {actions.map((action) => ( | |
| 215 | + <button | |
| 216 | + key={action.label} | |
| 217 | + onClick={(e) => { | |
| 218 | + e.stopPropagation(); | |
| 219 | + action.action(); | |
| 220 | + }} | |
| 221 | + className={`flex items-center gap-3 w-full px-4 py-3 text-sm transition-colors ${ | |
| 222 | + (action as { danger?: boolean }).danger | |
| 223 | + ? "text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10" | |
| 224 | + : "text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5" | |
| 225 | + }`} | |
| 226 | + > | |
| 227 | + <action.icon className="w-4 h-4" /> | |
| 228 | + {action.label} | |
| 229 | + </button> | |
| 230 | + ))} | |
| 231 | + </motion.div> | |
| 232 | + | |
| 233 | + {/* Rename Modal */} | |
| 234 | + <Modal isOpen={renameModal} onClose={() => setRenameModal(false)} title="Renommer"> | |
| 235 | + <input | |
| 236 | + type="text" | |
| 237 | + value={newName} | |
| 238 | + onChange={(e) => setNewName(e.target.value)} | |
| 239 | + className={`${inputClasses} mb-4`} | |
| 240 | + autoFocus | |
| 241 | + /> | |
| 242 | + <div className="flex gap-2 justify-end"> | |
| 243 | + <Button variant="ghost" onClick={() => setRenameModal(false)}>Annuler</Button> | |
| 244 | + <Button onClick={handleRename} loading={loading}>Renommer</Button> | |
| 245 | + </div> | |
| 246 | + </Modal> | |
| 247 | + | |
| 248 | + {/* Delete Modal */} | |
| 249 | + <Modal isOpen={deleteModal} onClose={() => setDeleteModal(false)} title="Supprimer"> | |
| 250 | + <p className="text-gray-500 dark:text-gray-400 text-sm mb-4"> | |
| 251 | + Supprimer <strong className="text-gray-900 dark:text-white">{file.name}</strong> ? Cette action est irréversible. | |
| 252 | + </p> | |
| 253 | + <div className="flex gap-2 justify-end"> | |
| 254 | + <Button variant="ghost" onClick={() => setDeleteModal(false)}>Annuler</Button> | |
| 255 | + <Button variant="danger" onClick={handleDelete} loading={loading}>Supprimer</Button> | |
| 256 | + </div> | |
| 257 | + </Modal> | |
| 258 | + | |
| 259 | + {/* Share Modal */} | |
| 260 | + <Modal isOpen={shareModal} onClose={() => { setShareModal(false); setShareLink(null); onClose(); }} title="Partager"> | |
| 261 | + {shareLink ? ( | |
| 262 | + <div className="space-y-4"> | |
| 263 | + <p className="text-sm text-green-600 dark:text-green-400 font-medium">Lien de partage créé !</p> | |
| 264 | + <div className="flex items-center gap-2"> | |
| 265 | + <input | |
| 266 | + type="text" | |
| 267 | + value={shareLink} | |
| 268 | + readOnly | |
| 269 | + className={`${inputClasses} text-xs font-mono`} | |
| 270 | + onClick={(e) => (e.target as HTMLInputElement).select()} | |
| 271 | + /> | |
| 272 | + <Button | |
| 273 | + size="sm" | |
| 274 | + onClick={() => { | |
| 275 | + copyToClipboard(shareLink); | |
| 276 | + toast.success("Lien copié !"); | |
| 277 | + }} | |
| 278 | + > | |
| 279 | + Copier | |
| 280 | + </Button> | |
| 281 | + </div> | |
| 282 | + <Button variant="ghost" onClick={() => { setShareModal(false); setShareLink(null); onClose(); }} className="w-full"> | |
| 283 | + Fermer | |
| 284 | + </Button> | |
| 285 | + </div> | |
| 286 | + ) : ( | |
| 287 | + <div className="space-y-4"> | |
| 288 | + <div> | |
| 289 | + <label className="text-xs text-gray-500 dark:text-gray-400 block mb-1">Mode</label> | |
| 290 | + <select | |
| 291 | + value={shareMode} | |
| 292 | + onChange={(e) => setShareMode(e.target.value as "DOWNLOAD" | "PREVIEW")} | |
| 293 | + className={inputClasses} | |
| 294 | + > | |
| 295 | + <option value="DOWNLOAD">Téléchargement</option> | |
| 296 | + <option value="PREVIEW">Aperçu seulement</option> | |
| 297 | + </select> | |
| 298 | + </div> | |
| 299 | + <div> | |
| 300 | + <label className="text-xs text-gray-500 dark:text-gray-400 block mb-1">Mot de passe (optionnel)</label> | |
| 301 | + <input | |
| 302 | + type="password" | |
| 303 | + value={sharePassword} | |
| 304 | + onChange={(e) => setSharePassword(e.target.value)} | |
| 305 | + placeholder="Laisser vide pour pas de mot de passe" | |
| 306 | + className={inputClasses} | |
| 307 | + /> | |
| 308 | + </div> | |
| 309 | + <div> | |
| 310 | + <label className="text-xs text-gray-500 dark:text-gray-400 block mb-1">Expiration (optionnel)</label> | |
| 311 | + <input | |
| 312 | + type="datetime-local" | |
| 313 | + value={shareExpiry} | |
| 314 | + onChange={(e) => setShareExpiry(e.target.value)} | |
| 315 | + className={inputClasses} | |
| 316 | + /> | |
| 317 | + </div> | |
| 318 | + <div className="flex gap-2 justify-end"> | |
| 319 | + <Button variant="ghost" onClick={() => { setShareModal(false); onClose(); }}>Annuler</Button> | |
| 320 | + <Button onClick={handleShare} loading={loading}>Créer le lien</Button> | |
| 321 | + </div> | |
| 322 | + </div> | |
| 323 | + )} | |
| 324 | + </Modal> | |
| 325 | + | |
| 326 | + {/* Move Modal */} | |
| 327 | + <Modal isOpen={moveModal} onClose={() => setMoveModal(false)} title="Déplacer vers"> | |
| 328 | + <div className="space-y-1 max-h-60 overflow-y-auto"> | |
| 329 | + <button | |
| 330 | + onClick={() => handleMove("root")} | |
| 331 | + className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg" | |
| 332 | + > | |
| 333 | + Racine | |
| 334 | + </button> | |
| 335 | + {folders.map((folder: { id: string; name: string }) => ( | |
| 336 | + <button | |
| 337 | + key={folder.id} | |
| 338 | + onClick={() => handleMove(folder.id)} | |
| 339 | + className="flex items-center gap-2 w-full px-3 py-2.5 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg" | |
| 340 | + > | |
| 341 | + {folder.name} | |
| 342 | + </button> | |
| 343 | + ))} | |
| 344 | + </div> | |
| 345 | + </Modal> | |
| 346 | + </> | |
| 347 | + ); | |
| 348 | +} | |
added
components/files/FileCard.tsx
+151 −0
@@ -0,0 +1,151 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion } from "framer-motion"; | |
| 4 | +import { | |
| 5 | + FileText, | |
| 6 | + Image, | |
| 7 | + Video, | |
| 8 | + Music, | |
| 9 | + FileCode, | |
| 10 | + FileSpreadsheet, | |
| 11 | + File, | |
| 12 | + Archive, | |
| 13 | + MoreVertical, | |
| 14 | +} from "lucide-react"; | |
| 15 | +import { formatFileSize, formatDate, getFileIcon } from "@/lib/utils"; | |
| 16 | +import { useState, useRef, useEffect } from "react"; | |
| 17 | +import FileActions from "./FileActions"; | |
| 18 | + | |
| 19 | +interface FileData { | |
| 20 | + id: string; | |
| 21 | + name: string; | |
| 22 | + size: number; | |
| 23 | + mimeType: string; | |
| 24 | + createdAt: string; | |
| 25 | + folderId?: string | null; | |
| 26 | +} | |
| 27 | + | |
| 28 | +interface FileCardProps { | |
| 29 | + file: FileData; | |
| 30 | + onPreview: (file: FileData) => void; | |
| 31 | + onRefresh: () => void; | |
| 32 | + viewMode: "grid" | "list"; | |
| 33 | +} | |
| 34 | + | |
| 35 | +const iconMap: Record<string, React.ComponentType<{ className?: string }>> = { | |
| 36 | + image: Image, | |
| 37 | + video: Video, | |
| 38 | + audio: Music, | |
| 39 | + pdf: FileText, | |
| 40 | + code: FileCode, | |
| 41 | + spreadsheet: FileSpreadsheet, | |
| 42 | + archive: Archive, | |
| 43 | + document: FileText, | |
| 44 | + file: File, | |
| 45 | + presentation: FileText, | |
| 46 | +}; | |
| 47 | + | |
| 48 | +export default function FileCard({ file, onPreview, onRefresh, viewMode }: FileCardProps) { | |
| 49 | + const [showActions, setShowActions] = useState(false); | |
| 50 | + const menuRef = useRef<HTMLDivElement>(null); | |
| 51 | + | |
| 52 | + const iconType = getFileIcon(file.mimeType); | |
| 53 | + const Icon = iconMap[iconType] || File; | |
| 54 | + | |
| 55 | + useEffect(() => { | |
| 56 | + function handleClick(e: MouseEvent) { | |
| 57 | + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { | |
| 58 | + setShowActions(false); | |
| 59 | + } | |
| 60 | + } | |
| 61 | + document.addEventListener("mousedown", handleClick); | |
| 62 | + return () => document.removeEventListener("mousedown", handleClick); | |
| 63 | + }, []); | |
| 64 | + | |
| 65 | + const isImage = file.mimeType.startsWith("image/"); | |
| 66 | + | |
| 67 | + if (viewMode === "list") { | |
| 68 | + return ( | |
| 69 | + <motion.div | |
| 70 | + initial={{ opacity: 0 }} | |
| 71 | + animate={{ opacity: 1 }} | |
| 72 | + className="flex items-center gap-3 px-3 py-3.5 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg cursor-pointer group transition-colors" | |
| 73 | + onClick={() => onPreview(file)} | |
| 74 | + > | |
| 75 | + <Icon className="w-5 h-5 text-gray-400 flex-shrink-0" /> | |
| 76 | + <span className="text-sm text-gray-700 dark:text-gray-200 flex-1 truncate">{file.name}</span> | |
| 77 | + <span className="text-sm text-gray-500 hidden sm:block">{formatFileSize(file.size)}</span> | |
| 78 | + <span className="text-xs text-gray-500 hidden md:block">{formatDate(file.createdAt)}</span> | |
| 79 | + <div className="relative" ref={menuRef}> | |
| 80 | + <button | |
| 81 | + onClick={(e) => { | |
| 82 | + e.stopPropagation(); | |
| 83 | + setShowActions(!showActions); | |
| 84 | + }} | |
| 85 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-white sm:opacity-0 sm:group-hover:opacity-100 transition-opacity" | |
| 86 | + > | |
| 87 | + <MoreVertical className="w-4 h-4" /> | |
| 88 | + </button> | |
| 89 | + {showActions && ( | |
| 90 | + <FileActions | |
| 91 | + file={file} | |
| 92 | + onClose={() => setShowActions(false)} | |
| 93 | + onRefresh={onRefresh} | |
| 94 | + /> | |
| 95 | + )} | |
| 96 | + </div> | |
| 97 | + </motion.div> | |
| 98 | + ); | |
| 99 | + } | |
| 100 | + | |
| 101 | + return ( | |
| 102 | + <motion.div | |
| 103 | + initial={{ opacity: 0, scale: 0.95 }} | |
| 104 | + animate={{ opacity: 1, scale: 1 }} | |
| 105 | + className="group relative bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl hover:border-gray-300 dark:hover:border-white/20 transition-colors cursor-pointer shadow-sm dark:shadow-none" | |
| 106 | + onClick={() => onPreview(file)} | |
| 107 | + > | |
| 108 | + {/* Preview thumbnail */} | |
| 109 | + <div className="aspect-square flex items-center justify-center bg-gray-50 dark:bg-white/[0.02] relative rounded-t-xl overflow-hidden"> | |
| 110 | + {isImage ? ( | |
| 111 | + <img | |
| 112 | + src={`/api/files/${file.id}/preview`} | |
| 113 | + alt={file.name} | |
| 114 | + className="w-full h-full object-cover" | |
| 115 | + loading="lazy" | |
| 116 | + /> | |
| 117 | + ) : ( | |
| 118 | + <Icon className="w-12 h-12 text-gray-300 dark:text-gray-600" /> | |
| 119 | + )} | |
| 120 | + | |
| 121 | + {/* Actions button */} | |
| 122 | + <div className="absolute top-2 right-2" ref={menuRef}> | |
| 123 | + <button | |
| 124 | + onClick={(e) => { | |
| 125 | + e.stopPropagation(); | |
| 126 | + setShowActions(!showActions); | |
| 127 | + }} | |
| 128 | + className="p-2 bg-white/80 dark:bg-black/50 rounded-lg text-gray-500 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white sm:opacity-0 sm:group-hover:opacity-100 transition-opacity backdrop-blur-sm min-w-[40px] min-h-[40px] flex items-center justify-center" | |
| 129 | + > | |
| 130 | + <MoreVertical className="w-4 h-4" /> | |
| 131 | + </button> | |
| 132 | + {showActions && ( | |
| 133 | + <FileActions | |
| 134 | + file={file} | |
| 135 | + onClose={() => setShowActions(false)} | |
| 136 | + onRefresh={onRefresh} | |
| 137 | + /> | |
| 138 | + )} | |
| 139 | + </div> | |
| 140 | + </div> | |
| 141 | + | |
| 142 | + {/* Info */} | |
| 143 | + <div className="p-3"> | |
| 144 | + <p className="text-sm text-gray-700 dark:text-gray-200 truncate">{file.name}</p> | |
| 145 | + <p className="text-sm text-gray-500 mt-1"> | |
| 146 | + {formatFileSize(file.size)} | |
| 147 | + </p> | |
| 148 | + </div> | |
| 149 | + </motion.div> | |
| 150 | + ); | |
| 151 | +} | |
added
components/files/FileExplorer.tsx
+233 −0
@@ -0,0 +1,233 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useCallback } from "react"; | |
| 4 | +import { Grid, List, Plus, Upload, CheckSquare, Square } from "lucide-react"; | |
| 5 | +import FileCard from "./FileCard"; | |
| 6 | +import FileUpload from "./FileUpload"; | |
| 7 | +import PreviewEngine from "../preview/PreviewEngine"; | |
| 8 | +import FolderCard from "../folders/FolderCard"; | |
| 9 | +import CreateFolderModal from "../folders/CreateFolderModal"; | |
| 10 | +import SearchBar from "../ui/SearchBar"; | |
| 11 | +import Button from "../ui/Button"; | |
| 12 | +import BulkActions from "./BulkActions"; | |
| 13 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 14 | + | |
| 15 | +interface FileData { | |
| 16 | + id: string; | |
| 17 | + name: string; | |
| 18 | + size: number; | |
| 19 | + mimeType: string; | |
| 20 | + createdAt: string; | |
| 21 | + folderId?: string | null; | |
| 22 | +} | |
| 23 | + | |
| 24 | +interface FolderData { | |
| 25 | + id: string; | |
| 26 | + name: string; | |
| 27 | + parentId: string | null; | |
| 28 | + createdAt: string; | |
| 29 | + _count?: { files: number; children: number }; | |
| 30 | +} | |
| 31 | + | |
| 32 | +interface FileExplorerProps { | |
| 33 | + folderId?: string | null; | |
| 34 | +} | |
| 35 | + | |
| 36 | +export default function FileExplorer({ folderId }: FileExplorerProps) { | |
| 37 | + const [files, setFiles] = useState<FileData[]>([]); | |
| 38 | + const [folders, setFolders] = useState<FolderData[]>([]); | |
| 39 | + const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); | |
| 40 | + const [showUpload, setShowUpload] = useState(false); | |
| 41 | + const [showCreateFolder, setShowCreateFolder] = useState(false); | |
| 42 | + const [previewFile, setPreviewFile] = useState<FileData | null>(null); | |
| 43 | + const [searchQuery, setSearchQuery] = useState(""); | |
| 44 | + const [loading, setLoading] = useState(true); | |
| 45 | + const [selectedIds, setSelectedIds] = useState<string[]>([]); | |
| 46 | + const [selectMode, setSelectMode] = useState(false); | |
| 47 | + | |
| 48 | + const fetchData = useCallback(async () => { | |
| 49 | + setLoading(true); | |
| 50 | + try { | |
| 51 | + const params = new URLSearchParams(); | |
| 52 | + if (folderId) params.set("folderId", folderId); | |
| 53 | + else params.set("folderId", "root"); | |
| 54 | + if (searchQuery) params.set("search", searchQuery); | |
| 55 | + | |
| 56 | + const [filesRes, foldersRes] = await Promise.all([ | |
| 57 | + fetch(`/api/files?${params}`), | |
| 58 | + fetch(`/api/folders?parentId=${folderId || "root"}`), | |
| 59 | + ]); | |
| 60 | + | |
| 61 | + if (filesRes.ok) setFiles(await filesRes.json()); | |
| 62 | + if (foldersRes.ok) setFolders(await foldersRes.json()); | |
| 63 | + } catch (err) { | |
| 64 | + console.error("Failed to fetch data:", err); | |
| 65 | + } finally { | |
| 66 | + setLoading(false); | |
| 67 | + } | |
| 68 | + }, [folderId, searchQuery]); | |
| 69 | + | |
| 70 | + useEffect(() => { fetchData(); }, [fetchData]); | |
| 71 | + | |
| 72 | + const handleSearch = useCallback((query: string) => { setSearchQuery(query); }, []); | |
| 73 | + | |
| 74 | + const toggleSelect = (id: string) => { | |
| 75 | + setSelectedIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); | |
| 76 | + }; | |
| 77 | + | |
| 78 | + const selectAll = () => { | |
| 79 | + if (selectedIds.length === files.length) setSelectedIds([]); | |
| 80 | + else setSelectedIds(files.map((f) => f.id)); | |
| 81 | + }; | |
| 82 | + | |
| 83 | + const handleFileClick = (file: FileData) => { | |
| 84 | + if (selectMode) { | |
| 85 | + toggleSelect(file.id); | |
| 86 | + } else { | |
| 87 | + setPreviewFile(file); | |
| 88 | + } | |
| 89 | + }; | |
| 90 | + | |
| 91 | + return ( | |
| 92 | + <div className="flex flex-col h-full lg:flex-row overflow-x-hidden"> | |
| 93 | + <div className="flex-1 flex flex-col min-w-0 overflow-x-hidden"> | |
| 94 | + {/* Toolbar */} | |
| 95 | + <div className="px-4 lg:px-6 py-4 border-b border-gray-200 dark:border-white/10 flex flex-col sm:flex-row items-start sm:items-center gap-3"> | |
| 96 | + <div className="flex-1 w-full sm:w-auto"> | |
| 97 | + <SearchBar onSearch={handleSearch} placeholder="Rechercher des fichiers..." /> | |
| 98 | + </div> | |
| 99 | + <div className="flex items-center gap-2 w-full sm:w-auto justify-between sm:justify-end"> | |
| 100 | + <div className="flex items-center gap-1 bg-gray-100 dark:bg-white/5 rounded-lg p-1"> | |
| 101 | + <button | |
| 102 | + onClick={() => setViewMode("grid")} | |
| 103 | + className={`min-w-[40px] min-h-[40px] flex items-center justify-center rounded transition-colors ${viewMode === "grid" ? "bg-white dark:bg-white/10 text-gray-900 dark:text-white shadow-sm dark:shadow-none" : "text-gray-400"}`} | |
| 104 | + > | |
| 105 | + <Grid className="w-4 h-4" /> | |
| 106 | + </button> | |
| 107 | + <button | |
| 108 | + onClick={() => setViewMode("list")} | |
| 109 | + className={`min-w-[40px] min-h-[40px] flex items-center justify-center rounded transition-colors ${viewMode === "list" ? "bg-white dark:bg-white/10 text-gray-900 dark:text-white shadow-sm dark:shadow-none" : "text-gray-400"}`} | |
| 110 | + > | |
| 111 | + <List className="w-4 h-4" /> | |
| 112 | + </button> | |
| 113 | + </div> | |
| 114 | + <Button | |
| 115 | + variant={selectMode ? "secondary" : "ghost"} | |
| 116 | + size="sm" | |
| 117 | + onClick={() => { setSelectMode(!selectMode); setSelectedIds([]); }} | |
| 118 | + className="min-w-[40px] min-h-[40px]" | |
| 119 | + > | |
| 120 | + <CheckSquare className="w-4 h-4" /> | |
| 121 | + <span className="hidden sm:inline">Sélection</span> | |
| 122 | + </Button> | |
| 123 | + <Button variant="ghost" size="sm" onClick={() => setShowCreateFolder(true)} className="min-w-[40px] min-h-[40px]"> | |
| 124 | + <Plus className="w-4 h-4" /> | |
| 125 | + <span className="hidden sm:inline">Dossier</span> | |
| 126 | + </Button> | |
| 127 | + <Button size="sm" onClick={() => setShowUpload(!showUpload)} className="min-w-[40px] min-h-[40px]"> | |
| 128 | + <Upload className="w-4 h-4" /> | |
| 129 | + <span className="hidden sm:inline">Upload</span> | |
| 130 | + </Button> | |
| 131 | + </div> | |
| 132 | + </div> | |
| 133 | + | |
| 134 | + {/* Bulk Actions Bar */} | |
| 135 | + <BulkActions selectedIds={selectedIds} onClear={() => { setSelectedIds([]); setSelectMode(false); }} onRefresh={fetchData} /> | |
| 136 | + | |
| 137 | + {/* Select All */} | |
| 138 | + {selectMode && files.length > 0 && ( | |
| 139 | + <div className="px-4 lg:px-6 py-2 border-b border-gray-100 dark:border-white/5"> | |
| 140 | + <button onClick={selectAll} className="flex items-center gap-2 text-sm sm:text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 min-h-[40px]"> | |
| 141 | + {selectedIds.length === files.length ? <CheckSquare className="w-4 h-4 sm:w-3.5 sm:h-3.5" /> : <Square className="w-4 h-4 sm:w-3.5 sm:h-3.5" />} | |
| 142 | + {selectedIds.length === files.length ? "Tout désélectionner" : "Tout sélectionner"} | |
| 143 | + </button> | |
| 144 | + </div> | |
| 145 | + )} | |
| 146 | + | |
| 147 | + {/* Upload Zone */} | |
| 148 | + <AnimatePresence> | |
| 149 | + {showUpload && ( | |
| 150 | + <motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }} className="px-4 lg:px-6 pt-4"> | |
| 151 | + <FileUpload folderId={folderId} onUploadComplete={fetchData} /> | |
| 152 | + </motion.div> | |
| 153 | + )} | |
| 154 | + </AnimatePresence> | |
| 155 | + | |
| 156 | + {/* Content */} | |
| 157 | + <div className="flex-1 overflow-auto overflow-x-hidden px-4 lg:px-6 py-4"> | |
| 158 | + {loading ? ( | |
| 159 | + <div className="flex items-center justify-center h-32"> | |
| 160 | + <div className="w-6 h-6 border-2 border-gray-300 dark:border-white/20 border-t-gray-600 dark:border-t-white rounded-full animate-spin" /> | |
| 161 | + </div> | |
| 162 | + ) : ( | |
| 163 | + <> | |
| 164 | + {folders.length > 0 && ( | |
| 165 | + <div className="mb-6"> | |
| 166 | + <h3 className="text-xs font-medium text-gray-500 uppercase tracking-wider mb-3">Dossiers</h3> | |
| 167 | + <div className={viewMode === "grid" ? "grid grid-cols-2 gap-2 sm:grid-cols-3 sm:gap-3 lg:grid-cols-4 xl:grid-cols-5" : "space-y-1"}> | |
| 168 | + {folders.map((folder) => ( | |
| 169 | + <FolderCard key={folder.id} folder={folder} onRefresh={fetchData} viewMode={viewMode} /> | |
| 170 | + ))} | |
| 171 | + </div> | |
| 172 | + </div> | |
| 173 | + )} | |
| 174 | + | |
| 175 | + {files.length > 0 && ( | |
| 176 | + <div> | |
| 177 | + <h3 className="text-xs font-medium text-gray-500 uppercase tracking-wider mb-3"> | |
| 178 | + Fichiers ({files.length}) | |
| 179 | + </h3> | |
| 180 | + <div className={viewMode === "grid" ? "grid grid-cols-2 gap-2 sm:grid-cols-3 sm:gap-3 lg:grid-cols-4 xl:grid-cols-5" : "space-y-1"}> | |
| 181 | + {files.map((file) => ( | |
| 182 | + <div key={file.id} className="relative"> | |
| 183 | + {selectMode && ( | |
| 184 | + <button | |
| 185 | + onClick={(e) => { e.stopPropagation(); toggleSelect(file.id); }} | |
| 186 | + className="absolute top-2 left-2 z-10 p-1.5 bg-white/80 dark:bg-black/50 rounded-md backdrop-blur-sm min-w-[36px] min-h-[36px] flex items-center justify-center" | |
| 187 | + > | |
| 188 | + {selectedIds.includes(file.id) ? ( | |
| 189 | + <CheckSquare className="w-4 h-4 text-blue-500" /> | |
| 190 | + ) : ( | |
| 191 | + <Square className="w-4 h-4 text-gray-400" /> | |
| 192 | + )} | |
| 193 | + </button> | |
| 194 | + )} | |
| 195 | + <div className={selectedIds.includes(file.id) ? "ring-2 ring-blue-500 rounded-xl" : ""}> | |
| 196 | + <FileCard | |
| 197 | + file={file} | |
| 198 | + onPreview={handleFileClick} | |
| 199 | + onRefresh={fetchData} | |
| 200 | + viewMode={viewMode} | |
| 201 | + /> | |
| 202 | + </div> | |
| 203 | + </div> | |
| 204 | + ))} | |
| 205 | + </div> | |
| 206 | + </div> | |
| 207 | + )} | |
| 208 | + | |
| 209 | + {folders.length === 0 && files.length === 0 && ( | |
| 210 | + <div className="flex flex-col items-center justify-center h-64 text-gray-500"> | |
| 211 | + <Upload className="w-12 h-12 mb-4 text-gray-300 dark:text-gray-600" /> | |
| 212 | + <p className="text-lg">Aucun fichier</p> | |
| 213 | + <p className="text-sm mt-1">Uploadez vos premiers fichiers</p> | |
| 214 | + <Button className="mt-4" size="sm" onClick={() => setShowUpload(true)}> | |
| 215 | + <Upload className="w-4 h-4" /> Upload | |
| 216 | + </Button> | |
| 217 | + </div> | |
| 218 | + )} | |
| 219 | + </> | |
| 220 | + )} | |
| 221 | + </div> | |
| 222 | + </div> | |
| 223 | + | |
| 224 | + {previewFile && ( | |
| 225 | + <div className="fixed inset-0 z-50 lg:relative lg:inset-auto lg:w-[400px] lg:border-l lg:border-gray-200 dark:lg:border-white/10"> | |
| 226 | + <PreviewEngine file={previewFile} files={files} onClose={() => setPreviewFile(null)} onNavigate={setPreviewFile} /> | |
| 227 | + </div> | |
| 228 | + )} | |
| 229 | + | |
| 230 | + <CreateFolderModal isOpen={showCreateFolder} onClose={() => setShowCreateFolder(false)} parentId={folderId || null} onCreated={fetchData} /> | |
| 231 | + </div> | |
| 232 | + ); | |
| 233 | +} | |
added
components/files/FilePreview.tsx
+281 −0
@@ -0,0 +1,281 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect } from "react"; | |
| 4 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 5 | +import { X, Download, ChevronLeft, ChevronRight, Maximize2, Minimize2 } from "lucide-react"; | |
| 6 | +import { isPreviewable, getLanguageFromMime, formatFileSize, formatDate } from "@/lib/utils"; | |
| 7 | +import dynamic from "next/dynamic"; | |
| 8 | + | |
| 9 | +const SyntaxHighlighter = dynamic( | |
| 10 | + () => import("react-syntax-highlighter").then((mod) => mod.Light), | |
| 11 | + { ssr: false } | |
| 12 | +); | |
| 13 | + | |
| 14 | +interface FileData { | |
| 15 | + id: string; | |
| 16 | + name: string; | |
| 17 | + size: number; | |
| 18 | + mimeType: string; | |
| 19 | + createdAt: string; | |
| 20 | +} | |
| 21 | + | |
| 22 | +interface FilePreviewProps { | |
| 23 | + file: FileData | null; | |
| 24 | + files?: FileData[]; | |
| 25 | + onClose: () => void; | |
| 26 | + onNavigate?: (file: FileData) => void; | |
| 27 | +} | |
| 28 | + | |
| 29 | +export default function FilePreview({ file, files = [], onClose, onNavigate }: FilePreviewProps) { | |
| 30 | + const [textContent, setTextContent] = useState<string>(""); | |
| 31 | + const [fullscreen, setFullscreen] = useState(false); | |
| 32 | + const [loading, setLoading] = useState(false); | |
| 33 | + | |
| 34 | + const currentIndex = files.findIndex((f) => f.id === file?.id); | |
| 35 | + | |
| 36 | + useEffect(() => { | |
| 37 | + if (!file) return; | |
| 38 | + | |
| 39 | + const isText = | |
| 40 | + file.mimeType.startsWith("text/") || | |
| 41 | + file.mimeType.includes("json") || | |
| 42 | + file.mimeType.includes("xml") || | |
| 43 | + file.mimeType.includes("javascript") || | |
| 44 | + file.mimeType.includes("typescript") || | |
| 45 | + file.mimeType.includes("markdown"); | |
| 46 | + | |
| 47 | + if (isText) { | |
| 48 | + setLoading(true); | |
| 49 | + fetch(`/api/files/${file.id}/preview`) | |
| 50 | + .then((res) => res.text()) | |
| 51 | + .then((text) => { | |
| 52 | + setTextContent(text); | |
| 53 | + setLoading(false); | |
| 54 | + }) | |
| 55 | + .catch(() => setLoading(false)); | |
| 56 | + } | |
| 57 | + | |
| 58 | + if (file.mimeType === "text/csv") { | |
| 59 | + setLoading(true); | |
| 60 | + fetch(`/api/files/${file.id}/preview`) | |
| 61 | + .then((res) => res.text()) | |
| 62 | + .then((text) => { | |
| 63 | + setTextContent(text); | |
| 64 | + setLoading(false); | |
| 65 | + }) | |
| 66 | + .catch(() => setLoading(false)); | |
| 67 | + } | |
| 68 | + }, [file]); | |
| 69 | + | |
| 70 | + if (!file) return null; | |
| 71 | + | |
| 72 | + const handlePrev = () => { | |
| 73 | + if (currentIndex > 0 && onNavigate) onNavigate(files[currentIndex - 1]); | |
| 74 | + }; | |
| 75 | + | |
| 76 | + const handleNext = () => { | |
| 77 | + if (currentIndex < files.length - 1 && onNavigate) onNavigate(files[currentIndex + 1]); | |
| 78 | + }; | |
| 79 | + | |
| 80 | + const renderPreview = () => { | |
| 81 | + if (!isPreviewable(file.mimeType)) { | |
| 82 | + return ( | |
| 83 | + <div className="flex flex-col items-center justify-center h-full text-gray-400"> | |
| 84 | + <p className="text-lg mb-2">Aperçu non disponible</p> | |
| 85 | + <p className="text-sm text-gray-500">{file.mimeType}</p> | |
| 86 | + <button | |
| 87 | + onClick={() => window.open(`/api/files/${file.id}/download`, "_blank")} | |
| 88 | + className="mt-4 px-4 py-2 bg-white/10 rounded-lg text-sm hover:bg-white/20 transition-colors" | |
| 89 | + > | |
| 90 | + Télécharger | |
| 91 | + </button> | |
| 92 | + </div> | |
| 93 | + ); | |
| 94 | + } | |
| 95 | + | |
| 96 | + if (file.mimeType.startsWith("image/")) { | |
| 97 | + return ( | |
| 98 | + <div className="flex items-center justify-center h-full"> | |
| 99 | + <img | |
| 100 | + src={`/api/files/${file.id}/preview`} | |
| 101 | + alt={file.name} | |
| 102 | + className="max-w-full max-h-full object-contain" | |
| 103 | + /> | |
| 104 | + </div> | |
| 105 | + ); | |
| 106 | + } | |
| 107 | + | |
| 108 | + if (file.mimeType.startsWith("video/")) { | |
| 109 | + return ( | |
| 110 | + <div className="flex items-center justify-center h-full"> | |
| 111 | + <video | |
| 112 | + src={`/api/files/${file.id}/preview`} | |
| 113 | + controls | |
| 114 | + className="max-w-full max-h-full" | |
| 115 | + > | |
| 116 | + Your browser does not support video. | |
| 117 | + </video> | |
| 118 | + </div> | |
| 119 | + ); | |
| 120 | + } | |
| 121 | + | |
| 122 | + if (file.mimeType.startsWith("audio/")) { | |
| 123 | + return ( | |
| 124 | + <div className="flex items-center justify-center h-full"> | |
| 125 | + <audio src={`/api/files/${file.id}/preview`} controls className="w-full max-w-md"> | |
| 126 | + Your browser does not support audio. | |
| 127 | + </audio> | |
| 128 | + </div> | |
| 129 | + ); | |
| 130 | + } | |
| 131 | + | |
| 132 | + if (file.mimeType === "application/pdf") { | |
| 133 | + return ( | |
| 134 | + <iframe | |
| 135 | + src={`/api/files/${file.id}/preview`} | |
| 136 | + className="w-full h-full border-0" | |
| 137 | + title={file.name} | |
| 138 | + /> | |
| 139 | + ); | |
| 140 | + } | |
| 141 | + | |
| 142 | + if (file.mimeType === "text/csv") { | |
| 143 | + if (loading) return <div className="text-gray-400 p-4">Chargement...</div>; | |
| 144 | + const rows = textContent.split("\n").map((row) => row.split(",")); | |
| 145 | + return ( | |
| 146 | + <div className="overflow-auto h-full p-4"> | |
| 147 | + <table className="w-full text-sm border-collapse"> | |
| 148 | + <thead> | |
| 149 | + <tr> | |
| 150 | + {rows[0]?.map((header, i) => ( | |
| 151 | + <th key={i} className="border border-white/10 px-3 py-2 text-left text-gray-300 bg-white/5"> | |
| 152 | + {header} | |
| 153 | + </th> | |
| 154 | + ))} | |
| 155 | + </tr> | |
| 156 | + </thead> | |
| 157 | + <tbody> | |
| 158 | + {rows.slice(1).map((row, i) => ( | |
| 159 | + <tr key={i}> | |
| 160 | + {row.map((cell, j) => ( | |
| 161 | + <td key={j} className="border border-white/10 px-3 py-2 text-gray-400"> | |
| 162 | + {cell} | |
| 163 | + </td> | |
| 164 | + ))} | |
| 165 | + </tr> | |
| 166 | + ))} | |
| 167 | + </tbody> | |
| 168 | + </table> | |
| 169 | + </div> | |
| 170 | + ); | |
| 171 | + } | |
| 172 | + | |
| 173 | + // Text / Code / Markdown | |
| 174 | + if (loading) return <div className="text-gray-400 p-4">Chargement...</div>; | |
| 175 | + | |
| 176 | + const isCode = | |
| 177 | + file.mimeType.includes("javascript") || | |
| 178 | + file.mimeType.includes("typescript") || | |
| 179 | + file.mimeType.includes("json") || | |
| 180 | + file.mimeType.includes("xml") || | |
| 181 | + file.mimeType.includes("css") || | |
| 182 | + file.mimeType.includes("html"); | |
| 183 | + | |
| 184 | + if (isCode) { | |
| 185 | + const language = getLanguageFromMime(file.mimeType); | |
| 186 | + return ( | |
| 187 | + <div className="overflow-auto h-full p-4"> | |
| 188 | + <SyntaxHighlighter | |
| 189 | + language={language} | |
| 190 | + customStyle={{ | |
| 191 | + background: "transparent", | |
| 192 | + padding: 0, | |
| 193 | + margin: 0, | |
| 194 | + fontSize: "0.875rem", | |
| 195 | + }} | |
| 196 | + > | |
| 197 | + {textContent} | |
| 198 | + </SyntaxHighlighter> | |
| 199 | + </div> | |
| 200 | + ); | |
| 201 | + } | |
| 202 | + | |
| 203 | + return ( | |
| 204 | + <div className="overflow-auto h-full p-6"> | |
| 205 | + <pre className="text-sm text-gray-300 whitespace-pre-wrap font-mono"> | |
| 206 | + {textContent} | |
| 207 | + </pre> | |
| 208 | + </div> | |
| 209 | + ); | |
| 210 | + }; | |
| 211 | + | |
| 212 | + return ( | |
| 213 | + <AnimatePresence> | |
| 214 | + <motion.div | |
| 215 | + initial={{ opacity: 0 }} | |
| 216 | + animate={{ opacity: 1 }} | |
| 217 | + exit={{ opacity: 0 }} | |
| 218 | + className={`fixed inset-0 z-50 flex flex-col bg-[#0a0a0a] ${ | |
| 219 | + fullscreen ? "" : "lg:relative lg:inset-auto lg:h-full" | |
| 220 | + }`} | |
| 221 | + > | |
| 222 | + {/* Header */} | |
| 223 | + <div className="flex items-center justify-between px-4 py-3 border-b border-white/10 flex-shrink-0"> | |
| 224 | + <div className="flex-1 min-w-0"> | |
| 225 | + <h3 className="text-sm font-medium text-white truncate">{file.name}</h3> | |
| 226 | + <p className="text-xs text-gray-500"> | |
| 227 | + {formatFileSize(file.size)} - {formatDate(file.createdAt)} | |
| 228 | + </p> | |
| 229 | + </div> | |
| 230 | + <div className="flex items-center gap-1"> | |
| 231 | + <button | |
| 232 | + onClick={() => window.open(`/api/files/${file.id}/download`, "_blank")} | |
| 233 | + className="p-2 text-gray-400 hover:text-white transition-colors" | |
| 234 | + > | |
| 235 | + <Download className="w-4 h-4" /> | |
| 236 | + </button> | |
| 237 | + <button | |
| 238 | + onClick={() => setFullscreen(!fullscreen)} | |
| 239 | + className="p-2 text-gray-400 hover:text-white transition-colors hidden lg:block" | |
| 240 | + > | |
| 241 | + {fullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| 242 | + </button> | |
| 243 | + <button | |
| 244 | + onClick={onClose} | |
| 245 | + className="p-2 text-gray-400 hover:text-white transition-colors" | |
| 246 | + > | |
| 247 | + <X className="w-4 h-4" /> | |
| 248 | + </button> | |
| 249 | + </div> | |
| 250 | + </div> | |
| 251 | + | |
| 252 | + {/* Preview Content */} | |
| 253 | + <div className="flex-1 relative overflow-hidden"> | |
| 254 | + {renderPreview()} | |
| 255 | + | |
| 256 | + {/* Navigation arrows */} | |
| 257 | + {files.length > 1 && ( | |
| 258 | + <> | |
| 259 | + {currentIndex > 0 && ( | |
| 260 | + <button | |
| 261 | + onClick={handlePrev} | |
| 262 | + className="absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-black/50 rounded-full text-white hover:bg-black/70 transition-colors backdrop-blur-sm" | |
| 263 | + > | |
| 264 | + <ChevronLeft className="w-5 h-5" /> | |
| 265 | + </button> | |
| 266 | + )} | |
| 267 | + {currentIndex < files.length - 1 && ( | |
| 268 | + <button | |
| 269 | + onClick={handleNext} | |
| 270 | + className="absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-black/50 rounded-full text-white hover:bg-black/70 transition-colors backdrop-blur-sm" | |
| 271 | + > | |
| 272 | + <ChevronRight className="w-5 h-5" /> | |
| 273 | + </button> | |
| 274 | + )} | |
| 275 | + </> | |
| 276 | + )} | |
| 277 | + </div> | |
| 278 | + </motion.div> | |
| 279 | + </AnimatePresence> | |
| 280 | + ); | |
| 281 | +} | |
added
components/files/FileUpload.tsx
+149 −0
@@ -0,0 +1,149 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useCallback, useState } from "react"; | |
| 4 | +import { useDropzone } from "react-dropzone"; | |
| 5 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 6 | +import { Upload, CheckCircle, AlertCircle } from "lucide-react"; | |
| 7 | +import { formatFileSize } from "@/lib/utils"; | |
| 8 | + | |
| 9 | +interface UploadItem { | |
| 10 | + file: File; | |
| 11 | + progress: number; | |
| 12 | + status: "uploading" | "done" | "error"; | |
| 13 | + error?: string; | |
| 14 | +} | |
| 15 | + | |
| 16 | +interface FileUploadProps { | |
| 17 | + folderId?: string | null; | |
| 18 | + onUploadComplete: () => void; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export default function FileUpload({ folderId, onUploadComplete }: FileUploadProps) { | |
| 22 | + const [uploads, setUploads] = useState<UploadItem[]>([]); | |
| 23 | + | |
| 24 | + const uploadFile = useCallback( | |
| 25 | + async (file: File) => { | |
| 26 | + const formData = new FormData(); | |
| 27 | + formData.append("file", file); | |
| 28 | + if (folderId) formData.append("folderId", folderId); | |
| 29 | + | |
| 30 | + setUploads((prev) => [ | |
| 31 | + ...prev, | |
| 32 | + { file, progress: 0, status: "uploading" }, | |
| 33 | + ]); | |
| 34 | + | |
| 35 | + try { | |
| 36 | + const res = await fetch("/api/files", { | |
| 37 | + method: "POST", | |
| 38 | + body: formData, | |
| 39 | + }); | |
| 40 | + | |
| 41 | + if (!res.ok) { | |
| 42 | + const data = await res.json(); | |
| 43 | + throw new Error(data.error || "Upload failed"); | |
| 44 | + } | |
| 45 | + | |
| 46 | + setUploads((prev) => | |
| 47 | + prev.map((u) => | |
| 48 | + u.file === file ? { ...u, progress: 100, status: "done" } : u | |
| 49 | + ) | |
| 50 | + ); | |
| 51 | + onUploadComplete(); | |
| 52 | + } catch (err) { | |
| 53 | + setUploads((prev) => | |
| 54 | + prev.map((u) => | |
| 55 | + u.file === file | |
| 56 | + ? { ...u, status: "error", error: (err as Error).message } | |
| 57 | + : u | |
| 58 | + ) | |
| 59 | + ); | |
| 60 | + } | |
| 61 | + }, | |
| 62 | + [folderId, onUploadComplete] | |
| 63 | + ); | |
| 64 | + | |
| 65 | + const onDrop = useCallback( | |
| 66 | + (acceptedFiles: File[]) => { | |
| 67 | + acceptedFiles.forEach((file) => uploadFile(file)); | |
| 68 | + }, | |
| 69 | + [uploadFile] | |
| 70 | + ); | |
| 71 | + | |
| 72 | + const { getRootProps, getInputProps, isDragActive } = useDropzone({ | |
| 73 | + onDrop, | |
| 74 | + noClick: false, | |
| 75 | + }); | |
| 76 | + | |
| 77 | + const clearUploads = () => setUploads([]); | |
| 78 | + | |
| 79 | + return ( | |
| 80 | + <div> | |
| 81 | + <div | |
| 82 | + {...getRootProps()} | |
| 83 | + className={`border-2 border-dashed rounded-xl p-6 sm:p-8 text-center cursor-pointer transition-all ${ | |
| 84 | + isDragActive | |
| 85 | + ? "border-blue-500 bg-blue-50 dark:bg-blue-500/5" | |
| 86 | + : "border-gray-300 dark:border-white/10 hover:border-gray-400 dark:hover:border-white/20" | |
| 87 | + }`} | |
| 88 | + > | |
| 89 | + <input {...getInputProps()} /> | |
| 90 | + <Upload className="w-8 h-8 mx-auto mb-3 text-gray-400 dark:text-gray-500" /> | |
| 91 | + <p className="text-sm text-gray-500 dark:text-gray-400"> | |
| 92 | + {isDragActive | |
| 93 | + ? "Déposez les fichiers ici..." | |
| 94 | + : ( | |
| 95 | + <> | |
| 96 | + <span className="hidden sm:inline">Glissez-déposez vos fichiers ou cliquez pour sélectionner</span> | |
| 97 | + <span className="sm:hidden">Tapez pour sélectionner des fichiers</span> | |
| 98 | + </> | |
| 99 | + )} | |
| 100 | + </p> | |
| 101 | + <p className="text-xs text-gray-400 dark:text-gray-600 mt-1"> | |
| 102 | + Max {formatFileSize(parseInt(process.env.NEXT_PUBLIC_MAX_FILE_SIZE || "104857600"))} | |
| 103 | + </p> | |
| 104 | + </div> | |
| 105 | + | |
| 106 | + <AnimatePresence> | |
| 107 | + {uploads.length > 0 && ( | |
| 108 | + <motion.div | |
| 109 | + initial={{ opacity: 0, height: 0 }} | |
| 110 | + animate={{ opacity: 1, height: "auto" }} | |
| 111 | + exit={{ opacity: 0, height: 0 }} | |
| 112 | + className="mt-3 space-y-2" | |
| 113 | + > | |
| 114 | + <div className="flex items-center justify-between"> | |
| 115 | + <span className="text-xs text-gray-500"> | |
| 116 | + {uploads.filter((u) => u.status === "done").length}/{uploads.length} terminé | |
| 117 | + </span> | |
| 118 | + <button onClick={clearUploads} className="text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 min-h-[36px] px-2"> | |
| 119 | + Effacer | |
| 120 | + </button> | |
| 121 | + </div> | |
| 122 | + {uploads.map((upload, i) => ( | |
| 123 | + <div | |
| 124 | + key={i} | |
| 125 | + className="flex items-center gap-3 bg-gray-100 dark:bg-white/5 rounded-lg px-3 py-3 sm:py-2" | |
| 126 | + > | |
| 127 | + {upload.status === "uploading" && ( | |
| 128 | + <div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin flex-shrink-0" /> | |
| 129 | + )} | |
| 130 | + {upload.status === "done" && ( | |
| 131 | + <CheckCircle className="w-4 h-4 text-green-500 dark:text-green-400 flex-shrink-0" /> | |
| 132 | + )} | |
| 133 | + {upload.status === "error" && ( | |
| 134 | + <AlertCircle className="w-4 h-4 text-red-500 dark:text-red-400 flex-shrink-0" /> | |
| 135 | + )} | |
| 136 | + <span className="text-sm text-gray-600 dark:text-gray-300 truncate flex-1"> | |
| 137 | + {upload.file.name} | |
| 138 | + </span> | |
| 139 | + <span className="text-xs text-gray-500 flex-shrink-0"> | |
| 140 | + {formatFileSize(upload.file.size)} | |
| 141 | + </span> | |
| 142 | + </div> | |
| 143 | + ))} | |
| 144 | + </motion.div> | |
| 145 | + )} | |
| 146 | + </AnimatePresence> | |
| 147 | + </div> | |
| 148 | + ); | |
| 149 | +} | |
added
components/folders/CreateFolderModal.tsx
+75 −0
@@ -0,0 +1,75 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import Modal from "../ui/Modal"; | |
| 5 | +import Button from "../ui/Button"; | |
| 6 | +import toast from "react-hot-toast"; | |
| 7 | + | |
| 8 | +interface CreateFolderModalProps { | |
| 9 | + isOpen: boolean; | |
| 10 | + onClose: () => void; | |
| 11 | + parentId: string | null; | |
| 12 | + onCreated: () => void; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export default function CreateFolderModal({ | |
| 16 | + isOpen, | |
| 17 | + onClose, | |
| 18 | + parentId, | |
| 19 | + onCreated, | |
| 20 | +}: CreateFolderModalProps) { | |
| 21 | + const [name, setName] = useState(""); | |
| 22 | + const [loading, setLoading] = useState(false); | |
| 23 | + | |
| 24 | + const handleCreate = async () => { | |
| 25 | + if (!name.trim()) return; | |
| 26 | + setLoading(true); | |
| 27 | + | |
| 28 | + try { | |
| 29 | + const res = await fetch("/api/folders", { | |
| 30 | + method: "POST", | |
| 31 | + headers: { "Content-Type": "application/json" }, | |
| 32 | + body: JSON.stringify({ | |
| 33 | + name: name.trim(), | |
| 34 | + parentId: parentId || "root", | |
| 35 | + }), | |
| 36 | + }); | |
| 37 | + | |
| 38 | + if (res.ok) { | |
| 39 | + toast.success("Dossier créé"); | |
| 40 | + setName(""); | |
| 41 | + onCreated(); | |
| 42 | + onClose(); | |
| 43 | + } else { | |
| 44 | + const data = await res.json(); | |
| 45 | + toast.error(data.error || "Erreur"); | |
| 46 | + } | |
| 47 | + } catch { | |
| 48 | + toast.error("Erreur réseau"); | |
| 49 | + } finally { | |
| 50 | + setLoading(false); | |
| 51 | + } | |
| 52 | + }; | |
| 53 | + | |
| 54 | + return ( | |
| 55 | + <Modal isOpen={isOpen} onClose={onClose} title="Nouveau dossier"> | |
| 56 | + <input | |
| 57 | + type="text" | |
| 58 | + value={name} | |
| 59 | + onChange={(e) => setName(e.target.value)} | |
| 60 | + placeholder="Nom du dossier" | |
| 61 | + className="w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-2.5 text-gray-900 dark:text-white text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:border-gray-400 dark:focus:border-white/20 mb-4" | |
| 62 | + autoFocus | |
| 63 | + onKeyDown={(e) => e.key === "Enter" && handleCreate()} | |
| 64 | + /> | |
| 65 | + <div className="flex gap-2 justify-end"> | |
| 66 | + <Button variant="ghost" onClick={onClose}> | |
| 67 | + Annuler | |
| 68 | + </Button> | |
| 69 | + <Button onClick={handleCreate} loading={loading} disabled={!name.trim()}> | |
| 70 | + Créer | |
| 71 | + </Button> | |
| 72 | + </div> | |
| 73 | + </Modal> | |
| 74 | + ); | |
| 75 | +} | |
added
components/folders/FolderCard.tsx
+225 −0
@@ -0,0 +1,225 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useRouter } from "next/navigation"; | |
| 4 | +import { motion } from "framer-motion"; | |
| 5 | +import { Folder, MoreVertical, Pencil, Trash2 } from "lucide-react"; | |
| 6 | +import { useState, useRef, useEffect } from "react"; | |
| 7 | +import toast from "react-hot-toast"; | |
| 8 | +import Modal from "../ui/Modal"; | |
| 9 | +import Button from "../ui/Button"; | |
| 10 | + | |
| 11 | +interface FolderData { | |
| 12 | + id: string; | |
| 13 | + name: string; | |
| 14 | + _count?: { files: number; children: number }; | |
| 15 | +} | |
| 16 | + | |
| 17 | +interface FolderCardProps { | |
| 18 | + folder: FolderData; | |
| 19 | + onRefresh: () => void; | |
| 20 | + viewMode: "grid" | "list"; | |
| 21 | +} | |
| 22 | + | |
| 23 | +export default function FolderCard({ folder, onRefresh, viewMode }: FolderCardProps) { | |
| 24 | + const router = useRouter(); | |
| 25 | + const [showMenu, setShowMenu] = useState(false); | |
| 26 | + const [renameModal, setRenameModal] = useState(false); | |
| 27 | + const [deleteModal, setDeleteModal] = useState(false); | |
| 28 | + const [newName, setNewName] = useState(folder.name); | |
| 29 | + const [loading, setLoading] = useState(false); | |
| 30 | + const menuRef = useRef<HTMLDivElement>(null); | |
| 31 | + | |
| 32 | + useEffect(() => { | |
| 33 | + function handleClick(e: MouseEvent) { | |
| 34 | + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { | |
| 35 | + setShowMenu(false); | |
| 36 | + } | |
| 37 | + } | |
| 38 | + document.addEventListener("mousedown", handleClick); | |
| 39 | + return () => document.removeEventListener("mousedown", handleClick); | |
| 40 | + }, []); | |
| 41 | + | |
| 42 | + const handleRename = async () => { | |
| 43 | + setLoading(true); | |
| 44 | + try { | |
| 45 | + const res = await fetch(`/api/folders/${folder.id}`, { | |
| 46 | + method: "PATCH", | |
| 47 | + headers: { "Content-Type": "application/json" }, | |
| 48 | + body: JSON.stringify({ name: newName }), | |
| 49 | + }); | |
| 50 | + if (res.ok) { | |
| 51 | + toast.success("Dossier renommé"); | |
| 52 | + onRefresh(); | |
| 53 | + } | |
| 54 | + } catch { | |
| 55 | + toast.error("Erreur"); | |
| 56 | + } finally { | |
| 57 | + setLoading(false); | |
| 58 | + setRenameModal(false); | |
| 59 | + } | |
| 60 | + }; | |
| 61 | + | |
| 62 | + const handleDelete = async () => { | |
| 63 | + setLoading(true); | |
| 64 | + try { | |
| 65 | + const res = await fetch(`/api/folders/${folder.id}`, { | |
| 66 | + method: "DELETE", | |
| 67 | + }); | |
| 68 | + if (res.ok) { | |
| 69 | + toast.success("Dossier supprimé"); | |
| 70 | + onRefresh(); | |
| 71 | + } | |
| 72 | + } catch { | |
| 73 | + toast.error("Erreur"); | |
| 74 | + } finally { | |
| 75 | + setLoading(false); | |
| 76 | + setDeleteModal(false); | |
| 77 | + } | |
| 78 | + }; | |
| 79 | + | |
| 80 | + const inputClasses = "w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-4 py-3 text-gray-900 dark:text-white text-sm focus:outline-none mb-4"; | |
| 81 | + | |
| 82 | + if (viewMode === "list") { | |
| 83 | + return ( | |
| 84 | + <> | |
| 85 | + <motion.div | |
| 86 | + initial={{ opacity: 0 }} | |
| 87 | + animate={{ opacity: 1 }} | |
| 88 | + className="flex items-center gap-3 px-3 py-3.5 hover:bg-gray-100 dark:hover:bg-white/5 rounded-lg cursor-pointer group transition-colors" | |
| 89 | + onClick={() => router.push(`/dashboard/folder/${folder.id}`)} | |
| 90 | + > | |
| 91 | + <Folder className="w-5 h-5 text-blue-400 flex-shrink-0" /> | |
| 92 | + <span className="text-sm text-gray-700 dark:text-gray-200 flex-1 truncate">{folder.name}</span> | |
| 93 | + {folder._count && ( | |
| 94 | + <span className="text-xs text-gray-500"> | |
| 95 | + {folder._count.files} fichiers | |
| 96 | + </span> | |
| 97 | + )} | |
| 98 | + <div className="relative" ref={menuRef}> | |
| 99 | + <button | |
| 100 | + onClick={(e) => { | |
| 101 | + e.stopPropagation(); | |
| 102 | + setShowMenu(!showMenu); | |
| 103 | + }} | |
| 104 | + className="min-w-[44px] min-h-[44px] flex items-center justify-center text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-white sm:opacity-0 sm:group-hover:opacity-100 transition-opacity" | |
| 105 | + > | |
| 106 | + <MoreVertical className="w-4 h-4" /> | |
| 107 | + </button> | |
| 108 | + {showMenu && ( | |
| 109 | + <div className="absolute right-0 top-full mt-1 w-40 bg-white dark:bg-gray-900 border border-gray-200 dark:border-white/10 rounded-xl shadow-2xl z-50 py-1 max-h-[70vh] overflow-auto"> | |
| 110 | + <button | |
| 111 | + onClick={(e) => { | |
| 112 | + e.stopPropagation(); | |
| 113 | + setRenameModal(true); | |
| 114 | + setShowMenu(false); | |
| 115 | + }} | |
| 116 | + className="flex items-center gap-2 w-full px-4 py-3 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5" | |
| 117 | + > | |
| 118 | + <Pencil className="w-4 h-4" /> Renommer | |
| 119 | + </button> | |
| 120 | + <button | |
| 121 | + onClick={(e) => { | |
| 122 | + e.stopPropagation(); | |
| 123 | + setDeleteModal(true); | |
| 124 | + setShowMenu(false); | |
| 125 | + }} | |
| 126 | + className="flex items-center gap-2 w-full px-4 py-3 text-sm text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10" | |
| 127 | + > | |
| 128 | + <Trash2 className="w-4 h-4" /> Supprimer | |
| 129 | + </button> | |
| 130 | + </div> | |
| 131 | + )} | |
| 132 | + </div> | |
| 133 | + </motion.div> | |
| 134 | + <Modal isOpen={renameModal} onClose={() => setRenameModal(false)} title="Renommer le dossier"> | |
| 135 | + <input type="text" value={newName} onChange={(e) => setNewName(e.target.value)} className={inputClasses} autoFocus /> | |
| 136 | + <div className="flex gap-2 justify-end"> | |
| 137 | + <Button variant="ghost" onClick={() => setRenameModal(false)}>Annuler</Button> | |
| 138 | + <Button onClick={handleRename} loading={loading}>Renommer</Button> | |
| 139 | + </div> | |
| 140 | + </Modal> | |
| 141 | + <Modal isOpen={deleteModal} onClose={() => setDeleteModal(false)} title="Supprimer le dossier"> | |
| 142 | + <p className="text-gray-500 dark:text-gray-400 text-sm mb-4">Supprimer <strong className="text-gray-900 dark:text-white">{folder.name}</strong> et tout son contenu ?</p> | |
| 143 | + <div className="flex gap-2 justify-end"> | |
| 144 | + <Button variant="ghost" onClick={() => setDeleteModal(false)}>Annuler</Button> | |
| 145 | + <Button variant="danger" onClick={handleDelete} loading={loading}>Supprimer</Button> | |
| 146 | + </div> | |
| 147 | + </Modal> | |
| 148 | + </> | |
| 149 | + ); | |
| 150 | + } | |
| 151 | + | |
| 152 | + return ( | |
| 153 | + <> | |
| 154 | + <motion.div | |
| 155 | + initial={{ opacity: 0, scale: 0.95 }} | |
| 156 | + animate={{ opacity: 1, scale: 1 }} | |
| 157 | + className="group bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl p-4 hover:border-blue-300 dark:hover:border-blue-500/30 transition-colors cursor-pointer relative shadow-sm dark:shadow-none min-h-[72px]" | |
| 158 | + onClick={() => router.push(`/dashboard/folder/${folder.id}`)} | |
| 159 | + > | |
| 160 | + <div className="flex items-center gap-3"> | |
| 161 | + <Folder className="w-8 h-8 text-blue-400" /> | |
| 162 | + <div className="flex-1 min-w-0"> | |
| 163 | + <p className="text-sm text-gray-700 dark:text-gray-200 truncate">{folder.name}</p> | |
| 164 | + {folder._count && ( | |
| 165 | + <p className="text-xs text-gray-500"> | |
| 166 | + {folder._count.files} fichiers, {folder._count.children} dossiers | |
| 167 | + </p> | |
| 168 | + )} | |
| 169 | + </div> | |
| 170 | + </div> | |
| 171 | + | |
| 172 | + <div className="absolute top-2 right-2" ref={menuRef}> | |
| 173 | + <button | |
| 174 | + onClick={(e) => { | |
| 175 | + e.stopPropagation(); | |
| 176 | + setShowMenu(!showMenu); | |
| 177 | + }} | |
| 178 | + className="p-2 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-white sm:opacity-0 sm:group-hover:opacity-100 transition-opacity min-w-[40px] min-h-[40px] flex items-center justify-center" | |
| 179 | + > | |
| 180 | + <MoreVertical className="w-4 h-4" /> | |
| 181 | + </button> | |
| 182 | + {showMenu && ( | |
| 183 | + <div className="absolute right-0 top-full mt-1 w-40 bg-white dark:bg-gray-900 border border-gray-200 dark:border-white/10 rounded-xl shadow-2xl z-50 py-1 max-h-[70vh] overflow-auto"> | |
| 184 | + <button | |
| 185 | + onClick={(e) => { | |
| 186 | + e.stopPropagation(); | |
| 187 | + setRenameModal(true); | |
| 188 | + setShowMenu(false); | |
| 189 | + }} | |
| 190 | + className="flex items-center gap-2 w-full px-4 py-3 text-sm text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-white/5" | |
| 191 | + > | |
| 192 | + <Pencil className="w-4 h-4" /> Renommer | |
| 193 | + </button> | |
| 194 | + <button | |
| 195 | + onClick={(e) => { | |
| 196 | + e.stopPropagation(); | |
| 197 | + setDeleteModal(true); | |
| 198 | + setShowMenu(false); | |
| 199 | + }} | |
| 200 | + className="flex items-center gap-2 w-full px-4 py-3 text-sm text-red-500 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/10" | |
| 201 | + > | |
| 202 | + <Trash2 className="w-4 h-4" /> Supprimer | |
| 203 | + </button> | |
| 204 | + </div> | |
| 205 | + )} | |
| 206 | + </div> | |
| 207 | + </motion.div> | |
| 208 | + | |
| 209 | + <Modal isOpen={renameModal} onClose={() => setRenameModal(false)} title="Renommer le dossier"> | |
| 210 | + <input type="text" value={newName} onChange={(e) => setNewName(e.target.value)} className={inputClasses} autoFocus /> | |
| 211 | + <div className="flex gap-2 justify-end"> | |
| 212 | + <Button variant="ghost" onClick={() => setRenameModal(false)}>Annuler</Button> | |
| 213 | + <Button onClick={handleRename} loading={loading}>Renommer</Button> | |
| 214 | + </div> | |
| 215 | + </Modal> | |
| 216 | + <Modal isOpen={deleteModal} onClose={() => setDeleteModal(false)} title="Supprimer le dossier"> | |
| 217 | + <p className="text-gray-500 dark:text-gray-400 text-sm mb-4">Supprimer <strong className="text-gray-900 dark:text-white">{folder.name}</strong> et tout son contenu ?</p> | |
| 218 | + <div className="flex gap-2 justify-end"> | |
| 219 | + <Button variant="ghost" onClick={() => setDeleteModal(false)}>Annuler</Button> | |
| 220 | + <Button variant="danger" onClick={handleDelete} loading={loading}>Supprimer</Button> | |
| 221 | + </div> | |
| 222 | + </Modal> | |
| 223 | + </> | |
| 224 | + ); | |
| 225 | +} | |
added
components/layout/AppShell.tsx
+56 −0
@@ -0,0 +1,56 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState } from "react"; | |
| 4 | +import Sidebar from "./Sidebar"; | |
| 5 | +import MobileNav from "./MobileNav"; | |
| 6 | +import ThemeToggle from "../theme/ThemeToggle"; | |
| 7 | +import NotificationBell from "./NotificationBell"; | |
| 8 | +import AiPanel from "../ai/AiPanel"; | |
| 9 | + | |
| 10 | +interface AppShellProps { | |
| 11 | + children: React.ReactNode; | |
| 12 | +} | |
| 13 | + | |
| 14 | +export default function AppShell({ children }: AppShellProps) { | |
| 15 | + const [sidebarOpen, setSidebarOpen] = useState(false); | |
| 16 | + | |
| 17 | + return ( | |
| 18 | + <div className="min-h-screen bg-gray-50 dark:bg-[#0a0a0a] flex transition-colors"> | |
| 19 | + {/* Desktop Sidebar */} | |
| 20 | + <div className="hidden lg:block w-64 flex-shrink-0"> | |
| 21 | + <Sidebar /> | |
| 22 | + </div> | |
| 23 | + | |
| 24 | + {/* Mobile Sidebar Overlay */} | |
| 25 | + <MobileNav isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} /> | |
| 26 | + | |
| 27 | + {/* Main Content */} | |
| 28 | + <div className="flex-1 flex flex-col min-h-screen min-w-0"> | |
| 29 | + {/* Mobile Header - safe area + larger touch targets */} | |
| 30 | + <div className="lg:hidden flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-white/10 bg-white dark:bg-[#0a0a0a] transition-colors safe-top"> | |
| 31 | + <button | |
| 32 | + onClick={() => setSidebarOpen(true)} | |
| 33 | + className="text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white p-3 -ml-1 min-w-[44px] min-h-[44px] flex items-center justify-center active:bg-gray-100 dark:active:bg-white/10 rounded-xl transition-colors" | |
| 34 | + > | |
| 35 | + <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| 36 | + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /> | |
| 37 | + </svg> | |
| 38 | + </button> | |
| 39 | + <span className="text-gray-900 dark:text-white font-semibold text-base">SPB Cloud</span> | |
| 40 | + <div className="flex items-center gap-0"> | |
| 41 | + <NotificationBell /> | |
| 42 | + <ThemeToggle /> | |
| 43 | + </div> | |
| 44 | + </div> | |
| 45 | + | |
| 46 | + {/* Content - with safe area bottom for notched devices */} | |
| 47 | + <main className="flex-1 overflow-auto pb-safe"> | |
| 48 | + {children} | |
| 49 | + </main> | |
| 50 | + </div> | |
| 51 | + | |
| 52 | + {/* AI Assistant */} | |
| 53 | + <AiPanel /> | |
| 54 | + </div> | |
| 55 | + ); | |
| 56 | +} | |
added
components/layout/Breadcrumb.tsx
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import Link from "next/link"; | |
| 4 | +import { ChevronRight, Home } from "lucide-react"; | |
| 5 | + | |
| 6 | +interface BreadcrumbItem { | |
| 7 | + id: string; | |
| 8 | + name: string; | |
| 9 | +} | |
| 10 | + | |
| 11 | +interface BreadcrumbProps { | |
| 12 | + items: BreadcrumbItem[]; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export default function Breadcrumb({ items }: BreadcrumbProps) { | |
| 16 | + return ( | |
| 17 | + <nav className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 overflow-x-auto pb-1 flex-nowrap scrollbar-none"> | |
| 18 | + <Link | |
| 19 | + href="/dashboard" | |
| 20 | + className="flex items-center gap-1 hover:text-gray-900 dark:hover:text-white transition-colors whitespace-nowrap py-1 flex-shrink-0" | |
| 21 | + > | |
| 22 | + <Home className="w-3.5 h-3.5" /> | |
| 23 | + <span>Fichiers</span> | |
| 24 | + </Link> | |
| 25 | + {items.map((item) => ( | |
| 26 | + <span key={item.id} className="flex items-center gap-1 flex-shrink-0"> | |
| 27 | + <ChevronRight className="w-3.5 h-3.5 text-gray-400 dark:text-gray-600 flex-shrink-0" /> | |
| 28 | + <Link | |
| 29 | + href={`/dashboard/folder/${item.id}`} | |
| 30 | + className="hover:text-gray-900 dark:hover:text-white transition-colors whitespace-nowrap py-1" | |
| 31 | + > | |
| 32 | + {item.name} | |
| 33 | + </Link> | |
| 34 | + </span> | |
| 35 | + ))} | |
| 36 | + </nav> | |
| 37 | + ); | |
| 38 | +} | |
added
components/layout/MobileNav.tsx
+36 −0
@@ -0,0 +1,36 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 4 | +import Sidebar from "./Sidebar"; | |
| 5 | + | |
| 6 | +interface MobileNavProps { | |
| 7 | + isOpen: boolean; | |
| 8 | + onClose: () => void; | |
| 9 | +} | |
| 10 | + | |
| 11 | +export default function MobileNav({ isOpen, onClose }: MobileNavProps) { | |
| 12 | + return ( | |
| 13 | + <AnimatePresence> | |
| 14 | + {isOpen && ( | |
| 15 | + <> | |
| 16 | + <motion.div | |
| 17 | + initial={{ opacity: 0 }} | |
| 18 | + animate={{ opacity: 1 }} | |
| 19 | + exit={{ opacity: 0 }} | |
| 20 | + className="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 lg:hidden" | |
| 21 | + onClick={onClose} | |
| 22 | + /> | |
| 23 | + <motion.div | |
| 24 | + initial={{ x: -300 }} | |
| 25 | + animate={{ x: 0 }} | |
| 26 | + exit={{ x: -300 }} | |
| 27 | + transition={{ type: "spring", damping: 28, stiffness: 300 }} | |
| 28 | + className="fixed left-0 top-0 bottom-0 w-[280px] max-w-[85vw] z-50 lg:hidden shadow-2xl" | |
| 29 | + > | |
| 30 | + <Sidebar onNavigate={onClose} /> | |
| 31 | + </motion.div> | |
| 32 | + </> | |
| 33 | + )} | |
| 34 | + </AnimatePresence> | |
| 35 | + ); | |
| 36 | +} | |
added
components/layout/NotificationBell.tsx
+145 −0
@@ -0,0 +1,145 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useRef } from "react"; | |
| 4 | +import { Bell, Check, CheckCheck } from "lucide-react"; | |
| 5 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 6 | +import { formatDate } from "@/lib/utils"; | |
| 7 | + | |
| 8 | +interface NotificationData { | |
| 9 | + id: string; | |
| 10 | + type: string; | |
| 11 | + title: string; | |
| 12 | + message: string; | |
| 13 | + read: boolean; | |
| 14 | + createdAt: string; | |
| 15 | +} | |
| 16 | + | |
| 17 | +export default function NotificationBell() { | |
| 18 | + const [notifications, setNotifications] = useState<NotificationData[]>([]); | |
| 19 | + const [unreadCount, setUnreadCount] = useState(0); | |
| 20 | + const [open, setOpen] = useState(false); | |
| 21 | + const ref = useRef<HTMLDivElement>(null); | |
| 22 | + | |
| 23 | + const fetchCount = async () => { | |
| 24 | + try { | |
| 25 | + const res = await fetch("/api/notifications?count=true"); | |
| 26 | + if (res.ok) { | |
| 27 | + const data = await res.json(); | |
| 28 | + setUnreadCount(data.count); | |
| 29 | + } | |
| 30 | + } catch {} | |
| 31 | + }; | |
| 32 | + | |
| 33 | + const fetchNotifications = async () => { | |
| 34 | + try { | |
| 35 | + const res = await fetch("/api/notifications"); | |
| 36 | + if (res.ok) setNotifications(await res.json()); | |
| 37 | + } catch {} | |
| 38 | + }; | |
| 39 | + | |
| 40 | + useEffect(() => { | |
| 41 | + fetchCount(); | |
| 42 | + const interval = setInterval(fetchCount, 30000); | |
| 43 | + return () => clearInterval(interval); | |
| 44 | + }, []); | |
| 45 | + | |
| 46 | + useEffect(() => { | |
| 47 | + if (open) fetchNotifications(); | |
| 48 | + }, [open]); | |
| 49 | + | |
| 50 | + useEffect(() => { | |
| 51 | + function handleClick(e: MouseEvent) { | |
| 52 | + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); | |
| 53 | + } | |
| 54 | + document.addEventListener("mousedown", handleClick); | |
| 55 | + return () => document.removeEventListener("mousedown", handleClick); | |
| 56 | + }, []); | |
| 57 | + | |
| 58 | + const markAllRead = async () => { | |
| 59 | + await fetch("/api/notifications", { | |
| 60 | + method: "POST", | |
| 61 | + headers: { "Content-Type": "application/json" }, | |
| 62 | + body: JSON.stringify({ action: "mark_all_read" }), | |
| 63 | + }); | |
| 64 | + setUnreadCount(0); | |
| 65 | + setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); | |
| 66 | + }; | |
| 67 | + | |
| 68 | + const markRead = async (id: string) => { | |
| 69 | + await fetch(`/api/notifications/${id}`, { method: "PATCH" }); | |
| 70 | + setNotifications((prev) => prev.map((n) => n.id === id ? { ...n, read: true } : n)); | |
| 71 | + setUnreadCount((prev) => Math.max(0, prev - 1)); | |
| 72 | + }; | |
| 73 | + | |
| 74 | + const typeColors: Record<string, string> = { | |
| 75 | + upload_complete: "text-green-500", | |
| 76 | + backup_complete: "text-blue-500", | |
| 77 | + ai_complete: "text-purple-500", | |
| 78 | + share_accessed: "text-orange-500", | |
| 79 | + }; | |
| 80 | + | |
| 81 | + return ( | |
| 82 | + <div className="relative" ref={ref}> | |
| 83 | + <button | |
| 84 | + onClick={() => setOpen(!open)} | |
| 85 | + className="relative min-w-[44px] min-h-[44px] p-2.5 flex items-center justify-center text-gray-400 hover:text-gray-700 dark:hover:text-white transition-colors" | |
| 86 | + > | |
| 87 | + <Bell className="w-5 h-5" /> | |
| 88 | + {unreadCount > 0 && ( | |
| 89 | + <span className="absolute top-1 right-1 w-4 h-4 bg-red-500 text-white text-xs font-bold rounded-full flex items-center justify-center"> | |
| 90 | + {unreadCount > 9 ? "9+" : unreadCount} | |
| 91 | + </span> | |
| 92 | + )} | |
| 93 | + </button> | |
| 94 | + | |
| 95 | + <AnimatePresence> | |
| 96 | + {open && ( | |
| 97 | + <motion.div | |
| 98 | + initial={{ opacity: 0, y: 5, scale: 0.95 }} | |
| 99 | + animate={{ opacity: 1, y: 0, scale: 1 }} | |
| 100 | + exit={{ opacity: 0, y: 5, scale: 0.95 }} | |
| 101 | + className="fixed inset-x-4 top-16 sm:absolute sm:inset-x-auto sm:top-full sm:right-0 sm:mt-2 sm:w-80 bg-white dark:bg-gray-900 border border-gray-200 dark:border-white/10 rounded-xl shadow-2xl z-50 overflow-hidden" | |
| 102 | + > | |
| 103 | + <div className="flex items-center justify-between px-4 py-3 border-b border-gray-200 dark:border-white/10"> | |
| 104 | + <h3 className="text-sm font-medium text-gray-900 dark:text-white">Notifications</h3> | |
| 105 | + {unreadCount > 0 && ( | |
| 106 | + <button onClick={markAllRead} className="text-xs text-blue-500 hover:text-blue-600 flex items-center gap-1 min-h-[36px]"> | |
| 107 | + <CheckCheck className="w-3 h-3" /> Tout lire | |
| 108 | + </button> | |
| 109 | + )} | |
| 110 | + </div> | |
| 111 | + <div className="max-h-80 overflow-y-auto"> | |
| 112 | + {notifications.length === 0 ? ( | |
| 113 | + <p className="text-center text-gray-500 text-sm py-8">Aucune notification</p> | |
| 114 | + ) : ( | |
| 115 | + notifications.map((n) => ( | |
| 116 | + <div | |
| 117 | + key={n.id} | |
| 118 | + className={`px-4 py-3.5 border-b border-gray-100 dark:border-white/5 hover:bg-gray-50 dark:hover:bg-white/5 transition-colors cursor-pointer ${!n.read ? "bg-blue-50/50 dark:bg-blue-500/5" : ""}`} | |
| 119 | + onClick={() => !n.read && markRead(n.id)} | |
| 120 | + > | |
| 121 | + <div className="flex items-start gap-2"> | |
| 122 | + <div className={`w-2 h-2 rounded-full mt-1.5 flex-shrink-0 ${n.read ? "bg-transparent" : "bg-blue-500"}`} /> | |
| 123 | + <div className="flex-1 min-w-0"> | |
| 124 | + <p className={`text-sm font-medium ${typeColors[n.type] || "text-gray-900 dark:text-white"}`}> | |
| 125 | + {n.title} | |
| 126 | + </p> | |
| 127 | + <p className="text-xs text-gray-500 mt-0.5">{n.message}</p> | |
| 128 | + <p className="text-xs text-gray-400 mt-1">{formatDate(n.createdAt)}</p> | |
| 129 | + </div> | |
| 130 | + {!n.read && ( | |
| 131 | + <button className="p-1.5 text-gray-400 hover:text-green-500 min-w-[32px] min-h-[32px] flex items-center justify-center" title="Marquer comme lu"> | |
| 132 | + <Check className="w-3.5 h-3.5" /> | |
| 133 | + </button> | |
| 134 | + )} | |
| 135 | + </div> | |
| 136 | + </div> | |
| 137 | + )) | |
| 138 | + )} | |
| 139 | + </div> | |
| 140 | + </motion.div> | |
| 141 | + )} | |
| 142 | + </AnimatePresence> | |
| 143 | + </div> | |
| 144 | + ); | |
| 145 | +} | |
added
components/layout/Sidebar.tsx
+188 −0
@@ -0,0 +1,188 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import Link from "next/link"; | |
| 4 | +import { usePathname, useRouter } from "next/navigation"; | |
| 5 | +import { useState, useEffect } from "react"; | |
| 6 | +import { | |
| 7 | + Cloud, | |
| 8 | + FolderOpen, | |
| 9 | + LayoutDashboard, | |
| 10 | + Share2, | |
| 11 | + Activity, | |
| 12 | + LogOut, | |
| 13 | + Star, | |
| 14 | + Tag, | |
| 15 | + ChevronDown, | |
| 16 | + ChevronRight, | |
| 17 | + Users, | |
| 18 | + HardDrive, | |
| 19 | + Key, | |
| 20 | + Settings, | |
| 21 | +} from "lucide-react"; | |
| 22 | +import ThemeToggle from "../theme/ThemeToggle"; | |
| 23 | +import NotificationBell from "./NotificationBell"; | |
| 24 | + | |
| 25 | +const navItems = [ | |
| 26 | + { href: "/dashboard", label: "Fichiers", icon: FolderOpen }, | |
| 27 | + { href: "/dashboard/favorites", label: "Favoris", icon: Star }, | |
| 28 | + { href: "/admin", label: "Dashboard", icon: LayoutDashboard }, | |
| 29 | + { href: "/admin/shares", label: "Liens partagés", icon: Share2 }, | |
| 30 | + { href: "/admin/activity", label: "Activité", icon: Activity }, | |
| 31 | +]; | |
| 32 | + | |
| 33 | +const adminItems = [ | |
| 34 | + { href: "/admin/users", label: "Utilisateurs", icon: Users }, | |
| 35 | + { href: "/admin/backup", label: "Backups", icon: HardDrive }, | |
| 36 | + { href: "/admin/api-keys", label: "Clés API", icon: Key }, | |
| 37 | + { href: "/admin/settings", label: "Paramètres", icon: Settings }, | |
| 38 | +]; | |
| 39 | + | |
| 40 | +interface TagData { | |
| 41 | + id: string; | |
| 42 | + name: string; | |
| 43 | + color: string; | |
| 44 | + _count?: { files: number }; | |
| 45 | +} | |
| 46 | + | |
| 47 | +interface SidebarProps { | |
| 48 | + onNavigate?: () => void; | |
| 49 | +} | |
| 50 | + | |
| 51 | +export default function Sidebar({ onNavigate }: SidebarProps) { | |
| 52 | + const pathname = usePathname(); | |
| 53 | + const router = useRouter(); | |
| 54 | + const [tags, setTags] = useState<TagData[]>([]); | |
| 55 | + const [showTags, setShowTags] = useState(false); | |
| 56 | + const [showAdmin, setShowAdmin] = useState(false); | |
| 57 | + | |
| 58 | + useEffect(() => { | |
| 59 | + fetch("/api/tags") | |
| 60 | + .then((res) => res.json()) | |
| 61 | + .then(setTags) | |
| 62 | + .catch(() => {}); | |
| 63 | + }, []); | |
| 64 | + | |
| 65 | + const handleLogout = async () => { | |
| 66 | + await fetch("/api/auth/logout", { method: "POST" }); | |
| 67 | + router.push("/login"); | |
| 68 | + onNavigate?.(); | |
| 69 | + }; | |
| 70 | + | |
| 71 | + const handleLinkClick = () => { | |
| 72 | + onNavigate?.(); | |
| 73 | + }; | |
| 74 | + | |
| 75 | + const isAdminActive = adminItems.some((i) => pathname === i.href); | |
| 76 | + | |
| 77 | + return ( | |
| 78 | + <div className="h-screen flex flex-col bg-white dark:bg-[#0a0a0a] border-r border-gray-200 dark:border-white/10 transition-colors"> | |
| 79 | + {/* Logo */} | |
| 80 | + <div className="px-4 py-4 flex items-center gap-2"> | |
| 81 | + <div className="w-9 h-9 rounded-xl bg-gray-100 dark:bg-white/10 flex items-center justify-center flex-shrink-0"> | |
| 82 | + <Cloud className="w-5 h-5 text-gray-700 dark:text-white" /> | |
| 83 | + </div> | |
| 84 | + <span className="text-gray-900 dark:text-white font-semibold flex-1 text-base">SPB Cloud</span> | |
| 85 | + <NotificationBell /> | |
| 86 | + <ThemeToggle /> | |
| 87 | + </div> | |
| 88 | + | |
| 89 | + {/* Navigation - touch optimized */} | |
| 90 | + <nav className="flex-1 px-2 py-1 space-y-0.5 overflow-auto overscroll-contain"> | |
| 91 | + {navItems.map((item) => { | |
| 92 | + const isActive = | |
| 93 | + pathname === item.href || | |
| 94 | + (item.href === "/dashboard" && pathname.startsWith("/dashboard") && !pathname.includes("favorites")); | |
| 95 | + return ( | |
| 96 | + <Link | |
| 97 | + key={item.href} | |
| 98 | + href={item.href} | |
| 99 | + onClick={handleLinkClick} | |
| 100 | + className={`flex items-center gap-3 px-3 py-3 rounded-xl text-sm transition-colors min-h-[44px] ${ | |
| 101 | + isActive | |
| 102 | + ? "bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white font-medium" | |
| 103 | + : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-white/5 active:bg-gray-100 dark:active:bg-white/10" | |
| 104 | + }`} | |
| 105 | + > | |
| 106 | + <item.icon className={`w-5 h-5 flex-shrink-0 ${item.href === "/dashboard/favorites" && pathname === item.href ? "text-yellow-400" : ""}`} /> | |
| 107 | + {item.label} | |
| 108 | + </Link> | |
| 109 | + ); | |
| 110 | + })} | |
| 111 | + | |
| 112 | + {/* Admin Section - toggle does NOT close sidebar */} | |
| 113 | + <div className="pt-3"> | |
| 114 | + <button | |
| 115 | + onClick={() => setShowAdmin(!showAdmin)} | |
| 116 | + className={`flex items-center gap-2 px-3 py-2.5 w-full text-xs transition-colors min-h-[40px] rounded-lg active:bg-gray-50 dark:active:bg-white/5 ${ | |
| 117 | + isAdminActive ? "text-gray-700 dark:text-gray-300" : "text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300" | |
| 118 | + }`} | |
| 119 | + > | |
| 120 | + {showAdmin ? <ChevronDown className="w-3.5 h-3.5" /> : <ChevronRight className="w-3.5 h-3.5" />} | |
| 121 | + <Settings className="w-3.5 h-3.5" /> | |
| 122 | + <span className="uppercase tracking-wider font-medium">Administration</span> | |
| 123 | + </button> | |
| 124 | + {(showAdmin || isAdminActive) && ( | |
| 125 | + <div className="space-y-0.5 ml-1"> | |
| 126 | + {adminItems.map((item) => ( | |
| 127 | + <Link | |
| 128 | + key={item.href} | |
| 129 | + href={item.href} | |
| 130 | + onClick={handleLinkClick} | |
| 131 | + className={`flex items-center gap-2.5 px-3 py-2.5 text-sm rounded-xl transition-colors min-h-[40px] ${ | |
| 132 | + pathname === item.href | |
| 133 | + ? "bg-gray-100 dark:bg-white/10 text-gray-900 dark:text-white font-medium" | |
| 134 | + : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-white/5 active:bg-gray-100 dark:active:bg-white/10" | |
| 135 | + }`} | |
| 136 | + > | |
| 137 | + <item.icon className="w-4 h-4 flex-shrink-0" /> | |
| 138 | + {item.label} | |
| 139 | + </Link> | |
| 140 | + ))} | |
| 141 | + </div> | |
| 142 | + )} | |
| 143 | + </div> | |
| 144 | + | |
| 145 | + {/* Tags Section - toggle does NOT close sidebar */} | |
| 146 | + {tags.length > 0 && ( | |
| 147 | + <div className="pt-3"> | |
| 148 | + <button | |
| 149 | + onClick={() => setShowTags(!showTags)} | |
| 150 | + className="flex items-center gap-2 px-3 py-2.5 w-full text-xs text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors min-h-[40px] rounded-lg active:bg-gray-50 dark:active:bg-white/5" | |
| 151 | + > | |
| 152 | + {showTags ? <ChevronDown className="w-3.5 h-3.5" /> : <ChevronRight className="w-3.5 h-3.5" />} | |
| 153 | + <Tag className="w-3.5 h-3.5" /> | |
| 154 | + <span className="uppercase tracking-wider font-medium">Tags</span> | |
| 155 | + </button> | |
| 156 | + {showTags && ( | |
| 157 | + <div className="space-y-0.5 ml-1"> | |
| 158 | + {tags.map((tag) => ( | |
| 159 | + <Link | |
| 160 | + key={tag.id} | |
| 161 | + href={`/dashboard?tagId=${tag.id}`} | |
| 162 | + onClick={handleLinkClick} | |
| 163 | + className="flex items-center gap-2.5 px-3 py-2.5 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-white/5 active:bg-gray-100 dark:active:bg-white/10 rounded-xl transition-colors min-h-[40px]" | |
| 164 | + > | |
| 165 | + <span className="w-3 h-3 rounded-full flex-shrink-0" style={{ backgroundColor: tag.color }} /> | |
| 166 | + <span className="truncate">{tag.name}</span> | |
| 167 | + {tag._count && <span className="text-xs text-gray-400 dark:text-gray-600 ml-auto tabular-nums">{tag._count.files}</span>} | |
| 168 | + </Link> | |
| 169 | + ))} | |
| 170 | + </div> | |
| 171 | + )} | |
| 172 | + </div> | |
| 173 | + )} | |
| 174 | + </nav> | |
| 175 | + | |
| 176 | + {/* Footer */} | |
| 177 | + <div className="px-2 py-3 border-t border-gray-200 dark:border-white/10"> | |
| 178 | + <button | |
| 179 | + onClick={handleLogout} | |
| 180 | + className="flex items-center gap-3 px-3 py-3 rounded-xl text-sm text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-500/5 active:bg-red-100 dark:active:bg-red-500/10 transition-colors w-full min-h-[44px]" | |
| 181 | + > | |
| 182 | + <LogOut className="w-5 h-5" /> | |
| 183 | + Déconnexion | |
| 184 | + </button> | |
| 185 | + </div> | |
| 186 | + </div> | |
| 187 | + ); | |
| 188 | +} | |
added
components/preview/PreviewEngine.tsx
+166 −0
@@ -0,0 +1,166 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useCallback } from "react"; | |
| 4 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 5 | +import { Info, ChevronUp } from "lucide-react"; | |
| 6 | +import PreviewToolbar from "./PreviewToolbar"; | |
| 7 | +import PreviewNavigation from "./PreviewNavigation"; | |
| 8 | +import MetadataEditor from "./editors/MetadataEditor"; | |
| 9 | +import VersionHistory from "./VersionHistory"; | |
| 10 | +import { getPlugin } from "./plugins"; | |
| 11 | +import type { FileData } from "@/types/files"; | |
| 12 | +import toast from "react-hot-toast"; | |
| 13 | + | |
| 14 | +interface PreviewEngineProps { | |
| 15 | + file: FileData | null; | |
| 16 | + files?: FileData[]; | |
| 17 | + onClose: () => void; | |
| 18 | + onNavigate?: (file: FileData) => void; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export default function PreviewEngine({ | |
| 22 | + file, | |
| 23 | + files = [], | |
| 24 | + onClose, | |
| 25 | + onNavigate, | |
| 26 | +}: PreviewEngineProps) { | |
| 27 | + const [fullscreen, setFullscreen] = useState(false); | |
| 28 | + const [isEditing, setIsEditing] = useState(false); | |
| 29 | + const [showInfo, setShowInfo] = useState(false); | |
| 30 | + const [showMobileInfo, setShowMobileInfo] = useState(false); | |
| 31 | + | |
| 32 | + const handleSave = useCallback( | |
| 33 | + async (data: Blob | string, asNewVersion?: boolean) => { | |
| 34 | + if (!file) return; | |
| 35 | + try { | |
| 36 | + if (typeof data === "string") { | |
| 37 | + const res = await fetch(`/api/files/${file.id}/content`, { | |
| 38 | + method: "PUT", | |
| 39 | + headers: { "Content-Type": "text/plain" }, | |
| 40 | + body: data, | |
| 41 | + }); | |
| 42 | + if (!res.ok) throw new Error("Save failed"); | |
| 43 | + toast.success("Fichier sauvegardé"); | |
| 44 | + } else { | |
| 45 | + const formData = new FormData(); | |
| 46 | + formData.append("file", data, file.name); | |
| 47 | + if (asNewVersion) formData.append("asNewVersion", "true"); | |
| 48 | + | |
| 49 | + const res = await fetch(`/api/files/${file.id}/save-edit`, { | |
| 50 | + method: "POST", | |
| 51 | + body: formData, | |
| 52 | + }); | |
| 53 | + if (!res.ok) throw new Error("Save failed"); | |
| 54 | + toast.success(asNewVersion ? "Nouvelle version créée" : "Fichier sauvegardé"); | |
| 55 | + } | |
| 56 | + } catch { | |
| 57 | + toast.error("Erreur lors de la sauvegarde"); | |
| 58 | + } | |
| 59 | + }, | |
| 60 | + [file] | |
| 61 | + ); | |
| 62 | + | |
| 63 | + const handleToggleFavorite = useCallback(async () => { | |
| 64 | + if (!file) return; | |
| 65 | + try { | |
| 66 | + await fetch(`/api/files/${file.id}/favorite`, { method: "POST" }); | |
| 67 | + toast.success("Favori mis à jour"); | |
| 68 | + } catch { | |
| 69 | + toast.error("Erreur"); | |
| 70 | + } | |
| 71 | + }, [file]); | |
| 72 | + | |
| 73 | + if (!file) return null; | |
| 74 | + | |
| 75 | + const plugin = getPlugin(file.mimeType); | |
| 76 | + const PluginComponent = plugin.component; | |
| 77 | + | |
| 78 | + return ( | |
| 79 | + <AnimatePresence> | |
| 80 | + <motion.div | |
| 81 | + initial={{ opacity: 0 }} | |
| 82 | + animate={{ opacity: 1 }} | |
| 83 | + exit={{ opacity: 0 }} | |
| 84 | + className={`fixed inset-0 z-50 flex flex-col bg-white dark:bg-[#0a0a0a] transition-colors ${ | |
| 85 | + fullscreen ? "" : "lg:relative lg:inset-auto lg:h-full" | |
| 86 | + }`} | |
| 87 | + > | |
| 88 | + <PreviewToolbar | |
| 89 | + file={file} | |
| 90 | + fullscreen={fullscreen} | |
| 91 | + isEditing={isEditing} | |
| 92 | + supportsEdit={!!plugin.supportsEdit} | |
| 93 | + onClose={onClose} | |
| 94 | + onToggleFullscreen={() => setFullscreen(!fullscreen)} | |
| 95 | + onToggleEdit={() => setIsEditing(!isEditing)} | |
| 96 | + onToggleFavorite={handleToggleFavorite} | |
| 97 | + isFavorite={file.metadata?.isFavorite} | |
| 98 | + /> | |
| 99 | + | |
| 100 | + <div className="flex-1 flex overflow-hidden"> | |
| 101 | + {/* Main preview area */} | |
| 102 | + <div className="flex-1 relative overflow-hidden"> | |
| 103 | + <PluginComponent | |
| 104 | + file={file} | |
| 105 | + isEditing={isEditing} | |
| 106 | + onSave={handleSave} | |
| 107 | + fullscreen={fullscreen} | |
| 108 | + /> | |
| 109 | + | |
| 110 | + {files.length > 1 && onNavigate && ( | |
| 111 | + <PreviewNavigation | |
| 112 | + files={files} | |
| 113 | + currentFile={file} | |
| 114 | + onNavigate={(f) => { | |
| 115 | + setIsEditing(false); | |
| 116 | + onNavigate(f); | |
| 117 | + }} | |
| 118 | + /> | |
| 119 | + )} | |
| 120 | + </div> | |
| 121 | + | |
| 122 | + {/* Info panel toggle - only visible on desktop in fullscreen */} | |
| 123 | + {fullscreen && ( | |
| 124 | + <div className="hidden lg:flex flex-col border-l border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-[#0a0a0a]"> | |
| 125 | + <button | |
| 126 | + onClick={() => setShowInfo(!showInfo)} | |
| 127 | + className={`min-w-[44px] min-h-[44px] flex items-center justify-center p-2.5 transition-colors ${showInfo ? "text-gray-900 dark:text-white bg-gray-200 dark:bg-white/10" : "text-gray-400 dark:text-gray-500 hover:text-gray-900 dark:hover:text-white"}`} | |
| 128 | + title="Infos & tags" | |
| 129 | + > | |
| 130 | + <Info className="w-4 h-4" /> | |
| 131 | + </button> | |
| 132 | + </div> | |
| 133 | + )} | |
| 134 | + </div> | |
| 135 | + | |
| 136 | + {/* Mobile info toggle button */} | |
| 137 | + <div className="lg:hidden border-t border-gray-200 dark:border-white/10"> | |
| 138 | + <button | |
| 139 | + onClick={() => setShowMobileInfo(!showMobileInfo)} | |
| 140 | + className="w-full flex items-center justify-center gap-2 py-3 text-sm text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition-colors" | |
| 141 | + > | |
| 142 | + <Info className="w-4 h-4" /> | |
| 143 | + <span>{showMobileInfo ? "Masquer les infos" : "Voir les infos"}</span> | |
| 144 | + <ChevronUp className={`w-4 h-4 transition-transform ${showMobileInfo ? "rotate-180" : ""}`} /> | |
| 145 | + </button> | |
| 146 | + </div> | |
| 147 | + | |
| 148 | + {/* Mobile info panel (bottom slide-up) */} | |
| 149 | + {showMobileInfo && ( | |
| 150 | + <div className="lg:hidden overflow-auto max-h-64 border-t border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-[#0a0a0a]"> | |
| 151 | + <MetadataEditor file={file} /> | |
| 152 | + <VersionHistory fileId={file.id} /> | |
| 153 | + </div> | |
| 154 | + )} | |
| 155 | + | |
| 156 | + {/* Desktop info sidebar (metadata + versions) */} | |
| 157 | + {((fullscreen && showInfo) || !fullscreen) && ( | |
| 158 | + <div className={`hidden lg:block ${fullscreen ? "w-72 border-l border-gray-200 dark:border-white/10 overflow-auto bg-gray-50 dark:bg-[#0a0a0a]" : "border-t border-gray-200 dark:border-white/10 overflow-auto max-h-64 lg:max-h-none"}`}> | |
| 159 | + <MetadataEditor file={file} /> | |
| 160 | + <VersionHistory fileId={file.id} /> | |
| 161 | + </div> | |
| 162 | + )} | |
| 163 | + </motion.div> | |
| 164 | + </AnimatePresence> | |
| 165 | + ); | |
| 166 | +} | |
added
components/preview/PreviewNavigation.tsx
+63 −0
@@ -0,0 +1,63 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useCallback } from "react"; | |
| 4 | +import { ChevronLeft, ChevronRight } from "lucide-react"; | |
| 5 | +import type { FileData } from "@/types/files"; | |
| 6 | + | |
| 7 | +interface PreviewNavigationProps { | |
| 8 | + files: FileData[]; | |
| 9 | + currentFile: FileData; | |
| 10 | + onNavigate: (file: FileData) => void; | |
| 11 | +} | |
| 12 | + | |
| 13 | +export default function PreviewNavigation({ | |
| 14 | + files, | |
| 15 | + currentFile, | |
| 16 | + onNavigate, | |
| 17 | +}: PreviewNavigationProps) { | |
| 18 | + const currentIndex = files.findIndex((f) => f.id === currentFile.id); | |
| 19 | + | |
| 20 | + const goPrev = useCallback(() => { | |
| 21 | + if (currentIndex > 0) onNavigate(files[currentIndex - 1]); | |
| 22 | + }, [currentIndex, files, onNavigate]); | |
| 23 | + | |
| 24 | + const goNext = useCallback(() => { | |
| 25 | + if (currentIndex < files.length - 1) onNavigate(files[currentIndex + 1]); | |
| 26 | + }, [currentIndex, files, onNavigate]); | |
| 27 | + | |
| 28 | + useEffect(() => { | |
| 29 | + const handleKey = (e: KeyboardEvent) => { | |
| 30 | + // Don't capture if typing in an input | |
| 31 | + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; | |
| 32 | + if (e.key === "ArrowLeft" && e.altKey) { e.preventDefault(); goPrev(); } | |
| 33 | + if (e.key === "ArrowRight" && e.altKey) { e.preventDefault(); goNext(); } | |
| 34 | + }; | |
| 35 | + window.addEventListener("keydown", handleKey); | |
| 36 | + return () => window.removeEventListener("keydown", handleKey); | |
| 37 | + }, [goPrev, goNext]); | |
| 38 | + | |
| 39 | + if (files.length <= 1) return null; | |
| 40 | + | |
| 41 | + return ( | |
| 42 | + <> | |
| 43 | + {currentIndex > 0 && ( | |
| 44 | + <button | |
| 45 | + onClick={goPrev} | |
| 46 | + className="absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-black/50 rounded-full text-white hover:bg-black/70 transition-colors backdrop-blur-sm z-10" | |
| 47 | + title="Précédent (Alt+←)" | |
| 48 | + > | |
| 49 | + <ChevronLeft className="w-5 h-5" /> | |
| 50 | + </button> | |
| 51 | + )} | |
| 52 | + {currentIndex < files.length - 1 && ( | |
| 53 | + <button | |
| 54 | + onClick={goNext} | |
| 55 | + className="absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-black/50 rounded-full text-white hover:bg-black/70 transition-colors backdrop-blur-sm z-10" | |
| 56 | + title="Suivant (Alt+→)" | |
| 57 | + > | |
| 58 | + <ChevronRight className="w-5 h-5" /> | |
| 59 | + </button> | |
| 60 | + )} | |
| 61 | + </> | |
| 62 | + ); | |
| 63 | +} | |
added
components/preview/PreviewToolbar.tsx
+85 −0
@@ -0,0 +1,85 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { X, Download, Maximize2, Minimize2, Edit3, Eye, Star } from "lucide-react"; | |
| 4 | +import { formatFileSize, formatDate } from "@/lib/utils"; | |
| 5 | +import type { FileData } from "@/types/files"; | |
| 6 | + | |
| 7 | +interface PreviewToolbarProps { | |
| 8 | + file: FileData; | |
| 9 | + fullscreen: boolean; | |
| 10 | + isEditing: boolean; | |
| 11 | + supportsEdit: boolean; | |
| 12 | + onClose: () => void; | |
| 13 | + onToggleFullscreen: () => void; | |
| 14 | + onToggleEdit: () => void; | |
| 15 | + onToggleFavorite?: () => void; | |
| 16 | + isFavorite?: boolean; | |
| 17 | +} | |
| 18 | + | |
| 19 | +export default function PreviewToolbar({ | |
| 20 | + file, | |
| 21 | + fullscreen, | |
| 22 | + isEditing, | |
| 23 | + supportsEdit, | |
| 24 | + onClose, | |
| 25 | + onToggleFullscreen, | |
| 26 | + onToggleEdit, | |
| 27 | + onToggleFavorite, | |
| 28 | + isFavorite, | |
| 29 | +}: PreviewToolbarProps) { | |
| 30 | + return ( | |
| 31 | + <div className="flex items-center justify-between px-3 sm:px-4 py-2.5 sm:py-3 border-b border-gray-200 dark:border-white/10 flex-shrink-0 bg-white dark:bg-[#0a0a0a] transition-colors"> | |
| 32 | + <div className="flex-1 min-w-0 mr-2"> | |
| 33 | + <h3 className="text-sm font-medium text-gray-900 dark:text-white truncate max-w-[200px] sm:max-w-none">{file.name}</h3> | |
| 34 | + <p className="text-xs text-gray-500"> | |
| 35 | + {formatFileSize(file.size)} - {formatDate(file.createdAt)} | |
| 36 | + </p> | |
| 37 | + </div> | |
| 38 | + <div className="flex items-center gap-1.5"> | |
| 39 | + {onToggleFavorite && ( | |
| 40 | + <button | |
| 41 | + onClick={onToggleFavorite} | |
| 42 | + className="p-2.5 transition-colors rounded-lg" | |
| 43 | + title={isFavorite ? "Retirer des favoris" : "Ajouter aux favoris"} | |
| 44 | + > | |
| 45 | + <Star | |
| 46 | + className={`w-4 h-4 ${isFavorite ? "text-yellow-400 fill-yellow-400" : "text-gray-400 hover:text-yellow-400"}`} | |
| 47 | + /> | |
| 48 | + </button> | |
| 49 | + )} | |
| 50 | + {supportsEdit && ( | |
| 51 | + <button | |
| 52 | + onClick={onToggleEdit} | |
| 53 | + className={`p-2.5 transition-colors rounded-lg ${ | |
| 54 | + isEditing ? "text-blue-400 bg-blue-400/10" : "text-gray-400 hover:text-gray-900 dark:hover:text-white" | |
| 55 | + }`} | |
| 56 | + title={isEditing ? "Mode aperçu" : "Mode édition"} | |
| 57 | + > | |
| 58 | + {isEditing ? <Eye className="w-4 h-4" /> : <Edit3 className="w-4 h-4" />} | |
| 59 | + </button> | |
| 60 | + )} | |
| 61 | + <button | |
| 62 | + onClick={() => window.open(`/api/files/${file.id}/download`, "_blank")} | |
| 63 | + className="p-2.5 text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors rounded-lg" | |
| 64 | + title="Télécharger" | |
| 65 | + > | |
| 66 | + <Download className="w-4 h-4" /> | |
| 67 | + </button> | |
| 68 | + <button | |
| 69 | + onClick={onToggleFullscreen} | |
| 70 | + className="p-2.5 text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors hidden lg:flex items-center justify-center rounded-lg" | |
| 71 | + title={fullscreen ? "Réduire" : "Plein écran"} | |
| 72 | + > | |
| 73 | + {fullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| 74 | + </button> | |
| 75 | + <button | |
| 76 | + onClick={onClose} | |
| 77 | + className="p-2.5 text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors rounded-lg" | |
| 78 | + title="Fermer" | |
| 79 | + > | |
| 80 | + <X className="w-4 h-4" /> | |
| 81 | + </button> | |
| 82 | + </div> | |
| 83 | + </div> | |
| 84 | + ); | |
| 85 | +} | |
added
components/preview/VersionHistory.tsx
+124 −0
@@ -0,0 +1,124 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useCallback } from "react"; | |
| 4 | +import { Clock, Download, RotateCcw, GitBranch } from "lucide-react"; | |
| 5 | +import { formatFileSize, formatDate } from "@/lib/utils"; | |
| 6 | +import type { FileVersionData } from "@/types/files"; | |
| 7 | +import toast from "react-hot-toast"; | |
| 8 | + | |
| 9 | +interface VersionHistoryProps { | |
| 10 | + fileId: string; | |
| 11 | + onRestore?: () => void; | |
| 12 | +} | |
| 13 | + | |
| 14 | +export default function VersionHistory({ fileId, onRestore }: VersionHistoryProps) { | |
| 15 | + const [versions, setVersions] = useState<FileVersionData[]>([]); | |
| 16 | + const [loading, setLoading] = useState(true); | |
| 17 | + | |
| 18 | + const loadVersions = useCallback(async () => { | |
| 19 | + try { | |
| 20 | + const res = await fetch(`/api/files/${fileId}/versions`); | |
| 21 | + if (res.ok) setVersions(await res.json()); | |
| 22 | + } catch {} | |
| 23 | + setLoading(false); | |
| 24 | + }, [fileId]); | |
| 25 | + | |
| 26 | + useEffect(() => { | |
| 27 | + loadVersions(); | |
| 28 | + }, [loadVersions]); | |
| 29 | + | |
| 30 | + const createSnapshot = async () => { | |
| 31 | + try { | |
| 32 | + const res = await fetch(`/api/files/${fileId}/versions`, { | |
| 33 | + method: "POST", | |
| 34 | + headers: { "Content-Type": "application/json" }, | |
| 35 | + body: JSON.stringify({ changeNote: "Manual snapshot" }), | |
| 36 | + }); | |
| 37 | + if (res.ok) { | |
| 38 | + toast.success("Snapshot créé"); | |
| 39 | + loadVersions(); | |
| 40 | + } | |
| 41 | + } catch { | |
| 42 | + toast.error("Erreur"); | |
| 43 | + } | |
| 44 | + }; | |
| 45 | + | |
| 46 | + const restoreVersion = async (versionId: string, versionNum: number) => { | |
| 47 | + if (!confirm(`Restaurer la version ${versionNum} ? La version actuelle sera sauvegardée.`)) return; | |
| 48 | + try { | |
| 49 | + const res = await fetch(`/api/files/${fileId}/versions/${versionId}/restore`, { | |
| 50 | + method: "POST", | |
| 51 | + }); | |
| 52 | + if (res.ok) { | |
| 53 | + toast.success(`Version ${versionNum} restaurée`); | |
| 54 | + loadVersions(); | |
| 55 | + onRestore?.(); | |
| 56 | + } | |
| 57 | + } catch { | |
| 58 | + toast.error("Erreur de restauration"); | |
| 59 | + } | |
| 60 | + }; | |
| 61 | + | |
| 62 | + if (loading) { | |
| 63 | + return ( | |
| 64 | + <div className="p-4 flex justify-center"> | |
| 65 | + <div className="w-5 h-5 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 66 | + </div> | |
| 67 | + ); | |
| 68 | + } | |
| 69 | + | |
| 70 | + return ( | |
| 71 | + <div className="p-4 border-t border-white/10"> | |
| 72 | + <div className="flex items-center justify-between mb-3"> | |
| 73 | + <h4 className="text-xs font-medium text-gray-400 flex items-center gap-1.5"> | |
| 74 | + <GitBranch className="w-3.5 h-3.5" /> | |
| 75 | + Versions ({versions.length}) | |
| 76 | + </h4> | |
| 77 | + <button | |
| 78 | + onClick={createSnapshot} | |
| 79 | + className="text-[10px] px-2 py-1 bg-white/10 hover:bg-white/20 text-gray-300 rounded transition-colors" | |
| 80 | + > | |
| 81 | + Snapshot | |
| 82 | + </button> | |
| 83 | + </div> | |
| 84 | + | |
| 85 | + {versions.length === 0 ? ( | |
| 86 | + <p className="text-xs text-gray-600">Aucune version précédente</p> | |
| 87 | + ) : ( | |
| 88 | + <div className="space-y-1.5 max-h-60 overflow-auto"> | |
| 89 | + {versions.map((v) => ( | |
| 90 | + <div | |
| 91 | + key={v.id} | |
| 92 | + className="flex items-center gap-2 p-2 bg-white/5 rounded-lg group hover:bg-white/10 transition-colors" | |
| 93 | + > | |
| 94 | + <Clock className="w-3.5 h-3.5 text-gray-500 flex-shrink-0" /> | |
| 95 | + <div className="flex-1 min-w-0"> | |
| 96 | + <p className="text-xs text-gray-300">v{v.versionNum}</p> | |
| 97 | + <p className="text-[10px] text-gray-500 truncate"> | |
| 98 | + {v.changeNote || "—"} · {formatFileSize(v.size)} | |
| 99 | + </p> | |
| 100 | + <p className="text-[10px] text-gray-600">{formatDate(v.createdAt)}</p> | |
| 101 | + </div> | |
| 102 | + <div className="hidden group-hover:flex items-center gap-1"> | |
| 103 | + <button | |
| 104 | + onClick={() => window.open(`/api/files/${fileId}/versions/${v.id}`, "_blank")} | |
| 105 | + className="p-1 text-gray-500 hover:text-white transition-colors" | |
| 106 | + title="Télécharger cette version" | |
| 107 | + > | |
| 108 | + <Download className="w-3 h-3" /> | |
| 109 | + </button> | |
| 110 | + <button | |
| 111 | + onClick={() => restoreVersion(v.id, v.versionNum)} | |
| 112 | + className="p-1 text-gray-500 hover:text-orange-400 transition-colors" | |
| 113 | + title="Restaurer cette version" | |
| 114 | + > | |
| 115 | + <RotateCcw className="w-3 h-3" /> | |
| 116 | + </button> | |
| 117 | + </div> | |
| 118 | + </div> | |
| 119 | + ))} | |
| 120 | + </div> | |
| 121 | + )} | |
| 122 | + </div> | |
| 123 | + ); | |
| 124 | +} | |
added
components/preview/editors/MetadataEditor.tsx
+118 −0
@@ -0,0 +1,118 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useCallback } from "react"; | |
| 4 | +import { Star, FileText, Tag } from "lucide-react"; | |
| 5 | +import TagSelector from "@/components/tags/TagSelector"; | |
| 6 | +import type { FileData } from "@/types/files"; | |
| 7 | +import toast from "react-hot-toast"; | |
| 8 | + | |
| 9 | +interface MetadataEditorProps { | |
| 10 | + file: FileData; | |
| 11 | + onUpdate?: () => void; | |
| 12 | +} | |
| 13 | + | |
| 14 | +export default function MetadataEditor({ file, onUpdate }: MetadataEditorProps) { | |
| 15 | + const [description, setDescription] = useState(file.metadata?.description || ""); | |
| 16 | + const [isFavorite, setIsFavorite] = useState(file.metadata?.isFavorite || false); | |
| 17 | + const [tags, setTags] = useState(file.tags || []); | |
| 18 | + const [, setSaving] = useState(false); | |
| 19 | + | |
| 20 | + const loadMetadata = useCallback(async () => { | |
| 21 | + try { | |
| 22 | + const [metaRes, tagsRes] = await Promise.all([ | |
| 23 | + fetch(`/api/files/${file.id}/metadata`), | |
| 24 | + fetch(`/api/files/${file.id}/tags`), | |
| 25 | + ]); | |
| 26 | + if (metaRes.ok) { | |
| 27 | + const meta = await metaRes.json(); | |
| 28 | + setDescription(meta.description || ""); | |
| 29 | + setIsFavorite(meta.isFavorite); | |
| 30 | + } | |
| 31 | + if (tagsRes.ok) { | |
| 32 | + setTags(await tagsRes.json()); | |
| 33 | + } | |
| 34 | + } catch {} | |
| 35 | + }, [file.id]); | |
| 36 | + | |
| 37 | + useEffect(() => { | |
| 38 | + loadMetadata(); | |
| 39 | + }, [loadMetadata]); | |
| 40 | + | |
| 41 | + const saveDescription = useCallback(async () => { | |
| 42 | + setSaving(true); | |
| 43 | + try { | |
| 44 | + await fetch(`/api/files/${file.id}/metadata`, { | |
| 45 | + method: "PATCH", | |
| 46 | + headers: { "Content-Type": "application/json" }, | |
| 47 | + body: JSON.stringify({ description }), | |
| 48 | + }); | |
| 49 | + toast.success("Description sauvegardée"); | |
| 50 | + onUpdate?.(); | |
| 51 | + } catch { | |
| 52 | + toast.error("Erreur"); | |
| 53 | + } | |
| 54 | + setSaving(false); | |
| 55 | + }, [file.id, description, onUpdate]); | |
| 56 | + | |
| 57 | + const toggleFavorite = useCallback(async () => { | |
| 58 | + try { | |
| 59 | + const res = await fetch(`/api/files/${file.id}/favorite`, { method: "POST" }); | |
| 60 | + if (res.ok) { | |
| 61 | + const meta = await res.json(); | |
| 62 | + setIsFavorite(meta.isFavorite); | |
| 63 | + onUpdate?.(); | |
| 64 | + } | |
| 65 | + } catch {} | |
| 66 | + }, [file.id, onUpdate]); | |
| 67 | + | |
| 68 | + return ( | |
| 69 | + <div className="p-4 space-y-4 border-t border-white/10"> | |
| 70 | + {/* Favorite */} | |
| 71 | + <div className="flex items-center justify-between"> | |
| 72 | + <span className="text-xs text-gray-400 flex items-center gap-1.5"> | |
| 73 | + <Star className="w-3.5 h-3.5" /> | |
| 74 | + Favori | |
| 75 | + </span> | |
| 76 | + <button | |
| 77 | + onClick={toggleFavorite} | |
| 78 | + className="p-1 transition-colors" | |
| 79 | + > | |
| 80 | + <Star | |
| 81 | + className={`w-5 h-5 ${ | |
| 82 | + isFavorite ? "text-yellow-400 fill-yellow-400" : "text-gray-600 hover:text-yellow-400" | |
| 83 | + }`} | |
| 84 | + /> | |
| 85 | + </button> | |
| 86 | + </div> | |
| 87 | + | |
| 88 | + {/* Description */} | |
| 89 | + <div> | |
| 90 | + <label className="text-xs text-gray-400 flex items-center gap-1.5 mb-1.5"> | |
| 91 | + <FileText className="w-3.5 h-3.5" /> | |
| 92 | + Description | |
| 93 | + </label> | |
| 94 | + <textarea | |
| 95 | + value={description} | |
| 96 | + onChange={(e) => setDescription(e.target.value)} | |
| 97 | + onBlur={saveDescription} | |
| 98 | + placeholder="Ajouter une description..." | |
| 99 | + rows={3} | |
| 100 | + className="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-xs text-gray-300 placeholder:text-gray-600 resize-none focus:outline-none focus:border-white/20" | |
| 101 | + /> | |
| 102 | + </div> | |
| 103 | + | |
| 104 | + {/* Tags */} | |
| 105 | + <div> | |
| 106 | + <label className="text-xs text-gray-400 flex items-center gap-1.5 mb-1.5"> | |
| 107 | + <Tag className="w-3.5 h-3.5" /> | |
| 108 | + Tags | |
| 109 | + </label> | |
| 110 | + <TagSelector | |
| 111 | + fileId={file.id} | |
| 112 | + currentTags={tags} | |
| 113 | + onTagsChange={loadMetadata} | |
| 114 | + /> | |
| 115 | + </div> | |
| 116 | + </div> | |
| 117 | + ); | |
| 118 | +} | |
added
components/preview/plugins/ArchivePreview.tsx
+265 −0
@@ -0,0 +1,265 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useMemo } from "react"; | |
| 4 | +import { File, Folder, Download, Eye, ChevronRight, ChevronDown, Archive } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | +import { formatFileSize } from "@/lib/utils"; | |
| 7 | + | |
| 8 | +interface ArchiveEntry { | |
| 9 | + name: string; | |
| 10 | + path: string; | |
| 11 | + size: number; | |
| 12 | + compressedSize: number; | |
| 13 | + isDirectory: boolean; | |
| 14 | + date: Date | null; | |
| 15 | +} | |
| 16 | + | |
| 17 | +interface TreeNode { | |
| 18 | + name: string; | |
| 19 | + path: string; | |
| 20 | + isDirectory: boolean; | |
| 21 | + size: number; | |
| 22 | + date: Date | null; | |
| 23 | + children: TreeNode[]; | |
| 24 | +} | |
| 25 | + | |
| 26 | +export default function ArchivePreview({ file }: PreviewPluginProps) { | |
| 27 | + const [entries, setEntries] = useState<ArchiveEntry[]>([]); | |
| 28 | + const [loading, setLoading] = useState(true); | |
| 29 | + const [error, setError] = useState<string | null>(null); | |
| 30 | + const [previewContent, setPreviewContent] = useState<string | null>(null); | |
| 31 | + const [previewName, setPreviewName] = useState(""); | |
| 32 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 33 | + const [zipInstance, setZipInstance] = useState<any>(null); | |
| 34 | + | |
| 35 | + useEffect(() => { | |
| 36 | + const loadArchive = async () => { | |
| 37 | + try { | |
| 38 | + const JSZip = (await import("jszip")).default; | |
| 39 | + const response = await fetch(`/api/files/${file.id}/preview`); | |
| 40 | + const buffer = await response.arrayBuffer(); | |
| 41 | + const zip = await JSZip.loadAsync(buffer); | |
| 42 | + setZipInstance(zip); | |
| 43 | + | |
| 44 | + const items: ArchiveEntry[] = []; | |
| 45 | + zip.forEach((relativePath, zipEntry) => { | |
| 46 | + items.push({ | |
| 47 | + name: zipEntry.name.split("/").filter(Boolean).pop() || zipEntry.name, | |
| 48 | + path: relativePath, | |
| 49 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 50 | + size: (zipEntry as any)._data?.uncompressedSize || 0, | |
| 51 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 52 | + compressedSize: (zipEntry as any)._data?.compressedSize || 0, | |
| 53 | + isDirectory: zipEntry.dir, | |
| 54 | + date: zipEntry.date, | |
| 55 | + }); | |
| 56 | + }); | |
| 57 | + | |
| 58 | + setEntries(items); | |
| 59 | + setLoading(false); | |
| 60 | + } catch { | |
| 61 | + setError("Impossible de lire l'archive"); | |
| 62 | + setLoading(false); | |
| 63 | + } | |
| 64 | + }; | |
| 65 | + | |
| 66 | + loadArchive(); | |
| 67 | + }, [file.id]); | |
| 68 | + | |
| 69 | + const tree = useMemo(() => { | |
| 70 | + const root: TreeNode = { name: "", path: "", isDirectory: true, size: 0, date: null, children: [] }; | |
| 71 | + | |
| 72 | + for (const entry of entries) { | |
| 73 | + const parts = entry.path.split("/").filter(Boolean); | |
| 74 | + let current = root; | |
| 75 | + | |
| 76 | + for (let i = 0; i < parts.length; i++) { | |
| 77 | + const isLast = i === parts.length - 1; | |
| 78 | + let child = current.children.find((c) => c.name === parts[i]); | |
| 79 | + | |
| 80 | + if (!child) { | |
| 81 | + child = { | |
| 82 | + name: parts[i], | |
| 83 | + path: parts.slice(0, i + 1).join("/"), | |
| 84 | + isDirectory: isLast ? entry.isDirectory : true, | |
| 85 | + size: isLast ? entry.size : 0, | |
| 86 | + date: isLast ? entry.date : null, | |
| 87 | + children: [], | |
| 88 | + }; | |
| 89 | + current.children.push(child); | |
| 90 | + } | |
| 91 | + | |
| 92 | + current = child; | |
| 93 | + } | |
| 94 | + } | |
| 95 | + | |
| 96 | + // Sort: directories first, then by name | |
| 97 | + const sortTree = (node: TreeNode) => { | |
| 98 | + node.children.sort((a, b) => { | |
| 99 | + if (a.isDirectory && !b.isDirectory) return -1; | |
| 100 | + if (!a.isDirectory && b.isDirectory) return 1; | |
| 101 | + return a.name.localeCompare(b.name); | |
| 102 | + }); | |
| 103 | + node.children.forEach(sortTree); | |
| 104 | + }; | |
| 105 | + sortTree(root); | |
| 106 | + | |
| 107 | + return root; | |
| 108 | + }, [entries]); | |
| 109 | + | |
| 110 | + const handlePreviewFile = async (path: string, name: string) => { | |
| 111 | + if (!zipInstance) return; | |
| 112 | + try { | |
| 113 | + const content = await zipInstance.file(path)?.async("string"); | |
| 114 | + setPreviewContent(content || ""); | |
| 115 | + setPreviewName(name); | |
| 116 | + } catch { | |
| 117 | + setPreviewContent("Impossible de lire ce fichier"); | |
| 118 | + setPreviewName(name); | |
| 119 | + } | |
| 120 | + }; | |
| 121 | + | |
| 122 | + const handleDownloadFile = async (path: string, name: string) => { | |
| 123 | + if (!zipInstance) return; | |
| 124 | + try { | |
| 125 | + const blob = await zipInstance.file(path)?.async("blob"); | |
| 126 | + if (!blob) return; | |
| 127 | + const url = URL.createObjectURL(blob); | |
| 128 | + const a = document.createElement("a"); | |
| 129 | + a.href = url; | |
| 130 | + a.download = name; | |
| 131 | + a.click(); | |
| 132 | + URL.revokeObjectURL(url); | |
| 133 | + } catch { | |
| 134 | + // ignore | |
| 135 | + } | |
| 136 | + }; | |
| 137 | + | |
| 138 | + if (loading) { | |
| 139 | + return ( | |
| 140 | + <div className="flex items-center justify-center h-full"> | |
| 141 | + <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 142 | + </div> | |
| 143 | + ); | |
| 144 | + } | |
| 145 | + | |
| 146 | + if (error) { | |
| 147 | + return ( | |
| 148 | + <div className="flex flex-col items-center justify-center h-full text-gray-400"> | |
| 149 | + <Archive className="w-12 h-12 mb-4 text-gray-600" /> | |
| 150 | + <p>{error}</p> | |
| 151 | + </div> | |
| 152 | + ); | |
| 153 | + } | |
| 154 | + | |
| 155 | + const fileCount = entries.filter((e) => !e.isDirectory).length; | |
| 156 | + const totalSize = entries.reduce((sum, e) => sum + e.size, 0); | |
| 157 | + | |
| 158 | + return ( | |
| 159 | + <div className="flex flex-col h-full"> | |
| 160 | + {/* Stats */} | |
| 161 | + <div className="flex items-center gap-3 px-3 py-2 border-b border-white/10 bg-white/5 text-xs text-gray-400"> | |
| 162 | + <span>{fileCount} fichiers</span> | |
| 163 | + <span>{formatFileSize(totalSize)} total</span> | |
| 164 | + </div> | |
| 165 | + | |
| 166 | + <div className="flex flex-1 overflow-hidden"> | |
| 167 | + {/* Tree */} | |
| 168 | + <div className="flex-1 overflow-auto p-3"> | |
| 169 | + {tree.children.map((node) => ( | |
| 170 | + <TreeNodeView | |
| 171 | + key={node.path} | |
| 172 | + node={node} | |
| 173 | + onPreview={handlePreviewFile} | |
| 174 | + onDownload={handleDownloadFile} | |
| 175 | + /> | |
| 176 | + ))} | |
| 177 | + </div> | |
| 178 | + | |
| 179 | + {/* Preview panel */} | |
| 180 | + {previewContent !== null && ( | |
| 181 | + <div className="w-1/2 border-l border-white/10 flex flex-col"> | |
| 182 | + <div className="flex items-center justify-between px-3 py-2 border-b border-white/10 bg-white/5"> | |
| 183 | + <span className="text-xs text-gray-400 truncate">{previewName}</span> | |
| 184 | + <button | |
| 185 | + onClick={() => setPreviewContent(null)} | |
| 186 | + className="text-xs text-gray-500 hover:text-white" | |
| 187 | + > | |
| 188 | + Fermer | |
| 189 | + </button> | |
| 190 | + </div> | |
| 191 | + <pre className="flex-1 overflow-auto p-3 text-xs text-gray-300 font-mono whitespace-pre-wrap"> | |
| 192 | + {previewContent} | |
| 193 | + </pre> | |
| 194 | + </div> | |
| 195 | + )} | |
| 196 | + </div> | |
| 197 | + </div> | |
| 198 | + ); | |
| 199 | +} | |
| 200 | + | |
| 201 | +function TreeNodeView({ | |
| 202 | + node, | |
| 203 | + onPreview, | |
| 204 | + onDownload, | |
| 205 | +}: { | |
| 206 | + node: TreeNode; | |
| 207 | + onPreview: (path: string, name: string) => void; | |
| 208 | + onDownload: (path: string, name: string) => void; | |
| 209 | +}) { | |
| 210 | + const [expanded, setExpanded] = useState(true); | |
| 211 | + | |
| 212 | + const isTextFile = (name: string) => { | |
| 213 | + const ext = name.split(".").pop()?.toLowerCase() || ""; | |
| 214 | + return ["txt", "md", "json", "xml", "csv", "js", "ts", "jsx", "tsx", "html", "css", "py", "rb", "go", "rs", "java", "c", "cpp", "h", "yml", "yaml", "toml", "sh", "env", "log", "sql", "conf", "cfg"].includes(ext); | |
| 215 | + }; | |
| 216 | + | |
| 217 | + if (node.isDirectory) { | |
| 218 | + return ( | |
| 219 | + <div> | |
| 220 | + <button | |
| 221 | + onClick={() => setExpanded(!expanded)} | |
| 222 | + className="flex items-center gap-1.5 w-full px-1 py-0.5 text-sm text-gray-300 hover:text-white hover:bg-white/5 rounded transition-colors" | |
| 223 | + > | |
| 224 | + {expanded ? <ChevronDown className="w-3 h-3 text-gray-500" /> : <ChevronRight className="w-3 h-3 text-gray-500" />} | |
| 225 | + <Folder className="w-4 h-4 text-yellow-500/70" /> | |
| 226 | + <span>{node.name}</span> | |
| 227 | + </button> | |
| 228 | + {expanded && ( | |
| 229 | + <div className="ml-4"> | |
| 230 | + {node.children.map((child) => ( | |
| 231 | + <TreeNodeView key={child.path} node={child} onPreview={onPreview} onDownload={onDownload} /> | |
| 232 | + ))} | |
| 233 | + </div> | |
| 234 | + )} | |
| 235 | + </div> | |
| 236 | + ); | |
| 237 | + } | |
| 238 | + | |
| 239 | + return ( | |
| 240 | + <div className="flex items-center gap-1.5 group px-1 py-0.5 text-sm text-gray-400 hover:text-white hover:bg-white/5 rounded transition-colors"> | |
| 241 | + <div className="w-3" /> | |
| 242 | + <File className="w-4 h-4 text-gray-500" /> | |
| 243 | + <span className="flex-1 truncate">{node.name}</span> | |
| 244 | + <span className="text-xs text-gray-600 mr-2">{formatFileSize(node.size)}</span> | |
| 245 | + <div className="hidden group-hover:flex items-center gap-1"> | |
| 246 | + {isTextFile(node.name) && ( | |
| 247 | + <button | |
| 248 | + onClick={() => onPreview(node.path, node.name)} | |
| 249 | + className="p-1 text-gray-500 hover:text-white transition-colors" | |
| 250 | + title="Aperçu" | |
| 251 | + > | |
| 252 | + <Eye className="w-3 h-3" /> | |
| 253 | + </button> | |
| 254 | + )} | |
| 255 | + <button | |
| 256 | + onClick={() => onDownload(node.path, node.name)} | |
| 257 | + className="p-1 text-gray-500 hover:text-white transition-colors" | |
| 258 | + title="Télécharger" | |
| 259 | + > | |
| 260 | + <Download className="w-3 h-3" /> | |
| 261 | + </button> | |
| 262 | + </div> | |
| 263 | + </div> | |
| 264 | + ); | |
| 265 | +} | |
added
components/preview/plugins/AudioPreview.tsx
+183 −0
@@ -0,0 +1,183 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useRef, useState, useCallback } from "react"; | |
| 4 | +import { Play, Pause, Volume2, VolumeX, Gauge } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | + | |
| 7 | +export default function AudioPreview({ file }: PreviewPluginProps) { | |
| 8 | + const containerRef = useRef<HTMLDivElement>(null); | |
| 9 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 10 | + const wavesurferRef = useRef<any>(null); | |
| 11 | + const [isPlaying, setIsPlaying] = useState(false); | |
| 12 | + const [currentTime, setCurrentTime] = useState(0); | |
| 13 | + const [duration, setDuration] = useState(0); | |
| 14 | + const [volume, setVolume] = useState(0.8); | |
| 15 | + const [isMuted, setIsMuted] = useState(false); | |
| 16 | + const [playbackRate, setPlaybackRate] = useState(1); | |
| 17 | + const [loading, setLoading] = useState(true); | |
| 18 | + const [error, setError] = useState(false); | |
| 19 | + | |
| 20 | + useEffect(() => { | |
| 21 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 22 | + let ws: any = null; | |
| 23 | + | |
| 24 | + const initWaveSurfer = async () => { | |
| 25 | + try { | |
| 26 | + const WaveSurfer = (await import("wavesurfer.js")).default; | |
| 27 | + | |
| 28 | + if (!containerRef.current) return; | |
| 29 | + | |
| 30 | + ws = WaveSurfer.create({ | |
| 31 | + container: containerRef.current, | |
| 32 | + waveColor: "#4b5563", | |
| 33 | + progressColor: "#9ca3af", | |
| 34 | + cursorColor: "#ffffff", | |
| 35 | + barWidth: 2, | |
| 36 | + barGap: 1, | |
| 37 | + barRadius: 2, | |
| 38 | + height: 120, | |
| 39 | + normalize: true, | |
| 40 | + url: `/api/files/${file.id}/stream`, | |
| 41 | + }); | |
| 42 | + | |
| 43 | + ws.on("ready", () => { | |
| 44 | + setDuration(ws.getDuration()); | |
| 45 | + setLoading(false); | |
| 46 | + ws.setVolume(volume); | |
| 47 | + }); | |
| 48 | + | |
| 49 | + ws.on("audioprocess", () => { | |
| 50 | + setCurrentTime(ws.getCurrentTime()); | |
| 51 | + }); | |
| 52 | + | |
| 53 | + ws.on("seeking", () => { | |
| 54 | + setCurrentTime(ws.getCurrentTime()); | |
| 55 | + }); | |
| 56 | + | |
| 57 | + ws.on("finish", () => { | |
| 58 | + setIsPlaying(false); | |
| 59 | + }); | |
| 60 | + | |
| 61 | + ws.on("error", () => { | |
| 62 | + setError(true); | |
| 63 | + setLoading(false); | |
| 64 | + }); | |
| 65 | + | |
| 66 | + wavesurferRef.current = ws; | |
| 67 | + } catch { | |
| 68 | + setError(true); | |
| 69 | + setLoading(false); | |
| 70 | + } | |
| 71 | + }; | |
| 72 | + | |
| 73 | + initWaveSurfer(); | |
| 74 | + | |
| 75 | + return () => { | |
| 76 | + if (ws) ws.destroy(); | |
| 77 | + }; | |
| 78 | + }, [file.id]); | |
| 79 | + | |
| 80 | + const togglePlay = useCallback(() => { | |
| 81 | + const ws = wavesurferRef.current; | |
| 82 | + if (!ws) return; | |
| 83 | + ws.playPause(); | |
| 84 | + setIsPlaying(!isPlaying); | |
| 85 | + }, [isPlaying]); | |
| 86 | + | |
| 87 | + const toggleMute = useCallback(() => { | |
| 88 | + const ws = wavesurferRef.current; | |
| 89 | + if (!ws) return; | |
| 90 | + const newMuted = !isMuted; | |
| 91 | + ws.setVolume(newMuted ? 0 : volume); | |
| 92 | + setIsMuted(newMuted); | |
| 93 | + }, [isMuted, volume]); | |
| 94 | + | |
| 95 | + const changeVolume = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { | |
| 96 | + const ws = wavesurferRef.current; | |
| 97 | + if (!ws) return; | |
| 98 | + const vol = parseFloat(e.target.value); | |
| 99 | + ws.setVolume(vol); | |
| 100 | + setVolume(vol); | |
| 101 | + setIsMuted(vol === 0); | |
| 102 | + }, []); | |
| 103 | + | |
| 104 | + const cycleSpeed = useCallback(() => { | |
| 105 | + const ws = wavesurferRef.current; | |
| 106 | + if (!ws) return; | |
| 107 | + const speeds = [0.5, 1, 1.25, 1.5, 2]; | |
| 108 | + const idx = speeds.indexOf(playbackRate); | |
| 109 | + const next = speeds[(idx + 1) % speeds.length]; | |
| 110 | + setPlaybackRate(next); | |
| 111 | + ws.setPlaybackRate(next); | |
| 112 | + }, [playbackRate]); | |
| 113 | + | |
| 114 | + const formatTime = (t: number) => { | |
| 115 | + const m = Math.floor(t / 60); | |
| 116 | + const s = Math.floor(t % 60); | |
| 117 | + return `${m}:${s.toString().padStart(2, "0")}`; | |
| 118 | + }; | |
| 119 | + | |
| 120 | + if (error) { | |
| 121 | + return ( | |
| 122 | + <div className="flex flex-col items-center justify-center h-full p-8"> | |
| 123 | + <p className="text-gray-400 mb-4">Lecteur audio</p> | |
| 124 | + <audio src={`/api/files/${file.id}/stream`} controls className="w-full max-w-md" /> | |
| 125 | + </div> | |
| 126 | + ); | |
| 127 | + } | |
| 128 | + | |
| 129 | + return ( | |
| 130 | + <div className="flex flex-col items-center justify-center h-full p-8 gap-6"> | |
| 131 | + {/* File info */} | |
| 132 | + <div className="text-center"> | |
| 133 | + <div className="w-20 h-20 mx-auto mb-4 rounded-full bg-white/5 flex items-center justify-center"> | |
| 134 | + <Volume2 className="w-10 h-10 text-gray-400" /> | |
| 135 | + </div> | |
| 136 | + <p className="text-sm text-gray-400">{file.name}</p> | |
| 137 | + </div> | |
| 138 | + | |
| 139 | + {/* Waveform */} | |
| 140 | + <div className="w-full max-w-lg"> | |
| 141 | + {loading && ( | |
| 142 | + <div className="flex items-center justify-center h-[120px]"> | |
| 143 | + <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 144 | + </div> | |
| 145 | + )} | |
| 146 | + <div ref={containerRef} className={loading ? "hidden" : ""} /> | |
| 147 | + </div> | |
| 148 | + | |
| 149 | + {/* Controls */} | |
| 150 | + <div className="flex items-center gap-4"> | |
| 151 | + <button onClick={togglePlay} className="p-3 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors"> | |
| 152 | + {isPlaying ? <Pause className="w-5 h-5" /> : <Play className="w-5 h-5 ml-0.5" />} | |
| 153 | + </button> | |
| 154 | + | |
| 155 | + <span className="text-xs text-gray-500 min-w-[5rem] text-center"> | |
| 156 | + {formatTime(currentTime)} / {formatTime(duration)} | |
| 157 | + </span> | |
| 158 | + | |
| 159 | + <button onClick={cycleSpeed} className="text-xs text-gray-400 hover:text-white flex items-center gap-1"> | |
| 160 | + <Gauge className="w-3 h-3" /> | |
| 161 | + {playbackRate}x | |
| 162 | + </button> | |
| 163 | + | |
| 164 | + <div className="flex items-center gap-1"> | |
| 165 | + <button onClick={toggleMute} className="text-gray-400 hover:text-white"> | |
| 166 | + {isMuted || volume === 0 ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />} | |
| 167 | + </button> | |
| 168 | + <input | |
| 169 | + type="range" | |
| 170 | + min={0} | |
| 171 | + max={1} | |
| 172 | + step={0.05} | |
| 173 | + value={isMuted ? 0 : volume} | |
| 174 | + onChange={changeVolume} | |
| 175 | + className="w-20 h-1 appearance-none bg-white/20 rounded-full cursor-pointer | |
| 176 | + [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5 | |
| 177 | + [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white" | |
| 178 | + /> | |
| 179 | + </div> | |
| 180 | + </div> | |
| 181 | + </div> | |
| 182 | + ); | |
| 183 | +} | |
added
components/preview/plugins/CodePreview.tsx
+246 −0
@@ -0,0 +1,246 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useRef, useState, useCallback } from "react"; | |
| 4 | +import { Copy, Check, WrapText } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | +import { getLanguageFromMime, getLanguageFromFilename } from "@/lib/utils"; | |
| 7 | + | |
| 8 | +export default function CodePreview({ file, isEditing, onSave }: PreviewPluginProps) { | |
| 9 | + const editorRef = useRef<HTMLDivElement>(null); | |
| 10 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 11 | + const viewRef = useRef<any>(null); | |
| 12 | + const [content, setContent] = useState<string>(""); | |
| 13 | + const [loading, setLoading] = useState(true); | |
| 14 | + const [copied, setCopied] = useState(false); | |
| 15 | + const [wordWrap, setWordWrap] = useState(false); | |
| 16 | + const [modified, setModified] = useState(false); | |
| 17 | + const saveTimeout = useRef<ReturnType<typeof setTimeout>>(); | |
| 18 | + | |
| 19 | + // Load content | |
| 20 | + useEffect(() => { | |
| 21 | + setLoading(true); | |
| 22 | + fetch(`/api/files/${file.id}/preview`) | |
| 23 | + .then((res) => res.text()) | |
| 24 | + .then((text) => { | |
| 25 | + setContent(text); | |
| 26 | + setLoading(false); | |
| 27 | + }) | |
| 28 | + .catch(() => setLoading(false)); | |
| 29 | + }, [file.id]); | |
| 30 | + | |
| 31 | + // Initialize CodeMirror | |
| 32 | + useEffect(() => { | |
| 33 | + if (loading || !editorRef.current) return; | |
| 34 | + | |
| 35 | + let destroyed = false; | |
| 36 | + | |
| 37 | + const initEditor = async () => { | |
| 38 | + const { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, highlightActiveLine } = await import("@codemirror/view"); | |
| 39 | + const { EditorState } = await import("@codemirror/state"); | |
| 40 | + const { defaultKeymap, history, historyKeymap, indentWithTab } = await import("@codemirror/commands"); | |
| 41 | + const { syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldGutter, foldKeymap, indentOnInput } = await import("@codemirror/language"); | |
| 42 | + const { searchKeymap, highlightSelectionMatches } = await import("@codemirror/search"); | |
| 43 | + const { oneDark } = await import("@codemirror/theme-one-dark"); | |
| 44 | + | |
| 45 | + if (destroyed || !editorRef.current) return; | |
| 46 | + | |
| 47 | + // Get language extension | |
| 48 | + const lang = getLanguageFromMime(file.mimeType) || getLanguageFromFilename(file.name); | |
| 49 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 50 | + let langExtension: any = null; | |
| 51 | + | |
| 52 | + try { | |
| 53 | + switch (lang) { | |
| 54 | + case "javascript": case "typescript": { | |
| 55 | + const { javascript } = await import("@codemirror/lang-javascript"); | |
| 56 | + langExtension = javascript({ typescript: lang === "typescript", jsx: true }); | |
| 57 | + break; | |
| 58 | + } | |
| 59 | + case "html": { | |
| 60 | + const { html } = await import("@codemirror/lang-html"); | |
| 61 | + langExtension = html(); | |
| 62 | + break; | |
| 63 | + } | |
| 64 | + case "css": { | |
| 65 | + const { css } = await import("@codemirror/lang-css"); | |
| 66 | + langExtension = css(); | |
| 67 | + break; | |
| 68 | + } | |
| 69 | + case "json": { | |
| 70 | + const { json } = await import("@codemirror/lang-json"); | |
| 71 | + langExtension = json(); | |
| 72 | + break; | |
| 73 | + } | |
| 74 | + case "markdown": { | |
| 75 | + const { markdown } = await import("@codemirror/lang-markdown"); | |
| 76 | + langExtension = markdown(); | |
| 77 | + break; | |
| 78 | + } | |
| 79 | + case "python": { | |
| 80 | + const { python } = await import("@codemirror/lang-python"); | |
| 81 | + langExtension = python(); | |
| 82 | + break; | |
| 83 | + } | |
| 84 | + case "xml": { | |
| 85 | + const { xml } = await import("@codemirror/lang-xml"); | |
| 86 | + langExtension = xml(); | |
| 87 | + break; | |
| 88 | + } | |
| 89 | + case "sql": { | |
| 90 | + const { sql } = await import("@codemirror/lang-sql"); | |
| 91 | + langExtension = sql(); | |
| 92 | + break; | |
| 93 | + } | |
| 94 | + case "java": { | |
| 95 | + const { java } = await import("@codemirror/lang-java"); | |
| 96 | + langExtension = java(); | |
| 97 | + break; | |
| 98 | + } | |
| 99 | + case "c": case "cpp": { | |
| 100 | + const { cpp } = await import("@codemirror/lang-cpp"); | |
| 101 | + langExtension = cpp(); | |
| 102 | + break; | |
| 103 | + } | |
| 104 | + case "rust": { | |
| 105 | + const { rust } = await import("@codemirror/lang-rust"); | |
| 106 | + langExtension = rust(); | |
| 107 | + break; | |
| 108 | + } | |
| 109 | + case "go": { | |
| 110 | + const { go } = await import("@codemirror/lang-go"); | |
| 111 | + langExtension = go(); | |
| 112 | + break; | |
| 113 | + } | |
| 114 | + case "php": { | |
| 115 | + const { php } = await import("@codemirror/lang-php"); | |
| 116 | + langExtension = php(); | |
| 117 | + break; | |
| 118 | + } | |
| 119 | + } | |
| 120 | + } catch { | |
| 121 | + // Language not available, continue without | |
| 122 | + } | |
| 123 | + | |
| 124 | + const extensions = [ | |
| 125 | + lineNumbers(), | |
| 126 | + highlightActiveLineGutter(), | |
| 127 | + highlightSpecialChars(), | |
| 128 | + drawSelection(), | |
| 129 | + highlightActiveLine(), | |
| 130 | + bracketMatching(), | |
| 131 | + foldGutter(), | |
| 132 | + indentOnInput(), | |
| 133 | + syntaxHighlighting(defaultHighlightStyle), | |
| 134 | + highlightSelectionMatches(), | |
| 135 | + history(), | |
| 136 | + oneDark, | |
| 137 | + keymap.of([ | |
| 138 | + ...defaultKeymap, | |
| 139 | + ...historyKeymap, | |
| 140 | + ...foldKeymap, | |
| 141 | + ...searchKeymap, | |
| 142 | + indentWithTab, | |
| 143 | + ]), | |
| 144 | + EditorView.editable.of(isEditing), | |
| 145 | + EditorState.readOnly.of(!isEditing), | |
| 146 | + ]; | |
| 147 | + | |
| 148 | + if (langExtension) extensions.push(langExtension); | |
| 149 | + | |
| 150 | + if (wordWrap) { | |
| 151 | + extensions.push(EditorView.lineWrapping); | |
| 152 | + } | |
| 153 | + | |
| 154 | + if (isEditing && onSave) { | |
| 155 | + extensions.push( | |
| 156 | + EditorView.updateListener.of((update) => { | |
| 157 | + if (update.docChanged) { | |
| 158 | + setModified(true); | |
| 159 | + if (saveTimeout.current) clearTimeout(saveTimeout.current); | |
| 160 | + saveTimeout.current = setTimeout(() => { | |
| 161 | + const text = update.state.doc.toString(); | |
| 162 | + onSave(text, false).then(() => setModified(false)); | |
| 163 | + }, 2000); | |
| 164 | + } | |
| 165 | + }), | |
| 166 | + keymap.of([{ | |
| 167 | + key: "Mod-s", | |
| 168 | + run: (view) => { | |
| 169 | + if (saveTimeout.current) clearTimeout(saveTimeout.current); | |
| 170 | + const text = view.state.doc.toString(); | |
| 171 | + onSave(text, false).then(() => setModified(false)); | |
| 172 | + return true; | |
| 173 | + }, | |
| 174 | + }]), | |
| 175 | + ); | |
| 176 | + } | |
| 177 | + | |
| 178 | + // Clean up previous editor | |
| 179 | + if (viewRef.current) viewRef.current.destroy(); | |
| 180 | + | |
| 181 | + editorRef.current.innerHTML = ""; | |
| 182 | + const view = new EditorView({ | |
| 183 | + state: EditorState.create({ doc: content, extensions }), | |
| 184 | + parent: editorRef.current, | |
| 185 | + }); | |
| 186 | + | |
| 187 | + viewRef.current = view; | |
| 188 | + }; | |
| 189 | + | |
| 190 | + initEditor(); | |
| 191 | + | |
| 192 | + return () => { | |
| 193 | + destroyed = true; | |
| 194 | + if (viewRef.current) { | |
| 195 | + viewRef.current.destroy(); | |
| 196 | + viewRef.current = null; | |
| 197 | + } | |
| 198 | + }; | |
| 199 | + }, [loading, content, isEditing, wordWrap, file.mimeType, file.name]); | |
| 200 | + | |
| 201 | + const copyContent = useCallback(() => { | |
| 202 | + navigator.clipboard.writeText(content); | |
| 203 | + setCopied(true); | |
| 204 | + setTimeout(() => setCopied(false), 2000); | |
| 205 | + }, [content]); | |
| 206 | + | |
| 207 | + if (loading) { | |
| 208 | + return ( | |
| 209 | + <div className="flex items-center justify-center h-full"> | |
| 210 | + <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 211 | + </div> | |
| 212 | + ); | |
| 213 | + } | |
| 214 | + | |
| 215 | + return ( | |
| 216 | + <div className="flex flex-col h-full"> | |
| 217 | + {/* Toolbar */} | |
| 218 | + <div className="flex items-center gap-1 px-3 py-2 border-b border-white/10 bg-white/5"> | |
| 219 | + <span className="text-xs text-gray-500 mr-2"> | |
| 220 | + {getLanguageFromMime(file.mimeType) || getLanguageFromFilename(file.name)} | |
| 221 | + </span> | |
| 222 | + {isEditing && modified && ( | |
| 223 | + <span className="text-xs text-yellow-400 mr-2">modifié</span> | |
| 224 | + )} | |
| 225 | + <div className="flex-1" /> | |
| 226 | + <button | |
| 227 | + onClick={() => setWordWrap(!wordWrap)} | |
| 228 | + className={`p-1.5 transition-colors rounded hover:bg-white/10 ${wordWrap ? "text-white" : "text-gray-400"}`} | |
| 229 | + title="Retour à la ligne" | |
| 230 | + > | |
| 231 | + <WrapText className="w-4 h-4" /> | |
| 232 | + </button> | |
| 233 | + <button | |
| 234 | + onClick={copyContent} | |
| 235 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 236 | + title="Copier" | |
| 237 | + > | |
| 238 | + {copied ? <Check className="w-4 h-4 text-green-400" /> : <Copy className="w-4 h-4" />} | |
| 239 | + </button> | |
| 240 | + </div> | |
| 241 | + | |
| 242 | + {/* Editor */} | |
| 243 | + <div ref={editorRef} className="flex-1 overflow-auto [&_.cm-editor]:h-full [&_.cm-scroller]:!overflow-auto" /> | |
| 244 | + </div> | |
| 245 | + ); | |
| 246 | +} | |
added
components/preview/plugins/CsvJsonPreview.tsx
+355 −0
@@ -0,0 +1,355 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useMemo, useCallback } from "react"; | |
| 4 | +import { ArrowUpDown, ChevronDown, ChevronRight, Filter } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | + | |
| 7 | +interface SortConfig { | |
| 8 | + column: number; | |
| 9 | + direction: "asc" | "desc"; | |
| 10 | +} | |
| 11 | + | |
| 12 | +export default function CsvJsonPreview({ file }: PreviewPluginProps) { | |
| 13 | + const [content, setContent] = useState<string>(""); | |
| 14 | + const [loading, setLoading] = useState(true); | |
| 15 | + | |
| 16 | + const isJson = file.mimeType.includes("json"); | |
| 17 | + | |
| 18 | + useEffect(() => { | |
| 19 | + setLoading(true); | |
| 20 | + fetch(`/api/files/${file.id}/preview`) | |
| 21 | + .then((res) => res.text()) | |
| 22 | + .then((text) => { | |
| 23 | + setContent(text); | |
| 24 | + setLoading(false); | |
| 25 | + }) | |
| 26 | + .catch(() => setLoading(false)); | |
| 27 | + }, [file.id]); | |
| 28 | + | |
| 29 | + if (loading) { | |
| 30 | + return ( | |
| 31 | + <div className="flex items-center justify-center h-full"> | |
| 32 | + <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 33 | + </div> | |
| 34 | + ); | |
| 35 | + } | |
| 36 | + | |
| 37 | + if (isJson) { | |
| 38 | + return <JsonViewer content={content} />; | |
| 39 | + } | |
| 40 | + | |
| 41 | + return <CsvViewer content={content} />; | |
| 42 | +} | |
| 43 | + | |
| 44 | +// --- CSV Viewer --- | |
| 45 | + | |
| 46 | +function CsvViewer({ content }: { content: string }) { | |
| 47 | + const [sortConfig, setSortConfig] = useState<SortConfig | null>(null); | |
| 48 | + const [filterColumn, setFilterColumn] = useState<number | null>(null); | |
| 49 | + const [filterText, setFilterText] = useState(""); | |
| 50 | + const [page, setPage] = useState(0); | |
| 51 | + const pageSize = 100; | |
| 52 | + | |
| 53 | + const { headers, rows } = useMemo(() => { | |
| 54 | + try { | |
| 55 | + // eslint-disable-next-line @typescript-eslint/no-require-imports | |
| 56 | + const Papa = require("papaparse"); | |
| 57 | + const result = Papa.parse(content, { header: false, skipEmptyLines: true }); | |
| 58 | + const data = result.data as string[][]; | |
| 59 | + return { headers: data[0] || [], rows: data.slice(1) }; | |
| 60 | + } catch { | |
| 61 | + const lines = content.split("\n").filter(Boolean); | |
| 62 | + return { | |
| 63 | + headers: lines[0]?.split(",") || [], | |
| 64 | + rows: lines.slice(1).map((line) => line.split(",")), | |
| 65 | + }; | |
| 66 | + } | |
| 67 | + }, [content]); | |
| 68 | + | |
| 69 | + const filteredRows = useMemo(() => { | |
| 70 | + let result = [...rows]; | |
| 71 | + | |
| 72 | + if (filterColumn !== null && filterText) { | |
| 73 | + result = result.filter((row) => | |
| 74 | + (row[filterColumn] || "").toLowerCase().includes(filterText.toLowerCase()) | |
| 75 | + ); | |
| 76 | + } | |
| 77 | + | |
| 78 | + if (sortConfig) { | |
| 79 | + result.sort((a, b) => { | |
| 80 | + const aVal = a[sortConfig.column] || ""; | |
| 81 | + const bVal = b[sortConfig.column] || ""; | |
| 82 | + const numA = parseFloat(aVal); | |
| 83 | + const numB = parseFloat(bVal); | |
| 84 | + if (!isNaN(numA) && !isNaN(numB)) { | |
| 85 | + return sortConfig.direction === "asc" ? numA - numB : numB - numA; | |
| 86 | + } | |
| 87 | + return sortConfig.direction === "asc" | |
| 88 | + ? aVal.localeCompare(bVal) | |
| 89 | + : bVal.localeCompare(aVal); | |
| 90 | + }); | |
| 91 | + } | |
| 92 | + | |
| 93 | + return result; | |
| 94 | + }, [rows, sortConfig, filterColumn, filterText]); | |
| 95 | + | |
| 96 | + const pageRows = filteredRows.slice(page * pageSize, (page + 1) * pageSize); | |
| 97 | + const totalPages = Math.ceil(filteredRows.length / pageSize); | |
| 98 | + | |
| 99 | + const toggleSort = useCallback((col: number) => { | |
| 100 | + setSortConfig((prev) => { | |
| 101 | + if (prev?.column === col) { | |
| 102 | + return prev.direction === "asc" ? { column: col, direction: "desc" } : null; | |
| 103 | + } | |
| 104 | + return { column: col, direction: "asc" }; | |
| 105 | + }); | |
| 106 | + }, []); | |
| 107 | + | |
| 108 | + return ( | |
| 109 | + <div className="flex flex-col h-full"> | |
| 110 | + {/* Stats */} | |
| 111 | + <div className="flex items-center gap-3 px-3 py-2 border-b border-white/10 bg-white/5 text-xs text-gray-400"> | |
| 112 | + <span>{rows.length} lignes</span> | |
| 113 | + <span>{headers.length} colonnes</span> | |
| 114 | + {filterText && <span>{filteredRows.length} résultats</span>} | |
| 115 | + </div> | |
| 116 | + | |
| 117 | + {/* Table */} | |
| 118 | + <div className="flex-1 overflow-auto"> | |
| 119 | + <table className="w-full text-sm border-collapse"> | |
| 120 | + <thead className="sticky top-0 z-10"> | |
| 121 | + <tr> | |
| 122 | + <th className="border border-white/10 px-2 py-1.5 text-left text-gray-500 bg-[#1a1a1a] text-xs w-10"> | |
| 123 | + # | |
| 124 | + </th> | |
| 125 | + {headers.map((header, i) => ( | |
| 126 | + <th | |
| 127 | + key={i} | |
| 128 | + className="border border-white/10 px-3 py-1.5 text-left text-gray-300 bg-[#1a1a1a] text-xs" | |
| 129 | + > | |
| 130 | + <div className="flex items-center gap-1"> | |
| 131 | + <button | |
| 132 | + onClick={() => toggleSort(i)} | |
| 133 | + className="flex items-center gap-1 hover:text-white transition-colors" | |
| 134 | + > | |
| 135 | + {header} | |
| 136 | + <ArrowUpDown className={`w-3 h-3 ${sortConfig?.column === i ? "text-white" : "text-gray-600"}`} /> | |
| 137 | + </button> | |
| 138 | + <button | |
| 139 | + onClick={() => { | |
| 140 | + setFilterColumn(filterColumn === i ? null : i); | |
| 141 | + setFilterText(""); | |
| 142 | + }} | |
| 143 | + className={`p-0.5 rounded transition-colors ${filterColumn === i ? "text-white" : "text-gray-600 hover:text-gray-400"}`} | |
| 144 | + > | |
| 145 | + <Filter className="w-3 h-3" /> | |
| 146 | + </button> | |
| 147 | + </div> | |
| 148 | + {filterColumn === i && ( | |
| 149 | + <input | |
| 150 | + type="text" | |
| 151 | + value={filterText} | |
| 152 | + onChange={(e) => { setFilterText(e.target.value); setPage(0); }} | |
| 153 | + placeholder="Filtrer..." | |
| 154 | + className="mt-1 w-full bg-white/10 border border-white/10 rounded px-2 py-0.5 text-xs text-white placeholder:text-gray-500" | |
| 155 | + autoFocus | |
| 156 | + /> | |
| 157 | + )} | |
| 158 | + </th> | |
| 159 | + ))} | |
| 160 | + </tr> | |
| 161 | + </thead> | |
| 162 | + <tbody> | |
| 163 | + {pageRows.map((row, i) => ( | |
| 164 | + <tr key={i} className="hover:bg-white/5"> | |
| 165 | + <td className="border border-white/10 px-2 py-1 text-gray-600 text-xs"> | |
| 166 | + {page * pageSize + i + 1} | |
| 167 | + </td> | |
| 168 | + {row.map((cell, j) => ( | |
| 169 | + <td key={j} className="border border-white/10 px-3 py-1 text-gray-400 text-xs"> | |
| 170 | + {cell} | |
| 171 | + </td> | |
| 172 | + ))} | |
| 173 | + </tr> | |
| 174 | + ))} | |
| 175 | + </tbody> | |
| 176 | + </table> | |
| 177 | + </div> | |
| 178 | + | |
| 179 | + {/* Pagination */} | |
| 180 | + {totalPages > 1 && ( | |
| 181 | + <div className="flex items-center justify-center gap-2 px-3 py-2 border-t border-white/10 bg-white/5"> | |
| 182 | + <button | |
| 183 | + onClick={() => setPage((p) => Math.max(0, p - 1))} | |
| 184 | + disabled={page === 0} | |
| 185 | + className="px-2 py-1 text-xs text-gray-400 hover:text-white disabled:opacity-30" | |
| 186 | + > | |
| 187 | + Précédent | |
| 188 | + </button> | |
| 189 | + <span className="text-xs text-gray-500"> | |
| 190 | + {page + 1} / {totalPages} | |
| 191 | + </span> | |
| 192 | + <button | |
| 193 | + onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))} | |
| 194 | + disabled={page >= totalPages - 1} | |
| 195 | + className="px-2 py-1 text-xs text-gray-400 hover:text-white disabled:opacity-30" | |
| 196 | + > | |
| 197 | + Suivant | |
| 198 | + </button> | |
| 199 | + </div> | |
| 200 | + )} | |
| 201 | + </div> | |
| 202 | + ); | |
| 203 | +} | |
| 204 | + | |
| 205 | +// --- JSON Viewer --- | |
| 206 | + | |
| 207 | +function JsonViewer({ content }: { content: string }) { | |
| 208 | + const parsed = useMemo(() => { | |
| 209 | + try { | |
| 210 | + return JSON.parse(content); | |
| 211 | + } catch { | |
| 212 | + return null; | |
| 213 | + } | |
| 214 | + }, [content]); | |
| 215 | + | |
| 216 | + if (parsed === null) { | |
| 217 | + return ( | |
| 218 | + <div className="overflow-auto h-full p-4"> | |
| 219 | + <pre className="text-sm text-gray-300 whitespace-pre-wrap font-mono">{content}</pre> | |
| 220 | + </div> | |
| 221 | + ); | |
| 222 | + } | |
| 223 | + | |
| 224 | + return ( | |
| 225 | + <div className="overflow-auto h-full p-4 font-mono text-sm"> | |
| 226 | + <JsonNode value={parsed} depth={0} /> | |
| 227 | + </div> | |
| 228 | + ); | |
| 229 | +} | |
| 230 | + | |
| 231 | +// eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 232 | +function JsonNode({ value, depth, keyName }: { value: any; depth: number; keyName?: string }) { | |
| 233 | + const [collapsed, setCollapsed] = useState(depth > 2); | |
| 234 | + | |
| 235 | + if (value === null) { | |
| 236 | + return ( | |
| 237 | + <span> | |
| 238 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 239 | + {keyName && <span className="text-gray-500">: </span>} | |
| 240 | + <span className="text-gray-500">null</span> | |
| 241 | + </span> | |
| 242 | + ); | |
| 243 | + } | |
| 244 | + | |
| 245 | + if (typeof value === "boolean") { | |
| 246 | + return ( | |
| 247 | + <span> | |
| 248 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 249 | + {keyName && <span className="text-gray-500">: </span>} | |
| 250 | + <span className="text-yellow-400">{value.toString()}</span> | |
| 251 | + </span> | |
| 252 | + ); | |
| 253 | + } | |
| 254 | + | |
| 255 | + if (typeof value === "number") { | |
| 256 | + return ( | |
| 257 | + <span> | |
| 258 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 259 | + {keyName && <span className="text-gray-500">: </span>} | |
| 260 | + <span className="text-blue-400">{value}</span> | |
| 261 | + </span> | |
| 262 | + ); | |
| 263 | + } | |
| 264 | + | |
| 265 | + if (typeof value === "string") { | |
| 266 | + return ( | |
| 267 | + <span> | |
| 268 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 269 | + {keyName && <span className="text-gray-500">: </span>} | |
| 270 | + <span className="text-green-400">"{value}"</span> | |
| 271 | + </span> | |
| 272 | + ); | |
| 273 | + } | |
| 274 | + | |
| 275 | + if (Array.isArray(value)) { | |
| 276 | + if (value.length === 0) { | |
| 277 | + return ( | |
| 278 | + <span> | |
| 279 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 280 | + {keyName && <span className="text-gray-500">: </span>} | |
| 281 | + <span className="text-gray-400">[]</span> | |
| 282 | + </span> | |
| 283 | + ); | |
| 284 | + } | |
| 285 | + | |
| 286 | + return ( | |
| 287 | + <div> | |
| 288 | + <span | |
| 289 | + className="cursor-pointer hover:text-white text-gray-400 inline-flex items-center gap-1" | |
| 290 | + onClick={() => setCollapsed(!collapsed)} | |
| 291 | + > | |
| 292 | + {collapsed ? <ChevronRight className="w-3 h-3 inline" /> : <ChevronDown className="w-3 h-3 inline" />} | |
| 293 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 294 | + {keyName && <span className="text-gray-500">: </span>} | |
| 295 | + <span>[</span> | |
| 296 | + {collapsed && <span className="text-gray-600">{value.length} items</span>} | |
| 297 | + {collapsed && <span>]</span>} | |
| 298 | + </span> | |
| 299 | + {!collapsed && ( | |
| 300 | + <div style={{ marginLeft: depth < 10 ? 16 : 0 }}> | |
| 301 | + {value.map((item, i) => ( | |
| 302 | + <div key={i}> | |
| 303 | + <JsonNode value={item} depth={depth + 1} /> | |
| 304 | + {i < value.length - 1 && <span className="text-gray-600">,</span>} | |
| 305 | + </div> | |
| 306 | + ))} | |
| 307 | + <div className="text-gray-400">]</div> | |
| 308 | + </div> | |
| 309 | + )} | |
| 310 | + </div> | |
| 311 | + ); | |
| 312 | + } | |
| 313 | + | |
| 314 | + if (typeof value === "object") { | |
| 315 | + const keys = Object.keys(value); | |
| 316 | + if (keys.length === 0) { | |
| 317 | + return ( | |
| 318 | + <span> | |
| 319 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 320 | + {keyName && <span className="text-gray-500">: </span>} | |
| 321 | + <span className="text-gray-400">{"{}"}</span> | |
| 322 | + </span> | |
| 323 | + ); | |
| 324 | + } | |
| 325 | + | |
| 326 | + return ( | |
| 327 | + <div> | |
| 328 | + <span | |
| 329 | + className="cursor-pointer hover:text-white text-gray-400 inline-flex items-center gap-1" | |
| 330 | + onClick={() => setCollapsed(!collapsed)} | |
| 331 | + > | |
| 332 | + {collapsed ? <ChevronRight className="w-3 h-3 inline" /> : <ChevronDown className="w-3 h-3 inline" />} | |
| 333 | + {keyName && <span className="text-purple-400">"{keyName}"</span>} | |
| 334 | + {keyName && <span className="text-gray-500">: </span>} | |
| 335 | + <span>{"{"}</span> | |
| 336 | + {collapsed && <span className="text-gray-600">{keys.length} keys</span>} | |
| 337 | + {collapsed && <span>{"}"}</span>} | |
| 338 | + </span> | |
| 339 | + {!collapsed && ( | |
| 340 | + <div style={{ marginLeft: depth < 10 ? 16 : 0 }}> | |
| 341 | + {keys.map((key, i) => ( | |
| 342 | + <div key={key}> | |
| 343 | + <JsonNode value={value[key]} depth={depth + 1} keyName={key} /> | |
| 344 | + {i < keys.length - 1 && <span className="text-gray-600">,</span>} | |
| 345 | + </div> | |
| 346 | + ))} | |
| 347 | + <div className="text-gray-400">{"}"}</div> | |
| 348 | + </div> | |
| 349 | + )} | |
| 350 | + </div> | |
| 351 | + ); | |
| 352 | + } | |
| 353 | + | |
| 354 | + return <span className="text-gray-400">{String(value)}</span>; | |
| 355 | +} | |
added
components/preview/plugins/FallbackPreview.tsx
+23 −0
@@ -0,0 +1,23 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { Download, FileQuestion } from "lucide-react"; | |
| 4 | +import type { PreviewPluginProps } from "./types"; | |
| 5 | +import { formatFileSize } from "@/lib/utils"; | |
| 6 | + | |
| 7 | +export default function FallbackPreview({ file }: PreviewPluginProps) { | |
| 8 | + return ( | |
| 9 | + <div className="flex flex-col items-center justify-center h-full text-gray-400 p-8"> | |
| 10 | + <FileQuestion className="w-16 h-16 mb-4 text-gray-600" /> | |
| 11 | + <p className="text-lg mb-1">Aperçu non disponible</p> | |
| 12 | + <p className="text-sm text-gray-500 mb-1">{file.name}</p> | |
| 13 | + <p className="text-xs text-gray-600 mb-6">{file.mimeType} - {formatFileSize(file.size)}</p> | |
| 14 | + <button | |
| 15 | + onClick={() => window.open(`/api/files/${file.id}/download`, "_blank")} | |
| 16 | + className="flex items-center gap-2 px-4 py-2 bg-white/10 rounded-lg text-sm text-white hover:bg-white/20 transition-colors" | |
| 17 | + > | |
| 18 | + <Download className="w-4 h-4" /> | |
| 19 | + Télécharger | |
| 20 | + </button> | |
| 21 | + </div> | |
| 22 | + ); | |
| 23 | +} | |
added
components/preview/plugins/ImagePreview.tsx
+154 −0
@@ -0,0 +1,154 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useRef, useCallback, useEffect } from "react"; | |
| 4 | +import { ZoomIn, ZoomOut, RotateCw, Maximize, Minimize } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | + | |
| 7 | +export default function ImagePreview({ file }: PreviewPluginProps) { | |
| 8 | + const [scale, setScale] = useState(1); | |
| 9 | + const [rotation, setRotation] = useState(0); | |
| 10 | + const [position, setPosition] = useState({ x: 0, y: 0 }); | |
| 11 | + const [isDragging, setIsDragging] = useState(false); | |
| 12 | + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); | |
| 13 | + const [isFitted, setIsFitted] = useState(true); | |
| 14 | + const containerRef = useRef<HTMLDivElement>(null); | |
| 15 | + | |
| 16 | + const resetView = useCallback(() => { | |
| 17 | + setScale(1); | |
| 18 | + setRotation(0); | |
| 19 | + setPosition({ x: 0, y: 0 }); | |
| 20 | + setIsFitted(true); | |
| 21 | + }, []); | |
| 22 | + | |
| 23 | + const handleWheel = useCallback((e: React.WheelEvent) => { | |
| 24 | + e.preventDefault(); | |
| 25 | + const delta = e.deltaY > 0 ? 0.9 : 1.1; | |
| 26 | + setScale((s) => Math.min(Math.max(s * delta, 0.1), 10)); | |
| 27 | + setIsFitted(false); | |
| 28 | + }, []); | |
| 29 | + | |
| 30 | + const handleMouseDown = useCallback((e: React.MouseEvent) => { | |
| 31 | + if (scale > 1) { | |
| 32 | + setIsDragging(true); | |
| 33 | + setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y }); | |
| 34 | + } | |
| 35 | + }, [scale, position]); | |
| 36 | + | |
| 37 | + const handleMouseMove = useCallback((e: React.MouseEvent) => { | |
| 38 | + if (isDragging) { | |
| 39 | + setPosition({ | |
| 40 | + x: e.clientX - dragStart.x, | |
| 41 | + y: e.clientY - dragStart.y, | |
| 42 | + }); | |
| 43 | + } | |
| 44 | + }, [isDragging, dragStart]); | |
| 45 | + | |
| 46 | + const handleMouseUp = useCallback(() => { | |
| 47 | + setIsDragging(false); | |
| 48 | + }, []); | |
| 49 | + | |
| 50 | + // Touch handling for pinch-to-zoom | |
| 51 | + const lastTouchDist = useRef<number | null>(null); | |
| 52 | + | |
| 53 | + const handleTouchMove = useCallback((e: React.TouchEvent) => { | |
| 54 | + if (e.touches.length === 2) { | |
| 55 | + e.preventDefault(); | |
| 56 | + const dist = Math.hypot( | |
| 57 | + e.touches[0].clientX - e.touches[1].clientX, | |
| 58 | + e.touches[0].clientY - e.touches[1].clientY | |
| 59 | + ); | |
| 60 | + if (lastTouchDist.current !== null) { | |
| 61 | + const delta = dist / lastTouchDist.current; | |
| 62 | + setScale((s) => Math.min(Math.max(s * delta, 0.1), 10)); | |
| 63 | + setIsFitted(false); | |
| 64 | + } | |
| 65 | + lastTouchDist.current = dist; | |
| 66 | + } else if (e.touches.length === 1 && scale > 1) { | |
| 67 | + const touch = e.touches[0]; | |
| 68 | + setPosition((pos) => ({ | |
| 69 | + x: pos.x + (touch.clientX - (dragStart.x || touch.clientX)), | |
| 70 | + y: pos.y + (touch.clientY - (dragStart.y || touch.clientY)), | |
| 71 | + })); | |
| 72 | + setDragStart({ x: touch.clientX, y: touch.clientY }); | |
| 73 | + } | |
| 74 | + }, [scale, dragStart]); | |
| 75 | + | |
| 76 | + const handleTouchEnd = useCallback(() => { | |
| 77 | + lastTouchDist.current = null; | |
| 78 | + }, []); | |
| 79 | + | |
| 80 | + const handleTouchStart = useCallback((e: React.TouchEvent) => { | |
| 81 | + if (e.touches.length === 1 && scale > 1) { | |
| 82 | + setDragStart({ x: e.touches[0].clientX, y: e.touches[0].clientY }); | |
| 83 | + } | |
| 84 | + }, [scale]); | |
| 85 | + | |
| 86 | + useEffect(() => { | |
| 87 | + resetView(); | |
| 88 | + }, [file.id, resetView]); | |
| 89 | + | |
| 90 | + return ( | |
| 91 | + <div className="flex flex-col h-full"> | |
| 92 | + {/* Controls */} | |
| 93 | + <div className="flex items-center gap-1 px-3 py-2 border-b border-white/10 bg-white/5"> | |
| 94 | + <button | |
| 95 | + onClick={() => { setScale((s) => Math.min(s * 1.2, 10)); setIsFitted(false); }} | |
| 96 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 97 | + title="Zoom in" | |
| 98 | + > | |
| 99 | + <ZoomIn className="w-4 h-4" /> | |
| 100 | + </button> | |
| 101 | + <button | |
| 102 | + onClick={() => { setScale((s) => Math.max(s * 0.8, 0.1)); setIsFitted(false); }} | |
| 103 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 104 | + title="Zoom out" | |
| 105 | + > | |
| 106 | + <ZoomOut className="w-4 h-4" /> | |
| 107 | + </button> | |
| 108 | + <span className="text-xs text-gray-500 min-w-[3rem] text-center"> | |
| 109 | + {Math.round(scale * 100)}% | |
| 110 | + </span> | |
| 111 | + <button | |
| 112 | + onClick={() => setRotation((r) => (r + 90) % 360)} | |
| 113 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 114 | + title="Rotate" | |
| 115 | + > | |
| 116 | + <RotateCw className="w-4 h-4" /> | |
| 117 | + </button> | |
| 118 | + <button | |
| 119 | + onClick={resetView} | |
| 120 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 121 | + title={isFitted ? "Taille réelle" : "Ajuster"} | |
| 122 | + > | |
| 123 | + {isFitted ? <Maximize className="w-4 h-4" /> : <Minimize className="w-4 h-4" />} | |
| 124 | + </button> | |
| 125 | + </div> | |
| 126 | + | |
| 127 | + {/* Image */} | |
| 128 | + <div | |
| 129 | + ref={containerRef} | |
| 130 | + className="flex-1 overflow-hidden flex items-center justify-center cursor-grab active:cursor-grabbing" | |
| 131 | + onWheel={handleWheel} | |
| 132 | + onMouseDown={handleMouseDown} | |
| 133 | + onMouseMove={handleMouseMove} | |
| 134 | + onMouseUp={handleMouseUp} | |
| 135 | + onMouseLeave={handleMouseUp} | |
| 136 | + onTouchStart={handleTouchStart} | |
| 137 | + onTouchMove={handleTouchMove} | |
| 138 | + onTouchEnd={handleTouchEnd} | |
| 139 | + > | |
| 140 | + <img | |
| 141 | + src={`/api/files/${file.id}/preview`} | |
| 142 | + alt={file.name} | |
| 143 | + className="max-w-full max-h-full select-none" | |
| 144 | + style={{ | |
| 145 | + transform: `translate(${position.x}px, ${position.y}px) scale(${scale}) rotate(${rotation}deg)`, | |
| 146 | + transition: isDragging ? "none" : "transform 0.2s ease", | |
| 147 | + objectFit: "contain", | |
| 148 | + }} | |
| 149 | + draggable={false} | |
| 150 | + /> | |
| 151 | + </div> | |
| 152 | + </div> | |
| 153 | + ); | |
| 154 | +} | |
added
components/preview/plugins/MarkdownPreview.tsx
+53 −0
@@ -0,0 +1,53 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect } from "react"; | |
| 4 | +import type { PreviewPluginProps } from "./types"; | |
| 5 | +import dynamic from "next/dynamic"; | |
| 6 | + | |
| 7 | +const ReactMarkdown = dynamic(() => import("react-markdown"), { ssr: false }); | |
| 8 | + | |
| 9 | +export default function MarkdownPreview({ file }: PreviewPluginProps) { | |
| 10 | + const [content, setContent] = useState<string>(""); | |
| 11 | + const [loading, setLoading] = useState(true); | |
| 12 | + | |
| 13 | + useEffect(() => { | |
| 14 | + setLoading(true); | |
| 15 | + fetch(`/api/files/${file.id}/preview`) | |
| 16 | + .then((res) => res.text()) | |
| 17 | + .then((text) => { | |
| 18 | + setContent(text); | |
| 19 | + setLoading(false); | |
| 20 | + }) | |
| 21 | + .catch(() => setLoading(false)); | |
| 22 | + }, [file.id]); | |
| 23 | + | |
| 24 | + if (loading) { | |
| 25 | + return ( | |
| 26 | + <div className="flex items-center justify-center h-full"> | |
| 27 | + <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 28 | + </div> | |
| 29 | + ); | |
| 30 | + } | |
| 31 | + | |
| 32 | + return ( | |
| 33 | + <div className="overflow-auto h-full p-6"> | |
| 34 | + <article className="prose prose-invert prose-sm max-w-none | |
| 35 | + prose-headings:text-gray-200 prose-headings:border-b prose-headings:border-white/10 prose-headings:pb-2 | |
| 36 | + prose-p:text-gray-300 | |
| 37 | + prose-a:text-blue-400 | |
| 38 | + prose-strong:text-gray-200 | |
| 39 | + prose-code:text-green-400 prose-code:bg-white/10 prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-xs | |
| 40 | + prose-pre:bg-[#1a1a1a] prose-pre:border prose-pre:border-white/10 | |
| 41 | + prose-blockquote:border-gray-600 | |
| 42 | + prose-table:border-collapse | |
| 43 | + prose-th:border prose-th:border-white/10 prose-th:bg-white/5 prose-th:px-3 prose-th:py-2 | |
| 44 | + prose-td:border prose-td:border-white/10 prose-td:px-3 prose-td:py-2 | |
| 45 | + prose-li:text-gray-300 | |
| 46 | + prose-hr:border-white/10 | |
| 47 | + prose-img:rounded-lg | |
| 48 | + "> | |
| 49 | + <ReactMarkdown>{content}</ReactMarkdown> | |
| 50 | + </article> | |
| 51 | + </div> | |
| 52 | + ); | |
| 53 | +} | |
added
components/preview/plugins/PdfPreview.tsx
+200 −0
@@ -0,0 +1,200 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useEffect, useRef, useState, useCallback } from "react"; | |
| 4 | +import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut } from "lucide-react"; | |
| 5 | +import type { PreviewPluginProps } from "./types"; | |
| 6 | + | |
| 7 | +export default function PdfPreview({ file }: PreviewPluginProps) { | |
| 8 | + const canvasRef = useRef<HTMLCanvasElement>(null); | |
| 9 | + const containerRef = useRef<HTMLDivElement>(null); | |
| 10 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| 11 | + const pdfDocRef = useRef<any>(null); | |
| 12 | + const renderTaskRef = useRef<{ cancel: () => void } | null>(null); | |
| 13 | + const [pageNum, setPageNum] = useState(1); | |
| 14 | + const [totalPages, setTotalPages] = useState(0); | |
| 15 | + const [scale, setScale] = useState(1.2); | |
| 16 | + const [loading, setLoading] = useState(true); | |
| 17 | + const [error, setError] = useState(false); | |
| 18 | + | |
| 19 | + const renderPage = useCallback(async (num: number, currentScale: number) => { | |
| 20 | + const pdf = pdfDocRef.current; | |
| 21 | + if (!pdf || !canvasRef.current) return; | |
| 22 | + | |
| 23 | + try { | |
| 24 | + // Cancel any in-progress render | |
| 25 | + if (renderTaskRef.current) { | |
| 26 | + renderTaskRef.current.cancel(); | |
| 27 | + renderTaskRef.current = null; | |
| 28 | + } | |
| 29 | + | |
| 30 | + const page = await pdf.getPage(num); | |
| 31 | + const viewport = page.getViewport({ scale: currentScale }); | |
| 32 | + const canvas = canvasRef.current; | |
| 33 | + const context = canvas.getContext("2d"); | |
| 34 | + if (!context) return; | |
| 35 | + | |
| 36 | + canvas.height = viewport.height; | |
| 37 | + canvas.width = viewport.width; | |
| 38 | + | |
| 39 | + const renderTask = page.render({ canvasContext: context, viewport }); | |
| 40 | + renderTaskRef.current = renderTask; | |
| 41 | + await renderTask.promise; | |
| 42 | + renderTaskRef.current = null; | |
| 43 | + } catch (e: unknown) { | |
| 44 | + // Ignore cancelled render errors | |
| 45 | + if (e && typeof e === "object" && "name" in e && (e as { name: string }).name === "RenderingCancelledException") return; | |
| 46 | + } | |
| 47 | + }, []); | |
| 48 | + | |
| 49 | + useEffect(() => { | |
| 50 | + let cancelled = false; | |
| 51 | + | |
| 52 | + const loadPdf = async () => { | |
| 53 | + try { | |
| 54 | + const pdfjsLib = await import("pdfjs-dist"); | |
| 55 | + | |
| 56 | + // Use local worker file copied to /public | |
| 57 | + pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.mjs"; | |
| 58 | + | |
| 59 | + const loadingTask = pdfjsLib.getDocument({ | |
| 60 | + url: `/api/files/${file.id}/preview`, | |
| 61 | + cMapUrl: "https://cdn.jsdelivr.net/npm/pdfjs-dist@5.4.624/cmaps/", | |
| 62 | + cMapPacked: true, | |
| 63 | + }); | |
| 64 | + | |
| 65 | + const pdf = await loadingTask.promise; | |
| 66 | + if (cancelled) return; | |
| 67 | + | |
| 68 | + pdfDocRef.current = pdf; | |
| 69 | + setTotalPages(pdf.numPages); | |
| 70 | + setLoading(false); | |
| 71 | + | |
| 72 | + // Render first page after state is set | |
| 73 | + await renderPage(1, 1.2); | |
| 74 | + } catch (err) { | |
| 75 | + console.error("PDF load error:", err); | |
| 76 | + if (!cancelled) { | |
| 77 | + setError(true); | |
| 78 | + setLoading(false); | |
| 79 | + } | |
| 80 | + } | |
| 81 | + }; | |
| 82 | + | |
| 83 | + loadPdf(); | |
| 84 | + return () => { | |
| 85 | + cancelled = true; | |
| 86 | + if (renderTaskRef.current) { | |
| 87 | + renderTaskRef.current.cancel(); | |
| 88 | + } | |
| 89 | + }; | |
| 90 | + }, [file.id, renderPage]); | |
| 91 | + | |
| 92 | + // Re-render when page or scale changes (but not on initial load) | |
| 93 | + useEffect(() => { | |
| 94 | + if (!loading && pdfDocRef.current) { | |
| 95 | + renderPage(pageNum, scale); | |
| 96 | + } | |
| 97 | + }, [pageNum, scale, renderPage, loading]); | |
| 98 | + | |
| 99 | + const goToPage = useCallback((num: number) => { | |
| 100 | + setPageNum(Math.max(1, Math.min(num, totalPages))); | |
| 101 | + }, [totalPages]); | |
| 102 | + | |
| 103 | + useEffect(() => { | |
| 104 | + const handleKey = (e: KeyboardEvent) => { | |
| 105 | + if (e.target instanceof HTMLInputElement) return; | |
| 106 | + if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); goToPage(pageNum - 1); } | |
| 107 | + if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); goToPage(pageNum + 1); } | |
| 108 | + }; | |
| 109 | + window.addEventListener("keydown", handleKey); | |
| 110 | + return () => window.removeEventListener("keydown", handleKey); | |
| 111 | + }, [pageNum, goToPage]); | |
| 112 | + | |
| 113 | + if (error) { | |
| 114 | + // Fallback to browser's built-in PDF viewer via iframe | |
| 115 | + return ( | |
| 116 | + <div className="w-full h-full"> | |
| 117 | + <iframe | |
| 118 | + src={`/api/files/${file.id}/preview`} | |
| 119 | + className="w-full h-full border-0" | |
| 120 | + title={file.name} | |
| 121 | + /> | |
| 122 | + </div> | |
| 123 | + ); | |
| 124 | + } | |
| 125 | + | |
| 126 | + return ( | |
| 127 | + <div className="flex flex-col h-full"> | |
| 128 | + {/* Toolbar */} | |
| 129 | + <div className="flex items-center gap-2 px-3 py-2 border-b border-white/10 bg-white/5 flex-shrink-0"> | |
| 130 | + <button | |
| 131 | + onClick={() => goToPage(pageNum - 1)} | |
| 132 | + disabled={pageNum <= 1} | |
| 133 | + className="p-1.5 text-gray-400 hover:text-white disabled:opacity-30 transition-colors rounded hover:bg-white/10" | |
| 134 | + > | |
| 135 | + <ChevronLeft className="w-4 h-4" /> | |
| 136 | + </button> | |
| 137 | + <div className="flex items-center gap-1 text-xs text-gray-400"> | |
| 138 | + <input | |
| 139 | + type="number" | |
| 140 | + value={pageNum} | |
| 141 | + onChange={(e) => goToPage(parseInt(e.target.value) || 1)} | |
| 142 | + className="w-10 bg-white/10 border border-white/10 rounded px-1.5 py-0.5 text-center text-white text-xs" | |
| 143 | + min={1} | |
| 144 | + max={totalPages} | |
| 145 | + /> | |
| 146 | + <span>/ {totalPages}</span> | |
| 147 | + </div> | |
| 148 | + <button | |
| 149 | + onClick={() => goToPage(pageNum + 1)} | |
| 150 | + disabled={pageNum >= totalPages} | |
| 151 | + className="p-1.5 text-gray-400 hover:text-white disabled:opacity-30 transition-colors rounded hover:bg-white/10" | |
| 152 | + > | |
| 153 | + <ChevronRight className="w-4 h-4" /> | |
| 154 | + </button> | |
| 155 | + | |
| 156 | + <div className="w-px h-4 bg-white/10 mx-1" /> | |
| 157 | + | |
| 158 | + <button | |
| 159 | + onClick={() => setScale((s) => Math.min(s + 0.2, 3))} | |
| 160 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 161 | + title="Zoom in" | |
| 162 | + > | |
| 163 | + <ZoomIn className="w-4 h-4" /> | |
| 164 | + </button> | |
| 165 | + <span className="text-xs text-gray-500 min-w-[3rem] text-center"> | |
| 166 | + {Math.round(scale * 100)}% | |
| 167 | + </span> | |
| 168 | + <button | |
| 169 | + onClick={() => setScale((s) => Math.max(s - 0.2, 0.4))} | |
| 170 | + className="p-1.5 text-gray-400 hover:text-white transition-colors rounded hover:bg-white/10" | |
| 171 | + title="Zoom out" | |
| 172 | + > | |
| 173 | + <ZoomOut className="w-4 h-4" /> | |
| 174 | + </button> | |
| 175 | + | |
| 176 | + <button | |
| 177 | + onClick={() => setScale(1.2)} | |
| 178 | + className="text-[10px] text-gray-500 hover:text-white px-1.5 py-0.5 rounded hover:bg-white/10 transition-colors" | |
| 179 | + > | |
| 180 | + Reset | |
| 181 | + </button> | |
| 182 | + </div> | |
| 183 | + | |
| 184 | + {/* Canvas */} | |
| 185 | + <div ref={containerRef} className="flex-1 overflow-auto flex justify-center p-4 bg-gray-900/50"> | |
| 186 | + {loading ? ( | |
| 187 | + <div className="flex items-center justify-center h-full"> | |
| 188 | + <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" /> | |
| 189 | + </div> | |
| 190 | + ) : ( | |
| 191 | + <canvas | |
| 192 | + ref={canvasRef} | |
| 193 | + className="shadow-2xl max-w-full" | |
| 194 | + style={{ background: "white" }} | |
| 195 | + /> | |
| 196 | + )} | |
| 197 | + </div> | |
| 198 | + </div> | |
| 199 | + ); | |
| 200 | +} | |
added
components/preview/plugins/VideoPreview.tsx
+204 −0
@@ -0,0 +1,204 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useRef, useCallback, useEffect } from "react"; | |
| 4 | +import { | |
| 5 | + Play, Pause, Volume2, VolumeX, Maximize, | |
| 6 | + SkipBack, SkipForward, Gauge | |
| 7 | +} from "lucide-react"; | |
| 8 | +import type { PreviewPluginProps } from "./types"; | |
| 9 | + | |
| 10 | +export default function VideoPreview({ file }: PreviewPluginProps) { | |
| 11 | + const videoRef = useRef<HTMLVideoElement>(null); | |
| 12 | + const [isPlaying, setIsPlaying] = useState(false); | |
| 13 | + const [currentTime, setCurrentTime] = useState(0); | |
| 14 | + const [duration, setDuration] = useState(0); | |
| 15 | + const [volume, setVolume] = useState(1); | |
| 16 | + const [isMuted, setIsMuted] = useState(false); | |
| 17 | + const [playbackRate, setPlaybackRate] = useState(1); | |
| 18 | + const [showControls, setShowControls] = useState(true); | |
| 19 | + const hideTimeout = useRef<ReturnType<typeof setTimeout>>(); | |
| 20 | + | |
| 21 | + const togglePlay = useCallback(() => { | |
| 22 | + const video = videoRef.current; | |
| 23 | + if (!video) return; | |
| 24 | + if (video.paused) { | |
| 25 | + video.play(); | |
| 26 | + setIsPlaying(true); | |
| 27 | + } else { | |
| 28 | + video.pause(); | |
| 29 | + setIsPlaying(false); | |
| 30 | + } | |
| 31 | + }, []); | |
| 32 | + | |
| 33 | + const handleSeek = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { | |
| 34 | + const video = videoRef.current; | |
| 35 | + if (!video) return; | |
| 36 | + const time = parseFloat(e.target.value); | |
| 37 | + video.currentTime = time; | |
| 38 | + setCurrentTime(time); | |
| 39 | + }, []); | |
| 40 | + | |
| 41 | + const skip = useCallback((seconds: number) => { | |
| 42 | + const video = videoRef.current; | |
| 43 | + if (!video) return; | |
| 44 | + video.currentTime = Math.max(0, Math.min(video.currentTime + seconds, duration)); | |
| 45 | + }, [duration]); | |
| 46 | + | |
| 47 | + const toggleMute = useCallback(() => { | |
| 48 | + const video = videoRef.current; | |
| 49 | + if (!video) return; | |
| 50 | + video.muted = !video.muted; | |
| 51 | + setIsMuted(video.muted); | |
| 52 | + }, []); | |
| 53 | + | |
| 54 | + const changeVolume = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { | |
| 55 | + const video = videoRef.current; | |
| 56 | + if (!video) return; | |
| 57 | + const vol = parseFloat(e.target.value); | |
| 58 | + video.volume = vol; | |
| 59 | + setVolume(vol); | |
| 60 | + setIsMuted(vol === 0); | |
| 61 | + }, []); | |
| 62 | + | |
| 63 | + const cycleSpeed = useCallback(() => { | |
| 64 | + const speeds = [0.5, 1, 1.25, 1.5, 2]; | |
| 65 | + const idx = speeds.indexOf(playbackRate); | |
| 66 | + const next = speeds[(idx + 1) % speeds.length]; | |
| 67 | + setPlaybackRate(next); | |
| 68 | + if (videoRef.current) videoRef.current.playbackRate = next; | |
| 69 | + }, [playbackRate]); | |
| 70 | + | |
| 71 | + const toggleFullscreen = useCallback(() => { | |
| 72 | + const video = videoRef.current; | |
| 73 | + if (!video) return; | |
| 74 | + if (document.fullscreenElement) { | |
| 75 | + document.exitFullscreen(); | |
| 76 | + } else { | |
| 77 | + video.requestFullscreen(); | |
| 78 | + } | |
| 79 | + }, []); | |
| 80 | + | |
| 81 | + const handleMouseMove = useCallback(() => { | |
| 82 | + setShowControls(true); | |
| 83 | + if (hideTimeout.current) clearTimeout(hideTimeout.current); | |
| 84 | + hideTimeout.current = setTimeout(() => { | |
| 85 | + if (isPlaying) setShowControls(false); | |
| 86 | + }, 3000); | |
| 87 | + }, [isPlaying]); | |
| 88 | + | |
| 89 | + useEffect(() => { | |
| 90 | + const handleKey = (e: KeyboardEvent) => { | |
| 91 | + switch (e.key) { | |
| 92 | + case " ": e.preventDefault(); togglePlay(); break; | |
| 93 | + case "ArrowLeft": skip(-10); break; | |
| 94 | + case "ArrowRight": skip(10); break; | |
| 95 | + case "f": toggleFullscreen(); break; | |
| 96 | + case "m": toggleMute(); break; | |
| 97 | + } | |
| 98 | + }; | |
| 99 | + window.addEventListener("keydown", handleKey); | |
| 100 | + return () => window.removeEventListener("keydown", handleKey); | |
| 101 | + }, [togglePlay, skip, toggleFullscreen, toggleMute]); | |
| 102 | + | |
| 103 | + const formatTime = (t: number) => { | |
| 104 | + const m = Math.floor(t / 60); | |
| 105 | + const s = Math.floor(t % 60); | |
| 106 | + return `${m}:${s.toString().padStart(2, "0")}`; | |
| 107 | + }; | |
| 108 | + | |
| 109 | + return ( | |
| 110 | + <div | |
| 111 | + className="flex flex-col h-full bg-black relative" | |
| 112 | + onMouseMove={handleMouseMove} | |
| 113 | + > | |
| 114 | + <div className="flex-1 flex items-center justify-center relative"> | |
| 115 | + <video | |
| 116 | + ref={videoRef} | |
| 117 | + src={`/api/files/${file.id}/stream`} | |
| 118 | + className="max-w-full max-h-full" | |
| 119 | + onClick={togglePlay} | |
| 120 | + onTimeUpdate={() => setCurrentTime(videoRef.current?.currentTime || 0)} | |
| 121 | + onLoadedMetadata={() => setDuration(videoRef.current?.duration || 0)} | |
| 122 | + onEnded={() => setIsPlaying(false)} | |
| 123 | + preload="metadata" | |
| 124 | + /> | |
| 125 | + | |
| 126 | + {/* Play overlay */} | |
| 127 | + {!isPlaying && ( | |
| 128 | + <button | |
| 129 | + onClick={togglePlay} | |
| 130 | + className="absolute inset-0 flex items-center justify-center bg-black/30" | |
| 131 | + > | |
| 132 | + <div className="w-16 h-16 rounded-full bg-white/20 backdrop-blur-sm flex items-center justify-center"> | |
| 133 | + <Play className="w-8 h-8 text-white ml-1" /> | |
| 134 | + </div> | |
| 135 | + </button> | |
| 136 | + )} | |
| 137 | + </div> | |
| 138 | + | |
| 139 | + {/* Controls */} | |
| 140 | + <div | |
| 141 | + className={`px-4 py-3 bg-gradient-to-t from-black/90 to-transparent transition-opacity ${ | |
| 142 | + showControls ? "opacity-100" : "opacity-0" | |
| 143 | + }`} | |
| 144 | + > | |
| 145 | + {/* Seek bar */} | |
| 146 | + <input | |
| 147 | + type="range" | |
| 148 | + min={0} | |
| 149 | + max={duration || 0} | |
| 150 | + step={0.1} | |
| 151 | + value={currentTime} | |
| 152 | + onChange={handleSeek} | |
| 153 | + className="w-full h-1 mb-2 appearance-none bg-white/20 rounded-full cursor-pointer | |
| 154 | + [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 | |
| 155 | + [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white" | |
| 156 | + /> | |
| 157 | + | |
| 158 | + <div className="flex items-center gap-3"> | |
| 159 | + <button onClick={() => skip(-10)} className="text-white/70 hover:text-white"> | |
| 160 | + <SkipBack className="w-4 h-4" /> | |
| 161 | + </button> | |
| 162 | + <button onClick={togglePlay} className="text-white"> | |
| 163 | + {isPlaying ? <Pause className="w-5 h-5" /> : <Play className="w-5 h-5" />} | |
| 164 | + </button> | |
| 165 | + <button onClick={() => skip(10)} className="text-white/70 hover:text-white"> | |
| 166 | + <SkipForward className="w-4 h-4" /> | |
| 167 | + </button> | |
| 168 | + | |
| 169 | + <span className="text-xs text-white/60 min-w-[5rem]"> | |
| 170 | + {formatTime(currentTime)} / {formatTime(duration)} | |
| 171 | + </span> | |
| 172 | + | |
| 173 | + <div className="flex-1" /> | |
| 174 | + | |
| 175 | + <button onClick={cycleSpeed} className="text-xs text-white/70 hover:text-white flex items-center gap-1"> | |
| 176 | + <Gauge className="w-3 h-3" /> | |
| 177 | + {playbackRate}x | |
| 178 | + </button> | |
| 179 | + | |
| 180 | + <div className="flex items-center gap-1"> | |
| 181 | + <button onClick={toggleMute} className="text-white/70 hover:text-white"> | |
| 182 | + {isMuted || volume === 0 ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />} | |
| 183 | + </button> | |
| 184 | + <input | |
| 185 | + type="range" | |
| 186 | + min={0} | |
| 187 | + max={1} | |
| 188 | + step={0.05} | |
| 189 | + value={isMuted ? 0 : volume} | |
| 190 | + onChange={changeVolume} | |
| 191 | + className="w-16 h-1 appearance-none bg-white/20 rounded-full cursor-pointer | |
| 192 | + [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5 | |
| 193 | + [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white" | |
| 194 | + /> | |
| 195 | + </div> | |
| 196 | + | |
| 197 | + <button onClick={toggleFullscreen} className="text-white/70 hover:text-white"> | |
| 198 | + <Maximize className="w-4 h-4" /> | |
| 199 | + </button> | |
| 200 | + </div> | |
| 201 | + </div> | |
| 202 | + </div> | |
| 203 | + ); | |
| 204 | +} | |
added
components/preview/plugins/index.ts
+99 −0
@@ -0,0 +1,99 @@ | ||
| 1 | +/* eslint-disable @typescript-eslint/no-explicit-any */ | |
| 2 | +import dynamic from "next/dynamic"; | |
| 3 | +import type { PreviewPlugin } from "./types"; | |
| 4 | +import { isArchiveMime, isCodeMime, isTextMime } from "@/lib/utils"; | |
| 5 | + | |
| 6 | +const ImagePreview = dynamic(() => import("./ImagePreview"), { ssr: false }); | |
| 7 | +const VideoPreview = dynamic(() => import("./VideoPreview"), { ssr: false }); | |
| 8 | +const AudioPreview = dynamic(() => import("./AudioPreview"), { ssr: false }); | |
| 9 | +const PdfPreview = dynamic(() => import("./PdfPreview"), { ssr: false }); | |
| 10 | +const CodePreview = dynamic(() => import("./CodePreview"), { ssr: false }); | |
| 11 | +const CsvJsonPreview = dynamic(() => import("./CsvJsonPreview"), { ssr: false }); | |
| 12 | +const MarkdownPreview = dynamic(() => import("./MarkdownPreview"), { ssr: false }); | |
| 13 | +const ArchivePreview = dynamic(() => import("./ArchivePreview"), { ssr: false }); | |
| 14 | +const FallbackPreview = dynamic(() => import("./FallbackPreview"), { ssr: false }); | |
| 15 | + | |
| 16 | +const plugins: PreviewPlugin[] = [ | |
| 17 | + { | |
| 18 | + id: "image", | |
| 19 | + name: "Image", | |
| 20 | + canHandle: (mime) => mime.startsWith("image/"), | |
| 21 | + priority: 10, | |
| 22 | + component: ImagePreview as any, | |
| 23 | + }, | |
| 24 | + { | |
| 25 | + id: "video", | |
| 26 | + name: "Video", | |
| 27 | + canHandle: (mime) => mime.startsWith("video/"), | |
| 28 | + priority: 10, | |
| 29 | + component: VideoPreview as any, | |
| 30 | + }, | |
| 31 | + { | |
| 32 | + id: "audio", | |
| 33 | + name: "Audio", | |
| 34 | + canHandle: (mime) => mime.startsWith("audio/"), | |
| 35 | + priority: 10, | |
| 36 | + component: AudioPreview as any, | |
| 37 | + }, | |
| 38 | + { | |
| 39 | + id: "pdf", | |
| 40 | + name: "PDF", | |
| 41 | + canHandle: (mime) => mime === "application/pdf", | |
| 42 | + priority: 10, | |
| 43 | + component: PdfPreview as any, | |
| 44 | + }, | |
| 45 | + { | |
| 46 | + id: "csv-json", | |
| 47 | + name: "CSV/JSON", | |
| 48 | + canHandle: (mime) => mime === "text/csv" || mime.includes("json"), | |
| 49 | + priority: 15, | |
| 50 | + component: CsvJsonPreview as any, | |
| 51 | + }, | |
| 52 | + { | |
| 53 | + id: "markdown", | |
| 54 | + name: "Markdown", | |
| 55 | + canHandle: (mime) => mime.includes("markdown") || mime === "text/x-markdown", | |
| 56 | + priority: 15, | |
| 57 | + component: MarkdownPreview as any, | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + id: "code", | |
| 61 | + name: "Code", | |
| 62 | + canHandle: (mime) => isCodeMime(mime), | |
| 63 | + priority: 10, | |
| 64 | + component: CodePreview as any, | |
| 65 | + supportsEdit: true, | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + id: "text", | |
| 69 | + name: "Texte", | |
| 70 | + canHandle: (mime) => isTextMime(mime), | |
| 71 | + priority: 5, | |
| 72 | + component: CodePreview as any, | |
| 73 | + supportsEdit: true, | |
| 74 | + }, | |
| 75 | + { | |
| 76 | + id: "archive", | |
| 77 | + name: "Archive", | |
| 78 | + canHandle: (mime) => isArchiveMime(mime), | |
| 79 | + priority: 10, | |
| 80 | + component: ArchivePreview as any, | |
| 81 | + }, | |
| 82 | + { | |
| 83 | + id: "fallback", | |
| 84 | + name: "Télécharger", | |
| 85 | + canHandle: () => true, | |
| 86 | + priority: 0, | |
| 87 | + component: FallbackPreview as any, | |
| 88 | + }, | |
| 89 | +]; | |
| 90 | + | |
| 91 | +export function getPlugin(mimeType: string): PreviewPlugin { | |
| 92 | + return plugins | |
| 93 | + .filter((p) => p.canHandle(mimeType)) | |
| 94 | + .sort((a, b) => b.priority - a.priority)[0]; | |
| 95 | +} | |
| 96 | + | |
| 97 | +export function getAllPlugins(): PreviewPlugin[] { | |
| 98 | + return plugins; | |
| 99 | +} | |
added
components/preview/plugins/types.ts
+17 −0
@@ -0,0 +1,17 @@ | ||
| 1 | +import { FileData } from "@/types/files"; | |
| 2 | + | |
| 3 | +export interface PreviewPluginProps { | |
| 4 | + file: FileData; | |
| 5 | + isEditing: boolean; | |
| 6 | + onSave?: (data: Blob | string, asNewVersion?: boolean) => Promise<void>; | |
| 7 | + fullscreen: boolean; | |
| 8 | +} | |
| 9 | + | |
| 10 | +export interface PreviewPlugin { | |
| 11 | + id: string; | |
| 12 | + name: string; | |
| 13 | + canHandle: (mimeType: string) => boolean; | |
| 14 | + priority: number; | |
| 15 | + component: React.ComponentType<PreviewPluginProps>; | |
| 16 | + supportsEdit?: boolean; | |
| 17 | +} | |
added
components/tags/TagBadge.tsx
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { X } from "lucide-react"; | |
| 4 | + | |
| 5 | +interface TagBadgeProps { | |
| 6 | + name: string; | |
| 7 | + color: string; | |
| 8 | + onRemove?: () => void; | |
| 9 | + size?: "sm" | "md"; | |
| 10 | +} | |
| 11 | + | |
| 12 | +export default function TagBadge({ name, color, onRemove, size = "sm" }: TagBadgeProps) { | |
| 13 | + return ( | |
| 14 | + <span | |
| 15 | + className={`inline-flex items-center gap-1 rounded-full border border-white/10 ${ | |
| 16 | + size === "sm" ? "px-2 py-0.5 text-[10px]" : "px-2.5 py-1 text-xs" | |
| 17 | + }`} | |
| 18 | + style={{ backgroundColor: `${color}20`, color }} | |
| 19 | + > | |
| 20 | + <span | |
| 21 | + className={`rounded-full ${size === "sm" ? "w-1.5 h-1.5" : "w-2 h-2"}`} | |
| 22 | + style={{ backgroundColor: color }} | |
| 23 | + /> | |
| 24 | + {name} | |
| 25 | + {onRemove && ( | |
| 26 | + <button | |
| 27 | + onClick={(e) => { | |
| 28 | + e.stopPropagation(); | |
| 29 | + onRemove(); | |
| 30 | + }} | |
| 31 | + className="hover:opacity-70 transition-opacity" | |
| 32 | + > | |
| 33 | + <X className={size === "sm" ? "w-2.5 h-2.5" : "w-3 h-3"} /> | |
| 34 | + </button> | |
| 35 | + )} | |
| 36 | + </span> | |
| 37 | + ); | |
| 38 | +} | |
added
components/tags/TagSelector.tsx
+155 −0
@@ -0,0 +1,155 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { useState, useEffect, useRef } from "react"; | |
| 4 | +import { Plus } from "lucide-react"; | |
| 5 | +import TagBadge from "./TagBadge"; | |
| 6 | + | |
| 7 | +interface TagData { | |
| 8 | + id: string; | |
| 9 | + name: string; | |
| 10 | + color: string; | |
| 11 | +} | |
| 12 | + | |
| 13 | +interface TagSelectorProps { | |
| 14 | + fileId: string; | |
| 15 | + currentTags: { id: string; tag: TagData }[]; | |
| 16 | + onTagsChange: () => void; | |
| 17 | +} | |
| 18 | + | |
| 19 | +const TAG_COLORS = [ | |
| 20 | + "#ef4444", "#f97316", "#eab308", "#22c55e", | |
| 21 | + "#06b6d4", "#3b82f6", "#8b5cf6", "#ec4899", | |
| 22 | +]; | |
| 23 | + | |
| 24 | +export default function TagSelector({ fileId, currentTags, onTagsChange }: TagSelectorProps) { | |
| 25 | + const [allTags, setAllTags] = useState<TagData[]>([]); | |
| 26 | + const [isOpen, setIsOpen] = useState(false); | |
| 27 | + const [newTagName, setNewTagName] = useState(""); | |
| 28 | + const [newTagColor, setNewTagColor] = useState(TAG_COLORS[0]); | |
| 29 | + const ref = useRef<HTMLDivElement>(null); | |
| 30 | + | |
| 31 | + useEffect(() => { | |
| 32 | + fetch("/api/tags") | |
| 33 | + .then((res) => res.json()) | |
| 34 | + .then(setAllTags) | |
| 35 | + .catch(() => {}); | |
| 36 | + }, []); | |
| 37 | + | |
| 38 | + useEffect(() => { | |
| 39 | + const handleClick = (e: MouseEvent) => { | |
| 40 | + if (ref.current && !ref.current.contains(e.target as Node)) { | |
| 41 | + setIsOpen(false); | |
| 42 | + } | |
| 43 | + }; | |
| 44 | + document.addEventListener("mousedown", handleClick); | |
| 45 | + return () => document.removeEventListener("mousedown", handleClick); | |
| 46 | + }, []); | |
| 47 | + | |
| 48 | + const currentTagIds = currentTags.map((t) => t.tag.id); | |
| 49 | + const availableTags = allTags.filter((t) => !currentTagIds.includes(t.id)); | |
| 50 | + | |
| 51 | + const addTag = async (tagId: string) => { | |
| 52 | + await fetch(`/api/files/${fileId}/tags`, { | |
| 53 | + method: "POST", | |
| 54 | + headers: { "Content-Type": "application/json" }, | |
| 55 | + body: JSON.stringify({ tagId }), | |
| 56 | + }); | |
| 57 | + onTagsChange(); | |
| 58 | + }; | |
| 59 | + | |
| 60 | + const removeTag = async (tagId: string) => { | |
| 61 | + await fetch(`/api/files/${fileId}/tags/${tagId}`, { method: "DELETE" }); | |
| 62 | + onTagsChange(); | |
| 63 | + }; | |
| 64 | + | |
| 65 | + const createAndAddTag = async () => { | |
| 66 | + if (!newTagName.trim()) return; | |
| 67 | + const res = await fetch("/api/tags", { | |
| 68 | + method: "POST", | |
| 69 | + headers: { "Content-Type": "application/json" }, | |
| 70 | + body: JSON.stringify({ name: newTagName.trim(), color: newTagColor }), | |
| 71 | + }); | |
| 72 | + const tag = await res.json(); | |
| 73 | + setAllTags((prev) => [...prev, tag]); | |
| 74 | + await addTag(tag.id); | |
| 75 | + setNewTagName(""); | |
| 76 | + setNewTagColor(TAG_COLORS[Math.floor(Math.random() * TAG_COLORS.length)]); | |
| 77 | + }; | |
| 78 | + | |
| 79 | + return ( | |
| 80 | + <div ref={ref} className="relative"> | |
| 81 | + <div className="flex flex-wrap gap-1.5 items-center"> | |
| 82 | + {currentTags.map((ft) => ( | |
| 83 | + <TagBadge | |
| 84 | + key={ft.id} | |
| 85 | + name={ft.tag.name} | |
| 86 | + color={ft.tag.color} | |
| 87 | + onRemove={() => removeTag(ft.tag.id)} | |
| 88 | + size="md" | |
| 89 | + /> | |
| 90 | + ))} | |
| 91 | + <button | |
| 92 | + onClick={() => setIsOpen(!isOpen)} | |
| 93 | + className="inline-flex items-center gap-1 px-2 py-1 text-xs text-gray-500 hover:text-white border border-dashed border-white/20 hover:border-white/40 rounded-full transition-colors" | |
| 94 | + > | |
| 95 | + <Plus className="w-3 h-3" /> | |
| 96 | + Tag | |
| 97 | + </button> | |
| 98 | + </div> | |
| 99 | + | |
| 100 | + {isOpen && ( | |
| 101 | + <div className="absolute top-full left-0 mt-2 w-56 bg-[#1a1a1a] border border-white/10 rounded-lg shadow-xl z-50 p-2"> | |
| 102 | + {availableTags.length > 0 && ( | |
| 103 | + <div className="mb-2"> | |
| 104 | + <p className="text-[10px] text-gray-500 uppercase tracking-wider px-2 mb-1">Tags existants</p> | |
| 105 | + {availableTags.map((tag) => ( | |
| 106 | + <button | |
| 107 | + key={tag.id} | |
| 108 | + onClick={() => addTag(tag.id)} | |
| 109 | + className="flex items-center gap-2 w-full px-2 py-1.5 text-xs text-gray-300 hover:bg-white/10 rounded transition-colors" | |
| 110 | + > | |
| 111 | + <span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: tag.color }} /> | |
| 112 | + {tag.name} | |
| 113 | + </button> | |
| 114 | + ))} | |
| 115 | + </div> | |
| 116 | + )} | |
| 117 | + | |
| 118 | + <div className="border-t border-white/10 pt-2"> | |
| 119 | + <p className="text-[10px] text-gray-500 uppercase tracking-wider px-2 mb-1">Nouveau tag</p> | |
| 120 | + <div className="flex items-center gap-1 px-1"> | |
| 121 | + <input | |
| 122 | + type="text" | |
| 123 | + value={newTagName} | |
| 124 | + onChange={(e) => setNewTagName(e.target.value)} | |
| 125 | + onKeyDown={(e) => e.key === "Enter" && createAndAddTag()} | |
| 126 | + placeholder="Nom..." | |
| 127 | + className="flex-1 bg-white/5 border border-white/10 rounded px-2 py-1 text-xs text-white placeholder:text-gray-600" | |
| 128 | + autoFocus | |
| 129 | + /> | |
| 130 | + <div className="flex gap-0.5"> | |
| 131 | + {TAG_COLORS.slice(0, 4).map((c) => ( | |
| 132 | + <button | |
| 133 | + key={c} | |
| 134 | + onClick={() => setNewTagColor(c)} | |
| 135 | + className={`w-4 h-4 rounded-full border-2 ${ | |
| 136 | + newTagColor === c ? "border-white" : "border-transparent" | |
| 137 | + }`} | |
| 138 | + style={{ backgroundColor: c }} | |
| 139 | + /> | |
| 140 | + ))} | |
| 141 | + </div> | |
| 142 | + <button | |
| 143 | + onClick={createAndAddTag} | |
| 144 | + disabled={!newTagName.trim()} | |
| 145 | + className="p-1 text-gray-400 hover:text-white disabled:opacity-30" | |
| 146 | + > | |
| 147 | + <Plus className="w-3.5 h-3.5" /> | |
| 148 | + </button> | |
| 149 | + </div> | |
| 150 | + </div> | |
| 151 | + </div> | |
| 152 | + )} | |
| 153 | + </div> | |
| 154 | + ); | |
| 155 | +} | |
added
components/theme/DynamicToaster.tsx
+28 −0
@@ -0,0 +1,28 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { Toaster } from "react-hot-toast"; | |
| 4 | +import { useTheme } from "./ThemeProvider"; | |
| 5 | + | |
| 6 | +export function DynamicToaster() { | |
| 7 | + const { theme } = useTheme(); | |
| 8 | + | |
| 9 | + return ( | |
| 10 | + <Toaster | |
| 11 | + position="bottom-right" | |
| 12 | + toastOptions={{ | |
| 13 | + style: { | |
| 14 | + background: theme === "dark" ? "#1f2937" : "#ffffff", | |
| 15 | + color: theme === "dark" ? "#e5e7eb" : "#111827", | |
| 16 | + border: theme === "dark" | |
| 17 | + ? "1px solid rgba(255,255,255,0.1)" | |
| 18 | + : "1px solid rgba(0,0,0,0.1)", | |
| 19 | + borderRadius: "12px", | |
| 20 | + fontSize: "14px", | |
| 21 | + boxShadow: theme === "dark" | |
| 22 | + ? "0 4px 12px rgba(0,0,0,0.5)" | |
| 23 | + : "0 4px 12px rgba(0,0,0,0.1)", | |
| 24 | + }, | |
| 25 | + }} | |
| 26 | + /> | |
| 27 | + ); | |
| 28 | +} | |
added
components/theme/ThemeProvider.tsx
+60 −0
@@ -0,0 +1,60 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { createContext, useContext, useEffect, useState } from "react"; | |
| 4 | + | |
| 5 | +type Theme = "dark" | "light"; | |
| 6 | + | |
| 7 | +interface ThemeContextType { | |
| 8 | + theme: Theme; | |
| 9 | + toggleTheme: () => void; | |
| 10 | +} | |
| 11 | + | |
| 12 | +const ThemeContext = createContext<ThemeContextType>({ | |
| 13 | + theme: "dark", | |
| 14 | + toggleTheme: () => {}, | |
| 15 | +}); | |
| 16 | + | |
| 17 | +export function useTheme() { | |
| 18 | + return useContext(ThemeContext); | |
| 19 | +} | |
| 20 | + | |
| 21 | +export default function ThemeProvider({ children }: { children: React.ReactNode }) { | |
| 22 | + const [theme, setTheme] = useState<Theme>("dark"); | |
| 23 | + const [mounted, setMounted] = useState(false); | |
| 24 | + | |
| 25 | + useEffect(() => { | |
| 26 | + const stored = localStorage.getItem("spb-theme") as Theme | null; | |
| 27 | + if (stored === "light" || stored === "dark") { | |
| 28 | + setTheme(stored); | |
| 29 | + } | |
| 30 | + setMounted(true); | |
| 31 | + }, []); | |
| 32 | + | |
| 33 | + useEffect(() => { | |
| 34 | + if (!mounted) return; | |
| 35 | + const root = document.documentElement; | |
| 36 | + if (theme === "dark") { | |
| 37 | + root.classList.add("dark"); | |
| 38 | + root.classList.remove("light"); | |
| 39 | + } else { | |
| 40 | + root.classList.remove("dark"); | |
| 41 | + root.classList.add("light"); | |
| 42 | + } | |
| 43 | + localStorage.setItem("spb-theme", theme); | |
| 44 | + }, [theme, mounted]); | |
| 45 | + | |
| 46 | + const toggleTheme = () => { | |
| 47 | + setTheme((prev) => (prev === "dark" ? "light" : "dark")); | |
| 48 | + }; | |
| 49 | + | |
| 50 | + // Prevent flash of wrong theme | |
| 51 | + if (!mounted) { | |
| 52 | + return <>{children}</>; | |
| 53 | + } | |
| 54 | + | |
| 55 | + return ( | |
| 56 | + <ThemeContext.Provider value={{ theme, toggleTheme }}> | |
| 57 | + {children} | |
| 58 | + </ThemeContext.Provider> | |
| 59 | + ); | |
| 60 | +} | |
added
components/theme/ThemeToggle.tsx
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion } from "framer-motion"; | |
| 4 | +import { Sun, Moon } from "lucide-react"; | |
| 5 | +import { useTheme } from "./ThemeProvider"; | |
| 6 | + | |
| 7 | +interface ThemeToggleProps { | |
| 8 | + className?: string; | |
| 9 | +} | |
| 10 | + | |
| 11 | +export default function ThemeToggle({ className = "" }: ThemeToggleProps) { | |
| 12 | + const { theme, toggleTheme } = useTheme(); | |
| 13 | + | |
| 14 | + return ( | |
| 15 | + <motion.button | |
| 16 | + whileTap={{ scale: 0.9 }} | |
| 17 | + onClick={toggleTheme} | |
| 18 | + className={`relative flex items-center justify-center min-w-[44px] min-h-[44px] w-10 h-10 rounded-xl transition-colors | |
| 19 | + dark:bg-white/10 dark:hover:bg-white/20 dark:active:bg-white/25 dark:text-yellow-300 | |
| 20 | + bg-gray-100 hover:bg-gray-200 active:bg-gray-300 text-gray-600 | |
| 21 | + ${className}`} | |
| 22 | + title={theme === "dark" ? "Mode clair" : "Mode sombre"} | |
| 23 | + > | |
| 24 | + <motion.div | |
| 25 | + key={theme} | |
| 26 | + initial={{ rotate: -90, opacity: 0 }} | |
| 27 | + animate={{ rotate: 0, opacity: 1 }} | |
| 28 | + transition={{ duration: 0.2 }} | |
| 29 | + > | |
| 30 | + {theme === "dark" ? ( | |
| 31 | + <Sun className="w-5 h-5" /> | |
| 32 | + ) : ( | |
| 33 | + <Moon className="w-5 h-5" /> | |
| 34 | + )} | |
| 35 | + </motion.div> | |
| 36 | + </motion.button> | |
| 37 | + ); | |
| 38 | +} | |
added
components/ui/Button.tsx
+47 −0
@@ -0,0 +1,47 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion } from "framer-motion"; | |
| 4 | +import { Loader2 } from "lucide-react"; | |
| 5 | +import { ButtonHTMLAttributes } from "react"; | |
| 6 | + | |
| 7 | +interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { | |
| 8 | + variant?: "primary" | "secondary" | "danger" | "ghost"; | |
| 9 | + size?: "sm" | "md" | "lg"; | |
| 10 | + loading?: boolean; | |
| 11 | +} | |
| 12 | + | |
| 13 | +const variants = { | |
| 14 | + primary: "bg-gray-900 text-white hover:bg-gray-800 active:bg-gray-700 dark:bg-white dark:text-black dark:hover:bg-gray-200 dark:active:bg-gray-300", | |
| 15 | + secondary: "bg-gray-100 text-gray-700 hover:bg-gray-200 active:bg-gray-300 border border-gray-200 dark:bg-white/10 dark:text-white dark:hover:bg-white/20 dark:active:bg-white/25 dark:border-white/10", | |
| 16 | + danger: "bg-red-50 text-red-600 hover:bg-red-100 active:bg-red-200 border border-red-200 dark:bg-red-500/10 dark:text-red-400 dark:hover:bg-red-500/20 dark:active:bg-red-500/30 dark:border-red-500/20", | |
| 17 | + ghost: "text-gray-500 hover:text-gray-900 hover:bg-gray-100 active:bg-gray-200 dark:text-gray-400 dark:hover:text-white dark:hover:bg-white/5 dark:active:bg-white/10", | |
| 18 | +}; | |
| 19 | + | |
| 20 | +// Mobile-optimized: minimum 44px touch targets | |
| 21 | +const sizes = { | |
| 22 | + sm: "px-3 py-2 text-xs min-h-[36px] sm:min-h-[32px]", | |
| 23 | + md: "px-4 py-2.5 text-sm min-h-[44px] sm:min-h-[40px]", | |
| 24 | + lg: "px-6 py-3.5 text-base min-h-[48px]", | |
| 25 | +}; | |
| 26 | + | |
| 27 | +export default function Button({ | |
| 28 | + variant = "primary", | |
| 29 | + size = "md", | |
| 30 | + loading, | |
| 31 | + children, | |
| 32 | + className = "", | |
| 33 | + disabled, | |
| 34 | + ...props | |
| 35 | +}: ButtonProps) { | |
| 36 | + return ( | |
| 37 | + <motion.button | |
| 38 | + whileTap={{ scale: 0.97 }} | |
| 39 | + className={`inline-flex items-center justify-center gap-2 rounded-xl font-medium transition-all disabled:opacity-50 disabled:cursor-not-allowed select-none ${variants[variant]} ${sizes[size]} ${className}`} | |
| 40 | + disabled={disabled || loading} | |
| 41 | + {...(props as Record<string, unknown>)} | |
| 42 | + > | |
| 43 | + {loading && <Loader2 className="w-4 h-4 animate-spin" />} | |
| 44 | + {children} | |
| 45 | + </motion.button> | |
| 46 | + ); | |
| 47 | +} | |
added
components/ui/Modal.tsx
+79 −0
@@ -0,0 +1,79 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { motion, AnimatePresence } from "framer-motion"; | |
| 4 | +import { X } from "lucide-react"; | |
| 5 | +import { useEffect } from "react"; | |
| 6 | + | |
| 7 | +interface ModalProps { | |
| 8 | + isOpen: boolean; | |
| 9 | + onClose: () => void; | |
| 10 | + title?: string; | |
| 11 | + children: React.ReactNode; | |
| 12 | + maxWidth?: string; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export default function Modal({ | |
| 16 | + isOpen, | |
| 17 | + onClose, | |
| 18 | + title, | |
| 19 | + children, | |
| 20 | + maxWidth = "max-w-md", | |
| 21 | +}: ModalProps) { | |
| 22 | + useEffect(() => { | |
| 23 | + if (isOpen) { | |
| 24 | + document.body.style.overflow = "hidden"; | |
| 25 | + } else { | |
| 26 | + document.body.style.overflow = ""; | |
| 27 | + } | |
| 28 | + return () => { | |
| 29 | + document.body.style.overflow = ""; | |
| 30 | + }; | |
| 31 | + }, [isOpen]); | |
| 32 | + | |
| 33 | + return ( | |
| 34 | + <AnimatePresence> | |
| 35 | + {isOpen && ( | |
| 36 | + <div | |
| 37 | + className="fixed inset-0 z-50 flex items-end sm:items-center justify-center sm:p-4" | |
| 38 | + onMouseDown={(e) => e.stopPropagation()} | |
| 39 | + onClick={(e) => e.stopPropagation()} | |
| 40 | + onPointerDown={(e) => e.stopPropagation()} | |
| 41 | + > | |
| 42 | + <motion.div | |
| 43 | + initial={{ opacity: 0 }} | |
| 44 | + animate={{ opacity: 1 }} | |
| 45 | + exit={{ opacity: 0 }} | |
| 46 | + className="absolute inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm" | |
| 47 | + onClick={onClose} | |
| 48 | + /> | |
| 49 | + {/* Bottom sheet on mobile, centered card on desktop */} | |
| 50 | + <motion.div | |
| 51 | + initial={{ opacity: 0, y: 100 }} | |
| 52 | + animate={{ opacity: 1, y: 0 }} | |
| 53 | + exit={{ opacity: 0, y: 100 }} | |
| 54 | + transition={{ type: "spring", damping: 25, stiffness: 300 }} | |
| 55 | + className={`relative w-full ${maxWidth} bg-white dark:bg-gray-900 border border-gray-200 dark:border-white/10 rounded-t-2xl sm:rounded-2xl shadow-2xl overflow-hidden transition-colors max-h-[90vh] flex flex-col`} | |
| 56 | + > | |
| 57 | + {/* Drag handle on mobile */} | |
| 58 | + <div className="sm:hidden flex justify-center pt-2 pb-1"> | |
| 59 | + <div className="w-10 h-1 rounded-full bg-gray-300 dark:bg-gray-600" /> | |
| 60 | + </div> | |
| 61 | + | |
| 62 | + {title && ( | |
| 63 | + <div className="flex items-center justify-between px-4 sm:px-6 py-3 sm:py-4 border-b border-gray-200 dark:border-white/10"> | |
| 64 | + <h2 className="text-base sm:text-lg font-semibold text-gray-900 dark:text-white">{title}</h2> | |
| 65 | + <button | |
| 66 | + onClick={onClose} | |
| 67 | + className="text-gray-400 hover:text-gray-700 dark:hover:text-white transition-colors p-2 -mr-1 min-w-[44px] min-h-[44px] flex items-center justify-center rounded-xl active:bg-gray-100 dark:active:bg-white/10" | |
| 68 | + > | |
| 69 | + <X className="w-5 h-5" /> | |
| 70 | + </button> | |
| 71 | + </div> | |
| 72 | + )} | |
| 73 | + <div className="p-4 sm:p-6 overflow-auto flex-1">{children}</div> | |
| 74 | + </motion.div> | |
| 75 | + </div> | |
| 76 | + )} | |
| 77 | + </AnimatePresence> | |
| 78 | + ); | |
| 79 | +} | |
added
components/ui/SearchBar.tsx
+50 −0
@@ -0,0 +1,50 @@ | ||
| 1 | +"use client"; | |
| 2 | + | |
| 3 | +import { Search, X } from "lucide-react"; | |
| 4 | +import { useState, useCallback } from "react"; | |
| 5 | + | |
| 6 | +interface SearchBarProps { | |
| 7 | + onSearch: (query: string) => void; | |
| 8 | + placeholder?: string; | |
| 9 | +} | |
| 10 | + | |
| 11 | +export default function SearchBar({ | |
| 12 | + onSearch, | |
| 13 | + placeholder = "Rechercher...", | |
| 14 | +}: SearchBarProps) { | |
| 15 | + const [query, setQuery] = useState(""); | |
| 16 | + | |
| 17 | + const handleChange = useCallback( | |
| 18 | + (value: string) => { | |
| 19 | + setQuery(value); | |
| 20 | + onSearch(value); | |
| 21 | + }, | |
| 22 | + [onSearch] | |
| 23 | + ); | |
| 24 | + | |
| 25 | + const handleClear = () => { | |
| 26 | + setQuery(""); | |
| 27 | + onSearch(""); | |
| 28 | + }; | |
| 29 | + | |
| 30 | + return ( | |
| 31 | + <div className="relative"> | |
| 32 | + <Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 dark:text-gray-500 pointer-events-none" /> | |
| 33 | + <input | |
| 34 | + type="text" | |
| 35 | + value={query} | |
| 36 | + onChange={(e) => handleChange(e.target.value)} | |
| 37 | + placeholder={placeholder} | |
| 38 | + className="w-full bg-gray-100 dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-xl pl-10 pr-10 py-3 sm:py-2.5 text-sm text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500/50 dark:focus:border-white/20 transition-all" | |
| 39 | + /> | |
| 40 | + {query && ( | |
| 41 | + <button | |
| 42 | + onClick={handleClear} | |
| 43 | + className="absolute right-1.5 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 p-2 min-w-[36px] min-h-[36px] flex items-center justify-center rounded-lg active:bg-gray-200 dark:active:bg-white/10" | |
| 44 | + > | |
| 45 | + <X className="w-4 h-4" /> | |
| 46 | + </button> | |
| 47 | + )} | |
| 48 | + </div> | |
| 49 | + ); | |
| 50 | +} | |
added
lib/activity.ts
+15 −0
@@ -0,0 +1,15 @@ | ||
| 1 | +import { prisma } from "./prisma"; | |
| 2 | + | |
| 3 | +export async function logActivity( | |
| 4 | + action: string, | |
| 5 | + fileId?: string | null, | |
| 6 | + details?: string | |
| 7 | +) { | |
| 8 | + await prisma.activityLog.create({ | |
| 9 | + data: { | |
| 10 | + action, | |
| 11 | + fileId: fileId || null, | |
| 12 | + details: details || null, | |
| 13 | + }, | |
| 14 | + }); | |
| 15 | +} | |
added
lib/ai.ts
+167 −0
@@ -0,0 +1,167 @@ | ||
| 1 | +import Anthropic from "@anthropic-ai/sdk"; | |
| 2 | +import { prisma } from "./prisma"; | |
| 3 | +import { readFileSync, existsSync } from "fs"; | |
| 4 | +import { getFilePath } from "./storage"; | |
| 5 | + | |
| 6 | +const anthropic = new Anthropic({ | |
| 7 | + apiKey: process.env.ANTHROPIC_API_KEY, | |
| 8 | +}); | |
| 9 | + | |
| 10 | +export async function aiSearch(query: string, userId?: string): Promise<{ | |
| 11 | + results: Array<{ fileId: string; name: string; relevance: string; snippet: string }>; | |
| 12 | + answer: string; | |
| 13 | +}> { | |
| 14 | + const where: Record<string, unknown> = {}; | |
| 15 | + if (userId) where.userId = userId; | |
| 16 | + | |
| 17 | + const files = await prisma.file.findMany({ | |
| 18 | + where, | |
| 19 | + select: { id: true, name: true, mimeType: true, size: true, aiTags: true, aiDescription: true, ocrText: true, createdAt: true, folder: { select: { name: true } } }, | |
| 20 | + take: 200, | |
| 21 | + orderBy: { createdAt: "desc" }, | |
| 22 | + }); | |
| 23 | + | |
| 24 | + const fileList = files.map((f) => { | |
| 25 | + const parts = [`- ${f.name} (${f.mimeType}, ID:${f.id})`]; | |
| 26 | + if (f.folder) parts.push(` Dossier: ${f.folder.name}`); | |
| 27 | + if (f.aiDescription) parts.push(` Description: ${f.aiDescription}`); | |
| 28 | + if (f.aiTags) parts.push(` Tags: ${f.aiTags}`); | |
| 29 | + if (f.ocrText) parts.push(` OCR: ${f.ocrText.substring(0, 200)}`); | |
| 30 | + return parts.join("\n"); | |
| 31 | + }).join("\n"); | |
| 32 | + | |
| 33 | + const response = await anthropic.messages.create({ | |
| 34 | + model: "claude-sonnet-4-5-20250929", | |
| 35 | + max_tokens: 1024, | |
| 36 | + messages: [{ role: "user", content: `Tu es un assistant de recherche de fichiers. Voici la liste des fichiers:\n\n${fileList}\n\nRecherche: "${query}"\n\nRéponds en JSON:\n{"results": [{"fileId": "...", "name": "...", "relevance": "high|medium|low", "snippet": "courte explication"}], "answer": "résumé"}` }], | |
| 37 | + }); | |
| 38 | + | |
| 39 | + try { | |
| 40 | + const text = response.content[0].type === "text" ? response.content[0].text : ""; | |
| 41 | + const jsonMatch = text.match(/\{[\s\S]*\}/); | |
| 42 | + if (jsonMatch) return JSON.parse(jsonMatch[0]); | |
| 43 | + } catch {} | |
| 44 | + return { results: [], answer: "Aucun résultat trouvé." }; | |
| 45 | +} | |
| 46 | + | |
| 47 | +export async function aiAutoTag(fileId: string): Promise<string[]> { | |
| 48 | + const file = await prisma.file.findUnique({ where: { id: fileId }, select: { id: true, name: true, mimeType: true, size: true, ocrText: true, storagePath: true } }); | |
| 49 | + if (!file) return []; | |
| 50 | + | |
| 51 | + let contentHint = `Fichier: ${file.name}\nType: ${file.mimeType}\nTaille: ${Number(file.size)} bytes`; | |
| 52 | + if (file.ocrText) contentHint += `\nContenu OCR: ${file.ocrText.substring(0, 500)}`; | |
| 53 | + | |
| 54 | + if (file.mimeType.startsWith("text/") || file.mimeType.includes("json") || file.mimeType.includes("xml")) { | |
| 55 | + try { | |
| 56 | + const filePath = getFilePath(file.storagePath); | |
| 57 | + if (existsSync(filePath)) contentHint += `\nContenu: ${readFileSync(filePath, "utf-8").substring(0, 1000)}`; | |
| 58 | + } catch {} | |
| 59 | + } | |
| 60 | + | |
| 61 | + const response = await anthropic.messages.create({ | |
| 62 | + model: "claude-sonnet-4-5-20250929", | |
| 63 | + max_tokens: 256, | |
| 64 | + messages: [{ role: "user", content: `Analyse ce fichier et suggère 3-5 tags pertinents en français.\n\n${contentHint}\n\nRéponds UNIQUEMENT avec un JSON array de strings. Ex: ["finance", "rapport", "2024"]` }], | |
| 65 | + }); | |
| 66 | + | |
| 67 | + try { | |
| 68 | + const text = response.content[0].type === "text" ? response.content[0].text : ""; | |
| 69 | + const match = text.match(/\[[\s\S]*\]/); | |
| 70 | + if (match) { | |
| 71 | + const tags = JSON.parse(match[0]) as string[]; | |
| 72 | + await prisma.file.update({ where: { id: fileId }, data: { aiTags: JSON.stringify(tags) } }); | |
| 73 | + return tags; | |
| 74 | + } | |
| 75 | + } catch {} | |
| 76 | + return []; | |
| 77 | +} | |
| 78 | + | |
| 79 | +export async function aiDescribe(fileId: string): Promise<string> { | |
| 80 | + const file = await prisma.file.findUnique({ where: { id: fileId }, select: { id: true, name: true, mimeType: true, storagePath: true, ocrText: true } }); | |
| 81 | + if (!file) return ""; | |
| 82 | + | |
| 83 | + if (file.mimeType.startsWith("image/")) { | |
| 84 | + try { | |
| 85 | + const filePath = getFilePath(file.storagePath); | |
| 86 | + if (existsSync(filePath)) { | |
| 87 | + const base64 = readFileSync(filePath).toString("base64"); | |
| 88 | + const mediaType = file.mimeType as "image/jpeg" | "image/png" | "image/gif" | "image/webp"; | |
| 89 | + const response = await anthropic.messages.create({ | |
| 90 | + model: "claude-sonnet-4-5-20250929", | |
| 91 | + max_tokens: 256, | |
| 92 | + messages: [{ role: "user", content: [{ type: "image", source: { type: "base64", media_type: mediaType, data: base64 } }, { type: "text", text: "Décris cette image en une phrase concise en français." }] }], | |
| 93 | + }); | |
| 94 | + const desc = response.content[0].type === "text" ? response.content[0].text : ""; | |
| 95 | + await prisma.file.update({ where: { id: fileId }, data: { aiDescription: desc } }); | |
| 96 | + return desc; | |
| 97 | + } | |
| 98 | + } catch {} | |
| 99 | + } | |
| 100 | + | |
| 101 | + let hint = `Fichier: ${file.name}\nType: ${file.mimeType}`; | |
| 102 | + if (file.mimeType.startsWith("text/") || file.mimeType.includes("json")) { | |
| 103 | + try { | |
| 104 | + const filePath = getFilePath(file.storagePath); | |
| 105 | + if (existsSync(filePath)) hint += `\nContenu: ${readFileSync(filePath, "utf-8").substring(0, 2000)}`; | |
| 106 | + } catch {} | |
| 107 | + } | |
| 108 | + if (file.ocrText) hint += `\nOCR: ${file.ocrText.substring(0, 500)}`; | |
| 109 | + | |
| 110 | + const response = await anthropic.messages.create({ | |
| 111 | + model: "claude-sonnet-4-5-20250929", | |
| 112 | + max_tokens: 128, | |
| 113 | + messages: [{ role: "user", content: `Décris ce fichier en une phrase concise en français:\n\n${hint}` }], | |
| 114 | + }); | |
| 115 | + const desc = response.content[0].type === "text" ? response.content[0].text : ""; | |
| 116 | + await prisma.file.update({ where: { id: fileId }, data: { aiDescription: desc } }); | |
| 117 | + return desc; | |
| 118 | +} | |
| 119 | + | |
| 120 | +export async function aiOcr(fileId: string): Promise<string> { | |
| 121 | + const file = await prisma.file.findUnique({ where: { id: fileId }, select: { id: true, name: true, mimeType: true, storagePath: true } }); | |
| 122 | + if (!file) return ""; | |
| 123 | + | |
| 124 | + if (file.mimeType.startsWith("image/")) { | |
| 125 | + try { | |
| 126 | + const filePath = getFilePath(file.storagePath); | |
| 127 | + if (!existsSync(filePath)) return ""; | |
| 128 | + const base64 = readFileSync(filePath).toString("base64"); | |
| 129 | + const mediaType = file.mimeType as "image/jpeg" | "image/png" | "image/gif" | "image/webp"; | |
| 130 | + const response = await anthropic.messages.create({ | |
| 131 | + model: "claude-sonnet-4-5-20250929", | |
| 132 | + max_tokens: 2048, | |
| 133 | + messages: [{ role: "user", content: [{ type: "image", source: { type: "base64", media_type: mediaType, data: base64 } }, { type: "text", text: "Extrais tout le texte visible dans cette image. Retourne uniquement le texte extrait, sans commentaire." }] }], | |
| 134 | + }); | |
| 135 | + const text = response.content[0].type === "text" ? response.content[0].text : ""; | |
| 136 | + await prisma.file.update({ where: { id: fileId }, data: { ocrText: text } }); | |
| 137 | + return text; | |
| 138 | + } catch {} | |
| 139 | + } | |
| 140 | + | |
| 141 | + if (file.mimeType === "application/pdf") { | |
| 142 | + try { | |
| 143 | + const filePath = getFilePath(file.storagePath); | |
| 144 | + if (!existsSync(filePath)) return ""; | |
| 145 | + const base64 = readFileSync(filePath).toString("base64"); | |
| 146 | + const response = await anthropic.messages.create({ | |
| 147 | + model: "claude-sonnet-4-5-20250929", | |
| 148 | + max_tokens: 4096, | |
| 149 | + messages: [{ role: "user", content: [{ type: "document", source: { type: "base64", media_type: "application/pdf", data: base64 } }, { type: "text", text: "Extrais tout le texte de ce document PDF. Retourne uniquement le texte extrait." }] as Anthropic.ContentBlockParam[] }], | |
| 150 | + }); | |
| 151 | + const text = response.content[0].type === "text" ? response.content[0].text : ""; | |
| 152 | + await prisma.file.update({ where: { id: fileId }, data: { ocrText: text } }); | |
| 153 | + return text; | |
| 154 | + } catch {} | |
| 155 | + } | |
| 156 | + return ""; | |
| 157 | +} | |
| 158 | + | |
| 159 | +export async function aiChat(message: string, context?: string): Promise<string> { | |
| 160 | + const response = await anthropic.messages.create({ | |
| 161 | + model: "claude-sonnet-4-5-20250929", | |
| 162 | + max_tokens: 1024, | |
| 163 | + system: "Tu es l'assistant IA de SPB Cloud, un cloud personnel privé. Aide l'utilisateur avec ses fichiers et réponds en français.", | |
| 164 | + messages: [{ role: "user", content: context ? `Contexte:\n${context}\n\nQuestion: ${message}` : message }], | |
| 165 | + }); | |
| 166 | + return response.content[0].type === "text" ? response.content[0].text : ""; | |
| 167 | +} | |
added
lib/auth.ts
+38 −0
@@ -0,0 +1,38 @@ | ||
| 1 | +import bcrypt from "bcryptjs"; | |
| 2 | +import { prisma } from "./prisma"; | |
| 3 | + | |
| 4 | +export async function verifyPassword(password: string, email?: string): Promise<{ valid: boolean; user: { id: string; role: string; twoFactorEnabled: boolean } | null }> { | |
| 5 | + let user; | |
| 6 | + if (email) { | |
| 7 | + user = await prisma.user.findUnique({ where: { email } }); | |
| 8 | + if (!user) return { valid: false, user: null }; | |
| 9 | + const valid = await bcrypt.compare(password, user.passwordHash); | |
| 10 | + return { valid, user: valid ? { id: user.id, role: user.role, twoFactorEnabled: user.twoFactorEnabled } : null }; | |
| 11 | + } | |
| 12 | + | |
| 13 | + // Legacy: try all users (for simple password-only login) | |
| 14 | + const users = await prisma.user.findMany(); | |
| 15 | + for (const u of users) { | |
| 16 | + const match = await bcrypt.compare(password, u.passwordHash); | |
| 17 | + if (match) { | |
| 18 | + return { valid: true, user: { id: u.id, role: u.role, twoFactorEnabled: u.twoFactorEnabled } }; | |
| 19 | + } | |
| 20 | + } | |
| 21 | + return { valid: false, user: null }; | |
| 22 | +} | |
| 23 | + | |
| 24 | +export async function hashPassword(password: string): Promise<string> { | |
| 25 | + return bcrypt.hash(password, 12); | |
| 26 | +} | |
| 27 | + | |
| 28 | +export async function getUserById(userId: string) { | |
| 29 | + return prisma.user.findUnique({ | |
| 30 | + where: { id: userId }, | |
| 31 | + select: { id: true, email: true, name: true, role: true, twoFactorEnabled: true, createdAt: true }, | |
| 32 | + }); | |
| 33 | +} | |
| 34 | + | |
| 35 | +export async function requireAdmin(userId: string): Promise<boolean> { | |
| 36 | + const user = await prisma.user.findUnique({ where: { id: userId } }); | |
| 37 | + return user?.role === "admin"; | |
| 38 | +} | |
added
lib/backup.ts
+60 −0
@@ -0,0 +1,60 @@ | ||
| 1 | +import { existsSync, mkdirSync, createWriteStream, readdirSync, statSync } from "fs"; | |
| 2 | +import { rm } from "fs/promises"; | |
| 3 | +import path from "path"; | |
| 4 | +import archiver from "archiver"; | |
| 5 | +import { prisma } from "./prisma"; | |
| 6 | + | |
| 7 | +const BACKUP_DIR = process.env.BACKUP_DIR || "./backups"; | |
| 8 | +const UPLOAD_DIR = process.env.UPLOAD_DIR || "./uploads"; | |
| 9 | + | |
| 10 | +if (!existsSync(BACKUP_DIR)) mkdirSync(BACKUP_DIR, { recursive: true }); | |
| 11 | + | |
| 12 | +export async function createBackup(type: "full" | "incremental" = "full"): Promise<{ id: string; path: string; size: number }> { | |
| 13 | + const log = await prisma.backupLog.create({ data: { status: "running", type } }); | |
| 14 | + | |
| 15 | + try { | |
| 16 | + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); | |
| 17 | + const backupPath = path.join(BACKUP_DIR, `backup-${type}-${timestamp}.zip`); | |
| 18 | + const output = createWriteStream(backupPath); | |
| 19 | + const archive = archiver("zip", { zlib: { level: 9 } }); | |
| 20 | + | |
| 21 | + const completed = new Promise<number>((resolve, reject) => { | |
| 22 | + output.on("close", () => resolve(archive.pointer())); | |
| 23 | + archive.on("error", reject); | |
| 24 | + }); | |
| 25 | + | |
| 26 | + archive.pipe(output); | |
| 27 | + if (existsSync(UPLOAD_DIR)) archive.directory(UPLOAD_DIR, "uploads"); | |
| 28 | + const dbPath = path.resolve("./prisma/dev.db"); | |
| 29 | + if (existsSync(dbPath)) archive.file(dbPath, { name: "database/dev.db" }); | |
| 30 | + await archive.finalize(); | |
| 31 | + const size = await completed; | |
| 32 | + | |
| 33 | + await prisma.backupLog.update({ where: { id: log.id }, data: { status: "completed", path: backupPath, size: BigInt(size), completedAt: new Date() } }); | |
| 34 | + return { id: log.id, path: backupPath, size }; | |
| 35 | + } catch (error) { | |
| 36 | + await prisma.backupLog.update({ where: { id: log.id }, data: { status: "failed", error: (error as Error).message, completedAt: new Date() } }); | |
| 37 | + throw error; | |
| 38 | + } | |
| 39 | +} | |
| 40 | + | |
| 41 | +export async function listBackups() { | |
| 42 | + const logs = await prisma.backupLog.findMany({ orderBy: { startedAt: "desc" }, take: 50 }); | |
| 43 | + return logs.map((l) => ({ id: l.id, status: l.status, type: l.type, size: l.size ? Number(l.size) : 0, path: l.path, startedAt: l.startedAt, completedAt: l.completedAt })); | |
| 44 | +} | |
| 45 | + | |
| 46 | +export async function deleteBackup(id: string): Promise<boolean> { | |
| 47 | + const log = await prisma.backupLog.findUnique({ where: { id } }); | |
| 48 | + if (!log) return false; | |
| 49 | + if (log.path && existsSync(log.path)) await rm(log.path); | |
| 50 | + await prisma.backupLog.delete({ where: { id } }); | |
| 51 | + return true; | |
| 52 | +} | |
| 53 | + | |
| 54 | +export function getBackupStats() { | |
| 55 | + if (!existsSync(BACKUP_DIR)) return { count: 0, totalSize: 0 }; | |
| 56 | + const files = readdirSync(BACKUP_DIR); | |
| 57 | + let totalSize = 0; | |
| 58 | + for (const file of files) totalSize += statSync(path.join(BACKUP_DIR, file)).size; | |
| 59 | + return { count: files.length, totalSize }; | |
| 60 | +} | |
added
lib/config.ts
+14 −0
@@ -0,0 +1,14 @@ | ||
| 1 | +export const features = { | |
| 2 | + codeEditor: true, | |
| 3 | + pdfAnnotations: true, | |
| 4 | + imageEditor: true, | |
| 5 | + versioning: true, | |
| 6 | + tags: true, | |
| 7 | + waveform: true, | |
| 8 | + archiveBrowser: true, | |
| 9 | +}; | |
| 10 | + | |
| 11 | +export const MAX_VERSIONS_PER_FILE = parseInt( | |
| 12 | + process.env.MAX_VERSIONS_PER_FILE || "10", | |
| 13 | + 10 | |
| 14 | +); | |
added
lib/encryption.ts
+62 −0
@@ -0,0 +1,62 @@ | ||
| 1 | +import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto"; | |
| 2 | +import { readFileSync, writeFileSync, existsSync } from "fs"; | |
| 3 | +import { getFilePath } from "./storage"; | |
| 4 | +import { prisma } from "./prisma"; | |
| 5 | + | |
| 6 | +const ALGORITHM = "aes-256-gcm"; | |
| 7 | + | |
| 8 | +function getKey(): Buffer { | |
| 9 | + const masterKey = process.env.ENCRYPTION_KEY || "default-encryption-key-change-me!"; | |
| 10 | + return scryptSync(masterKey, "spb-cloud-salt", 32); | |
| 11 | +} | |
| 12 | + | |
| 13 | +export async function encryptFile(fileId: string): Promise<boolean> { | |
| 14 | + const file = await prisma.file.findUnique({ where: { id: fileId } }); | |
| 15 | + if (!file || file.isEncrypted) return false; | |
| 16 | + | |
| 17 | + const filePath = getFilePath(file.storagePath); | |
| 18 | + if (!existsSync(filePath)) return false; | |
| 19 | + | |
| 20 | + const key = getKey(); | |
| 21 | + const iv = randomBytes(16); | |
| 22 | + const cipher = createCipheriv(ALGORITHM, key, iv); | |
| 23 | + | |
| 24 | + const plaintext = readFileSync(filePath); | |
| 25 | + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]); | |
| 26 | + const authTag = cipher.getAuthTag(); | |
| 27 | + const combined = Buffer.concat([authTag, encrypted]); | |
| 28 | + writeFileSync(filePath, combined); | |
| 29 | + | |
| 30 | + await prisma.file.update({ where: { id: fileId }, data: { isEncrypted: true, encryptionIV: iv.toString("hex") } }); | |
| 31 | + return true; | |
| 32 | +} | |
| 33 | + | |
| 34 | +export async function decryptFile(fileId: string): Promise<Buffer | null> { | |
| 35 | + const file = await prisma.file.findUnique({ where: { id: fileId } }); | |
| 36 | + if (!file || !file.isEncrypted || !file.encryptionIV) return null; | |
| 37 | + | |
| 38 | + const filePath = getFilePath(file.storagePath); | |
| 39 | + if (!existsSync(filePath)) return null; | |
| 40 | + | |
| 41 | + const key = getKey(); | |
| 42 | + const iv = Buffer.from(file.encryptionIV, "hex"); | |
| 43 | + const combined = readFileSync(filePath); | |
| 44 | + const authTag = combined.subarray(0, 16); | |
| 45 | + const encrypted = combined.subarray(16); | |
| 46 | + const decipher = createDecipheriv(ALGORITHM, key, iv); | |
| 47 | + decipher.setAuthTag(authTag); | |
| 48 | + return Buffer.concat([decipher.update(encrypted), decipher.final()]); | |
| 49 | +} | |
| 50 | + | |
| 51 | +export async function removeEncryption(fileId: string): Promise<boolean> { | |
| 52 | + const decrypted = await decryptFile(fileId); | |
| 53 | + if (!decrypted) return false; | |
| 54 | + | |
| 55 | + const file = await prisma.file.findUnique({ where: { id: fileId } }); | |
| 56 | + if (!file) return false; | |
| 57 | + | |
| 58 | + const filePath = getFilePath(file.storagePath); | |
| 59 | + writeFileSync(filePath, decrypted); | |
| 60 | + await prisma.file.update({ where: { id: fileId }, data: { isEncrypted: false, encryptionIV: null } }); | |
| 61 | + return true; | |
| 62 | +} | |
added
lib/notifications.ts
+35 −0
@@ -0,0 +1,35 @@ | ||
| 1 | +import { prisma } from "./prisma"; | |
| 2 | + | |
| 3 | +export async function createNotification(userId: string, type: string, title: string, message: string, data?: Record<string, unknown>) { | |
| 4 | + return prisma.notification.create({ data: { userId, type, title, message, data: data ? JSON.stringify(data) : null } }); | |
| 5 | +} | |
| 6 | + | |
| 7 | +export async function getNotifications(userId: string, unreadOnly = false) { | |
| 8 | + const where: Record<string, unknown> = { userId }; | |
| 9 | + if (unreadOnly) where.read = false; | |
| 10 | + return prisma.notification.findMany({ where, orderBy: { createdAt: "desc" }, take: 50 }); | |
| 11 | +} | |
| 12 | + | |
| 13 | +export async function markAsRead(notificationId: string) { | |
| 14 | + return prisma.notification.update({ where: { id: notificationId }, data: { read: true } }); | |
| 15 | +} | |
| 16 | + | |
| 17 | +export async function markAllAsRead(userId: string) { | |
| 18 | + return prisma.notification.updateMany({ where: { userId, read: false }, data: { read: true } }); | |
| 19 | +} | |
| 20 | + | |
| 21 | +export async function getUnreadCount(userId: string): Promise<number> { | |
| 22 | + return prisma.notification.count({ where: { userId, read: false } }); | |
| 23 | +} | |
| 24 | + | |
| 25 | +export async function notifyUploadComplete(userId: string, fileName: string, fileId: string) { | |
| 26 | + return createNotification(userId, "upload_complete", "Upload terminé", `${fileName} uploadé avec succès`, { fileId }); | |
| 27 | +} | |
| 28 | + | |
| 29 | +export async function notifyBackupComplete(userId: string, backupId: string) { | |
| 30 | + return createNotification(userId, "backup_complete", "Backup terminé", "Le backup a été créé avec succès", { backupId }); | |
| 31 | +} | |
| 32 | + | |
| 33 | +export async function notifyAiComplete(userId: string, fileName: string, action: string) { | |
| 34 | + return createNotification(userId, "ai_complete", "Analyse IA", `${action} terminée pour ${fileName}`); | |
| 35 | +} | |
added
lib/prisma.ts
+9 −0
@@ -0,0 +1,9 @@ | ||
| 1 | +import { PrismaClient } from "@prisma/client"; | |
| 2 | + | |
| 3 | +const globalForPrisma = globalThis as unknown as { | |
| 4 | + prisma: PrismaClient | undefined; | |
| 5 | +}; | |
| 6 | + | |
| 7 | +export const prisma = globalForPrisma.prisma ?? new PrismaClient(); | |
| 8 | + | |
| 9 | +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; | |
added
lib/rate-limit.ts
+29 −0
@@ -0,0 +1,29 @@ | ||
| 1 | +import { RateLimiterMemory } from "rate-limiter-flexible"; | |
| 2 | + | |
| 3 | +const loginLimiter = new RateLimiterMemory({ | |
| 4 | + points: 5, | |
| 5 | + duration: 60, // 5 attempts per minute | |
| 6 | +}); | |
| 7 | + | |
| 8 | +const apiLimiter = new RateLimiterMemory({ | |
| 9 | + points: 100, | |
| 10 | + duration: 60, // 100 requests per minute | |
| 11 | +}); | |
| 12 | + | |
| 13 | +export async function checkLoginRateLimit(ip: string): Promise<boolean> { | |
| 14 | + try { | |
| 15 | + await loginLimiter.consume(ip); | |
| 16 | + return true; | |
| 17 | + } catch { | |
| 18 | + return false; | |
| 19 | + } | |
| 20 | +} | |
| 21 | + | |
| 22 | +export async function checkApiRateLimit(ip: string): Promise<boolean> { | |
| 23 | + try { | |
| 24 | + await apiLimiter.consume(ip); | |
| 25 | + return true; | |
| 26 | + } catch { | |
| 27 | + return false; | |
| 28 | + } | |
| 29 | +} | |
added
lib/session.ts
+25 −0
@@ -0,0 +1,25 @@ | ||
| 1 | +import { SessionOptions } from "iron-session"; | |
| 2 | + | |
| 3 | +export interface SessionData { | |
| 4 | + isLoggedIn: boolean; | |
| 5 | + userId?: string; | |
| 6 | + userRole?: string; | |
| 7 | + csrfToken?: string; | |
| 8 | + pendingTwoFactor?: boolean; | |
| 9 | + pendingUserId?: string; | |
| 10 | +} | |
| 11 | + | |
| 12 | +export const sessionOptions: SessionOptions = { | |
| 13 | + password: process.env.SESSION_SECRET || "spb-cloud-secret-key-change-in-production-min-32-chars", | |
| 14 | + cookieName: "spb-cloud-session", | |
| 15 | + cookieOptions: { | |
| 16 | + secure: process.env.NODE_ENV === "production", | |
| 17 | + httpOnly: true, | |
| 18 | + sameSite: "lax" as const, | |
| 19 | + maxAge: 60 * 60 * 24, | |
| 20 | + }, | |
| 21 | +}; | |
| 22 | + | |
| 23 | +export const defaultSession: SessionData = { | |
| 24 | + isLoggedIn: false, | |
| 25 | +}; | |
added
lib/storage.ts
+193 −0
@@ -0,0 +1,193 @@ | ||
| 1 | +import { createReadStream, createWriteStream, existsSync, mkdirSync } from "fs"; | |
| 2 | +import { unlink, copyFile, readdir, rm, stat } from "fs/promises"; | |
| 3 | +import path from "path"; | |
| 4 | +import { v4 as uuidv4 } from "uuid"; | |
| 5 | +import { ReadStream } from "fs"; | |
| 6 | + | |
| 7 | +const UPLOAD_DIR = process.env.UPLOAD_DIR || "./uploads"; | |
| 8 | +const FILES_DIR = path.join(UPLOAD_DIR, "files"); | |
| 9 | +const CHUNKS_DIR = path.join(UPLOAD_DIR, "chunks"); | |
| 10 | +const VERSIONS_DIR = path.join(UPLOAD_DIR, "versions"); | |
| 11 | + | |
| 12 | +// Ensure directories exist | |
| 13 | +[FILES_DIR, CHUNKS_DIR, VERSIONS_DIR].forEach((dir) => { | |
| 14 | + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); | |
| 15 | +}); | |
| 16 | + | |
| 17 | +function getExtension(filename: string): string { | |
| 18 | + const ext = path.extname(filename); | |
| 19 | + return ext || ""; | |
| 20 | +} | |
| 21 | + | |
| 22 | +export async function saveFile( | |
| 23 | + buffer: Buffer, | |
| 24 | + originalName: string | |
| 25 | +): Promise<{ storagePath: string; size: number }> { | |
| 26 | + const ext = getExtension(originalName); | |
| 27 | + const storagePath = `${uuidv4()}${ext}`; | |
| 28 | + const fullPath = path.join(FILES_DIR, storagePath); | |
| 29 | + | |
| 30 | + await new Promise<void>((resolve, reject) => { | |
| 31 | + const stream = createWriteStream(fullPath); | |
| 32 | + stream.write(buffer); | |
| 33 | + stream.end(); | |
| 34 | + stream.on("finish", resolve); | |
| 35 | + stream.on("error", reject); | |
| 36 | + }); | |
| 37 | + | |
| 38 | + return { storagePath, size: buffer.length }; | |
| 39 | +} | |
| 40 | + | |
| 41 | +export async function saveChunk( | |
| 42 | + buffer: Buffer, | |
| 43 | + uploadId: string, | |
| 44 | + chunkIndex: number | |
| 45 | +): Promise<void> { | |
| 46 | + const chunkDir = path.join(CHUNKS_DIR, uploadId); | |
| 47 | + if (!existsSync(chunkDir)) mkdirSync(chunkDir, { recursive: true }); | |
| 48 | + | |
| 49 | + const chunkPath = path.join(chunkDir, `chunk-${chunkIndex}`); | |
| 50 | + await new Promise<void>((resolve, reject) => { | |
| 51 | + const stream = createWriteStream(chunkPath); | |
| 52 | + stream.write(buffer); | |
| 53 | + stream.end(); | |
| 54 | + stream.on("finish", resolve); | |
| 55 | + stream.on("error", reject); | |
| 56 | + }); | |
| 57 | +} | |
| 58 | + | |
| 59 | +export async function assembleChunks( | |
| 60 | + uploadId: string, | |
| 61 | + totalChunks: number, | |
| 62 | + originalName: string | |
| 63 | +): Promise<{ storagePath: string; size: number }> { | |
| 64 | + const ext = getExtension(originalName); | |
| 65 | + const storagePath = `${uuidv4()}${ext}`; | |
| 66 | + const fullPath = path.join(FILES_DIR, storagePath); | |
| 67 | + const chunkDir = path.join(CHUNKS_DIR, uploadId); | |
| 68 | + | |
| 69 | + const writeStream = createWriteStream(fullPath); | |
| 70 | + let totalSize = 0; | |
| 71 | + | |
| 72 | + for (let i = 0; i < totalChunks; i++) { | |
| 73 | + const chunkPath = path.join(chunkDir, `chunk-${i}`); | |
| 74 | + const chunkBuffer = await new Promise<Buffer>((resolve, reject) => { | |
| 75 | + const chunks: Buffer[] = []; | |
| 76 | + const readStream = createReadStream(chunkPath); | |
| 77 | + readStream.on("data", (chunk) => chunks.push(chunk as Buffer)); | |
| 78 | + readStream.on("end", () => resolve(Buffer.concat(chunks))); | |
| 79 | + readStream.on("error", reject); | |
| 80 | + }); | |
| 81 | + writeStream.write(chunkBuffer); | |
| 82 | + totalSize += chunkBuffer.length; | |
| 83 | + } | |
| 84 | + | |
| 85 | + await new Promise<void>((resolve) => writeStream.end(resolve)); | |
| 86 | + | |
| 87 | + // Clean up chunks | |
| 88 | + await rm(chunkDir, { recursive: true, force: true }); | |
| 89 | + | |
| 90 | + return { storagePath, size: totalSize }; | |
| 91 | +} | |
| 92 | + | |
| 93 | +export async function deleteFile(storagePath: string): Promise<void> { | |
| 94 | + const fullPath = path.join(FILES_DIR, storagePath); | |
| 95 | + if (existsSync(fullPath)) { | |
| 96 | + await unlink(fullPath); | |
| 97 | + } | |
| 98 | +} | |
| 99 | + | |
| 100 | +export async function duplicateFile( | |
| 101 | + storagePath: string, | |
| 102 | + originalName: string | |
| 103 | +): Promise<{ storagePath: string; size: number }> { | |
| 104 | + const ext = getExtension(originalName); | |
| 105 | + const newStoragePath = `${uuidv4()}${ext}`; | |
| 106 | + const srcPath = path.join(FILES_DIR, storagePath); | |
| 107 | + const destPath = path.join(FILES_DIR, newStoragePath); | |
| 108 | + | |
| 109 | + await copyFile(srcPath, destPath); | |
| 110 | + const stats = await stat(destPath); | |
| 111 | + | |
| 112 | + return { storagePath: newStoragePath, size: Number(stats.size) }; | |
| 113 | +} | |
| 114 | + | |
| 115 | +export function getFilePath(storagePath: string): string { | |
| 116 | + return path.resolve(FILES_DIR, storagePath); | |
| 117 | +} | |
| 118 | + | |
| 119 | +export function getFileStream(storagePath: string): ReadStream { | |
| 120 | + const fullPath = path.join(FILES_DIR, storagePath); | |
| 121 | + return createReadStream(fullPath); | |
| 122 | +} | |
| 123 | + | |
| 124 | +export async function getStorageUsage(): Promise<{ | |
| 125 | + totalSize: number; | |
| 126 | + fileCount: number; | |
| 127 | +}> { | |
| 128 | + let totalSize = 0; | |
| 129 | + let fileCount = 0; | |
| 130 | + | |
| 131 | + if (existsSync(FILES_DIR)) { | |
| 132 | + const files = await readdir(FILES_DIR); | |
| 133 | + for (const file of files) { | |
| 134 | + const filePath = path.join(FILES_DIR, file); | |
| 135 | + const stats = await stat(filePath); | |
| 136 | + totalSize += stats.size; | |
| 137 | + fileCount++; | |
| 138 | + } | |
| 139 | + } | |
| 140 | + | |
| 141 | + return { totalSize, fileCount }; | |
| 142 | +} | |
| 143 | + | |
| 144 | +// --- Version management --- | |
| 145 | + | |
| 146 | +export async function saveVersion( | |
| 147 | + currentStoragePath: string, | |
| 148 | + originalName: string | |
| 149 | +): Promise<{ storagePath: string; size: number }> { | |
| 150 | + const ext = getExtension(originalName); | |
| 151 | + const versionStoragePath = `${uuidv4()}${ext}`; | |
| 152 | + const srcPath = path.join(FILES_DIR, currentStoragePath); | |
| 153 | + const destPath = path.join(VERSIONS_DIR, versionStoragePath); | |
| 154 | + | |
| 155 | + await copyFile(srcPath, destPath); | |
| 156 | + const stats = await stat(destPath); | |
| 157 | + | |
| 158 | + return { storagePath: versionStoragePath, size: Number(stats.size) }; | |
| 159 | +} | |
| 160 | + | |
| 161 | +export function getVersionPath(storagePath: string): string { | |
| 162 | + return path.resolve(VERSIONS_DIR, storagePath); | |
| 163 | +} | |
| 164 | + | |
| 165 | +export async function deleteVersion(storagePath: string): Promise<void> { | |
| 166 | + const fullPath = path.join(VERSIONS_DIR, storagePath); | |
| 167 | + if (existsSync(fullPath)) { | |
| 168 | + await unlink(fullPath); | |
| 169 | + } | |
| 170 | +} | |
| 171 | + | |
| 172 | +export async function restoreVersion( | |
| 173 | + versionStoragePath: string, | |
| 174 | + currentStoragePath: string | |
| 175 | +): Promise<void> { | |
| 176 | + const srcPath = path.join(VERSIONS_DIR, versionStoragePath); | |
| 177 | + const destPath = path.join(FILES_DIR, currentStoragePath); | |
| 178 | + await copyFile(srcPath, destPath); | |
| 179 | +} | |
| 180 | + | |
| 181 | +export async function saveFileFromBuffer( | |
| 182 | + buffer: Buffer, | |
| 183 | + storagePath: string | |
| 184 | +): Promise<void> { | |
| 185 | + const fullPath = path.join(FILES_DIR, storagePath); | |
| 186 | + await new Promise<void>((resolve, reject) => { | |
| 187 | + const stream = createWriteStream(fullPath); | |
| 188 | + stream.write(buffer); | |
| 189 | + stream.end(); | |
| 190 | + stream.on("finish", resolve); | |
| 191 | + stream.on("error", reject); | |
| 192 | + }); | |
| 193 | +} | |
added
lib/utils.ts
+161 −0
@@ -0,0 +1,161 @@ | ||
| 1 | +export function formatFileSize(bytes: number): string { | |
| 2 | + if (bytes === 0) return "0 B"; | |
| 3 | + const k = 1024; | |
| 4 | + const sizes = ["B", "KB", "MB", "GB", "TB"]; | |
| 5 | + const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| 6 | + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`; | |
| 7 | +} | |
| 8 | + | |
| 9 | +export function formatDate(date: string | Date): string { | |
| 10 | + return new Intl.DateTimeFormat("fr-CA", { | |
| 11 | + year: "numeric", | |
| 12 | + month: "short", | |
| 13 | + day: "numeric", | |
| 14 | + hour: "2-digit", | |
| 15 | + minute: "2-digit", | |
| 16 | + }).format(new Date(date)); | |
| 17 | +} | |
| 18 | + | |
| 19 | +export function getFileIcon(mimeType: string): string { | |
| 20 | + if (mimeType.startsWith("image/")) return "image"; | |
| 21 | + if (mimeType.startsWith("video/")) return "video"; | |
| 22 | + if (mimeType.startsWith("audio/")) return "audio"; | |
| 23 | + if (mimeType === "application/pdf") return "pdf"; | |
| 24 | + if (mimeType.includes("spreadsheet") || mimeType === "text/csv") return "spreadsheet"; | |
| 25 | + if (mimeType.includes("document") || mimeType.includes("word")) return "document"; | |
| 26 | + if (mimeType.includes("presentation")) return "presentation"; | |
| 27 | + if (isArchiveMime(mimeType)) return "archive"; | |
| 28 | + if (mimeType.startsWith("text/") || mimeType.includes("json") || mimeType.includes("xml") || mimeType.includes("javascript") || mimeType.includes("typescript")) return "code"; | |
| 29 | + return "file"; | |
| 30 | +} | |
| 31 | + | |
| 32 | +export function isArchiveMime(mimeType: string): boolean { | |
| 33 | + return ( | |
| 34 | + mimeType === "application/zip" || | |
| 35 | + mimeType === "application/x-zip-compressed" || | |
| 36 | + mimeType === "application/x-rar-compressed" || | |
| 37 | + mimeType === "application/x-7z-compressed" || | |
| 38 | + mimeType === "application/x-tar" || | |
| 39 | + mimeType === "application/gzip" || | |
| 40 | + mimeType.includes("zip") || | |
| 41 | + mimeType.includes("archive") || | |
| 42 | + mimeType.includes("compressed") | |
| 43 | + ); | |
| 44 | +} | |
| 45 | + | |
| 46 | +export function isPreviewable(mimeType: string): boolean { | |
| 47 | + return ( | |
| 48 | + mimeType.startsWith("image/") || | |
| 49 | + mimeType.startsWith("video/") || | |
| 50 | + mimeType.startsWith("audio/") || | |
| 51 | + mimeType === "application/pdf" || | |
| 52 | + mimeType.startsWith("text/") || | |
| 53 | + mimeType === "text/csv" || | |
| 54 | + mimeType.includes("json") || | |
| 55 | + mimeType.includes("xml") || | |
| 56 | + mimeType.includes("javascript") || | |
| 57 | + mimeType.includes("typescript") || | |
| 58 | + mimeType.includes("markdown") || | |
| 59 | + isArchiveMime(mimeType) | |
| 60 | + ); | |
| 61 | +} | |
| 62 | + | |
| 63 | +export function isTextMime(mimeType: string): boolean { | |
| 64 | + return ( | |
| 65 | + mimeType.startsWith("text/") || | |
| 66 | + mimeType.includes("json") || | |
| 67 | + mimeType.includes("xml") || | |
| 68 | + mimeType.includes("javascript") || | |
| 69 | + mimeType.includes("typescript") || | |
| 70 | + mimeType.includes("markdown") | |
| 71 | + ); | |
| 72 | +} | |
| 73 | + | |
| 74 | +export function isCodeMime(mimeType: string): boolean { | |
| 75 | + return ( | |
| 76 | + mimeType.includes("javascript") || | |
| 77 | + mimeType.includes("typescript") || | |
| 78 | + mimeType.includes("json") || | |
| 79 | + mimeType.includes("xml") || | |
| 80 | + mimeType.includes("css") || | |
| 81 | + mimeType.includes("html") || | |
| 82 | + mimeType.includes("python") || | |
| 83 | + mimeType.includes("java") || | |
| 84 | + mimeType.includes("x-c") || | |
| 85 | + mimeType.includes("rust") || | |
| 86 | + mimeType.includes("go") || | |
| 87 | + mimeType.includes("yaml") || | |
| 88 | + mimeType.includes("toml") || | |
| 89 | + mimeType.includes("sql") || | |
| 90 | + mimeType.includes("shell") || | |
| 91 | + mimeType.includes("bash") | |
| 92 | + ); | |
| 93 | +} | |
| 94 | + | |
| 95 | +export function getLanguageFromMime(mimeType: string): string { | |
| 96 | + const map: Record<string, string> = { | |
| 97 | + "application/javascript": "javascript", | |
| 98 | + "text/javascript": "javascript", | |
| 99 | + "application/typescript": "typescript", | |
| 100 | + "text/typescript": "typescript", | |
| 101 | + "application/json": "json", | |
| 102 | + "text/html": "html", | |
| 103 | + "text/css": "css", | |
| 104 | + "text/xml": "xml", | |
| 105 | + "application/xml": "xml", | |
| 106 | + "text/markdown": "markdown", | |
| 107 | + "text/x-python": "python", | |
| 108 | + "text/x-java": "java", | |
| 109 | + "text/x-c": "c", | |
| 110 | + "text/x-cpp": "cpp", | |
| 111 | + "text/x-rust": "rust", | |
| 112 | + "text/x-go": "go", | |
| 113 | + "text/yaml": "yaml", | |
| 114 | + "application/x-yaml": "yaml", | |
| 115 | + "text/x-toml": "toml", | |
| 116 | + "text/x-sql": "sql", | |
| 117 | + "text/x-shellscript": "shell", | |
| 118 | + "application/x-sh": "shell", | |
| 119 | + }; | |
| 120 | + return map[mimeType] || "text"; | |
| 121 | +} | |
| 122 | + | |
| 123 | +export function getLanguageFromFilename(filename: string): string { | |
| 124 | + const ext = filename.split(".").pop()?.toLowerCase() || ""; | |
| 125 | + const map: Record<string, string> = { | |
| 126 | + js: "javascript", | |
| 127 | + jsx: "javascript", | |
| 128 | + ts: "typescript", | |
| 129 | + tsx: "typescript", | |
| 130 | + json: "json", | |
| 131 | + html: "html", | |
| 132 | + htm: "html", | |
| 133 | + css: "css", | |
| 134 | + scss: "css", | |
| 135 | + xml: "xml", | |
| 136 | + md: "markdown", | |
| 137 | + py: "python", | |
| 138 | + java: "java", | |
| 139 | + c: "c", | |
| 140 | + h: "c", | |
| 141 | + cpp: "cpp", | |
| 142 | + hpp: "cpp", | |
| 143 | + rs: "rust", | |
| 144 | + go: "go", | |
| 145 | + yaml: "yaml", | |
| 146 | + yml: "yaml", | |
| 147 | + toml: "toml", | |
| 148 | + sql: "sql", | |
| 149 | + sh: "shell", | |
| 150 | + bash: "shell", | |
| 151 | + zsh: "shell", | |
| 152 | + rb: "ruby", | |
| 153 | + php: "php", | |
| 154 | + swift: "swift", | |
| 155 | + kt: "kotlin", | |
| 156 | + r: "r", | |
| 157 | + lua: "lua", | |
| 158 | + dockerfile: "dockerfile", | |
| 159 | + }; | |
| 160 | + return map[ext] || "text"; | |
| 161 | +} | |
added
middleware.ts
+42 −0
@@ -0,0 +1,42 @@ | ||
| 1 | +import { NextRequest, NextResponse } from "next/server"; | |
| 2 | +import { getIronSession } from "iron-session"; | |
| 3 | +import { SessionData, sessionOptions } from "./lib/session"; | |
| 4 | + | |
| 5 | +const publicPaths = [ | |
| 6 | + "/login", | |
| 7 | + "/api/auth/login", | |
| 8 | + "/api/auth/2fa/verify", | |
| 9 | + "/shared", | |
| 10 | + "/api/shared", | |
| 11 | + "/api/v1/", | |
| 12 | + "/_next", | |
| 13 | + "/favicon.ico", | |
| 14 | +]; | |
| 15 | + | |
| 16 | +export async function middleware(request: NextRequest) { | |
| 17 | + const { pathname } = request.nextUrl; | |
| 18 | + | |
| 19 | + // Allow public paths | |
| 20 | + if (publicPaths.some((path) => pathname.startsWith(path))) { | |
| 21 | + return NextResponse.next(); | |
| 22 | + } | |
| 23 | + | |
| 24 | + // Check session | |
| 25 | + const response = NextResponse.next(); | |
| 26 | + const session = await getIronSession<SessionData>(request, response, sessionOptions); | |
| 27 | + | |
| 28 | + if (!session.isLoggedIn) { | |
| 29 | + if (pathname.startsWith("/api/")) { | |
| 30 | + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | |
| 31 | + } | |
| 32 | + return NextResponse.redirect(new URL("/login", request.url)); | |
| 33 | + } | |
| 34 | + | |
| 35 | + return response; | |
| 36 | +} | |
| 37 | + | |
| 38 | +export const config = { | |
| 39 | + matcher: [ | |
| 40 | + "/((?!_next/static|_next/image|favicon.ico).*)", | |
| 41 | + ], | |
| 42 | +}; | |
added
next-env.d.ts
+5 −0
@@ -0,0 +1,5 @@ | ||
| 1 | +/// <reference types="next" /> | |
| 2 | +/// <reference types="next/image-types/global" /> | |
| 3 | + | |
| 4 | +// NOTE: This file should not be edited | |
| 5 | +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. | |
added
next.config.mjs
+32 −0
@@ -0,0 +1,32 @@ | ||
| 1 | +/** @type {import('next').NextConfig} */ | |
| 2 | +const nextConfig = { | |
| 3 | + experimental: { | |
| 4 | + serverComponentsExternalPackages: ["bcryptjs"], | |
| 5 | + }, | |
| 6 | + async headers() { | |
| 7 | + return [ | |
| 8 | + { | |
| 9 | + source: "/(.*)", | |
| 10 | + headers: [ | |
| 11 | + { key: "X-Content-Type-Options", value: "nosniff" }, | |
| 12 | + { key: "X-Frame-Options", value: "SAMEORIGIN" }, | |
| 13 | + { key: "X-XSS-Protection", value: "1; mode=block" }, | |
| 14 | + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, | |
| 15 | + ], | |
| 16 | + }, | |
| 17 | + { | |
| 18 | + source: "/pdf.worker.min.mjs", | |
| 19 | + headers: [ | |
| 20 | + { key: "Content-Type", value: "application/javascript" }, | |
| 21 | + ], | |
| 22 | + }, | |
| 23 | + ]; | |
| 24 | + }, | |
| 25 | + webpack: (config) => { | |
| 26 | + // Avoid bundling pdfjs-dist worker | |
| 27 | + config.resolve.alias.canvas = false; | |
| 28 | + return config; | |
| 29 | + }, | |
| 30 | +}; | |
| 31 | + | |
| 32 | +export default nextConfig; | |
added
package-lock.json
+9544 −0
@@ -0,0 +1,10484 @@ | ||
| 1 | +{ | |
| 2 | + "name": "spb-private-cloud", | |
| 3 | + "version": "1.0.0", | |
| 4 | + "lockfileVersion": 3, | |
| 5 | + "requires": true, | |
| 6 | + "packages": { | |
| 7 | + "": { | |
| 8 | + "name": "spb-private-cloud", | |
| 9 | + "version": "1.0.0", | |
| 10 | + "hasInstallScript": true, | |
| 11 | + "dependencies": { | |
| 12 | + "@anthropic-ai/sdk": "^0.75.0", | |
| 13 | + "@codemirror/autocomplete": "^6.20.0", | |
| 14 | + "@codemirror/commands": "^6.10.2", | |
| 15 | + "@codemirror/lang-cpp": "^6.0.3", | |
| 16 | + "@codemirror/lang-css": "^6.3.1", | |
| 17 | + "@codemirror/lang-go": "^6.0.1", | |
| 18 | + "@codemirror/lang-html": "^6.4.11", | |
| 19 | + "@codemirror/lang-java": "^6.0.2", | |
| 20 | + "@codemirror/lang-javascript": "^6.2.4", | |
| 21 | + "@codemirror/lang-json": "^6.0.2", | |
| 22 | + "@codemirror/lang-markdown": "^6.5.0", | |
| 23 | + "@codemirror/lang-php": "^6.0.2", | |
| 24 | + "@codemirror/lang-python": "^6.2.1", | |
| 25 | + "@codemirror/lang-rust": "^6.0.2", | |
| 26 | + "@codemirror/lang-sql": "^6.10.0", | |
| 27 | + "@codemirror/lang-xml": "^6.1.0", | |
| 28 | + "@codemirror/language": "^6.12.1", | |
| 29 | + "@codemirror/search": "^6.6.0", | |
| 30 | + "@codemirror/state": "^6.5.4", | |
| 31 | + "@codemirror/theme-one-dark": "^6.1.3", | |
| 32 | + "@codemirror/view": "^6.39.14", | |
| 33 | + "@prisma/client": "^5.22.0", | |
| 34 | + "archiver": "^7.0.1", | |
| 35 | + "bcryptjs": "^3.0.3", | |
| 36 | + "diff": "^8.0.3", | |
| 37 | + "framer-motion": "^12.34.0", | |
| 38 | + "iron-session": "^8.0.4", | |
| 39 | + "jszip": "^3.10.1", | |
| 40 | + "lucide-react": "^0.564.0", | |
| 41 | + "mime-types": "^3.0.2", | |
| 42 | + "next": "14.2.35", | |
| 43 | + "otplib": "^13.3.0", | |
| 44 | + "papaparse": "^5.5.3", | |
| 45 | + "pdf-lib": "^1.17.1", | |
| 46 | + "pdfjs-dist": "^5.4.624", | |
| 47 | + "prisma": "^5.22.0", | |
| 48 | + "qrcode": "^1.5.4", | |
| 49 | + "rate-limiter-flexible": "^9.1.1", | |
| 50 | + "react": "^18", | |
| 51 | + "react-dom": "^18", | |
| 52 | + "react-dropzone": "^15.0.0", | |
| 53 | + "react-hot-toast": "^2.6.0", | |
| 54 | + "react-markdown": "^10.1.0", | |
| 55 | + "react-syntax-highlighter": "^16.1.0", | |
| 56 | + "sharp": "^0.34.5", | |
| 57 | + "uuid": "^13.0.0", | |
| 58 | + "wavesurfer.js": "^7.12.1" | |
| 59 | + }, | |
| 60 | + "devDependencies": { | |
| 61 | + "@types/archiver": "^7.0.0", | |
| 62 | + "@types/bcryptjs": "^2.4.6", | |
| 63 | + "@types/diff": "^7.0.2", | |
| 64 | + "@types/mime-types": "^3.0.1", | |
| 65 | + "@types/node": "^20", | |
| 66 | + "@types/papaparse": "^5.5.2", | |
| 67 | + "@types/qrcode": "^1.5.6", | |
| 68 | + "@types/react": "^18", | |
| 69 | + "@types/react-dom": "^18", | |
| 70 | + "@types/react-syntax-highlighter": "^15.5.13", | |
| 71 | + "@types/sharp": "^0.31.1", | |
| 72 | + "@types/uuid": "^10.0.0", | |
| 73 | + "eslint": "^8", | |
| 74 | + "eslint-config-next": "14.2.35", | |
| 75 | + "postcss": "^8", | |
| 76 | + "tailwindcss": "^3.4.1", | |
| 77 | + "tsx": "^4.21.0", | |
| 78 | + "typescript": "^5" | |
| 79 | + } | |
| 80 | + }, | |
| 81 | + "node_modules/@alloc/quick-lru": { | |
| 82 | + "version": "5.2.0", | |
| 83 | + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", | |
| 84 | + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", | |
| 85 | + "dev": true, | |
| 86 | + "license": "MIT", | |
| 87 | + "engines": { | |
| 88 | + "node": ">=10" | |
| 89 | + }, | |
| 90 | + "funding": { | |
| 91 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 92 | + } | |
| 93 | + }, | |
| 94 | + "node_modules/@anthropic-ai/sdk": { | |
| 95 | + "version": "0.75.0", | |
| 96 | + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.75.0.tgz", | |
| 97 | + "integrity": "sha512-Xr/BESTYMOnpyOK7Gxie2LWc+G5f9+xZEYVAuWtDdYDtINocoe046gszFMeRJZ2FfySyGxdF3OHKYwB6/+p4uQ==", | |
| 98 | + "license": "MIT", | |
| 99 | + "dependencies": { | |
| 100 | + "json-schema-to-ts": "^3.1.1" | |
| 101 | + }, | |
| 102 | + "bin": { | |
| 103 | + "anthropic-ai-sdk": "bin/cli" | |
| 104 | + }, | |
| 105 | + "peerDependencies": { | |
| 106 | + "zod": "^3.25.0 || ^4.0.0" | |
| 107 | + }, | |
| 108 | + "peerDependenciesMeta": { | |
| 109 | + "zod": { | |
| 110 | + "optional": true | |
| 111 | + } | |
| 112 | + } | |
| 113 | + }, | |
| 114 | + "node_modules/@babel/runtime": { | |
| 115 | + "version": "7.28.6", | |
| 116 | + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", | |
| 117 | + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", | |
| 118 | + "license": "MIT", | |
| 119 | + "engines": { | |
| 120 | + "node": ">=6.9.0" | |
| 121 | + } | |
| 122 | + }, | |
| 123 | + "node_modules/@codemirror/autocomplete": { | |
| 124 | + "version": "6.20.0", | |
| 125 | + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", | |
| 126 | + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", | |
| 127 | + "license": "MIT", | |
| 128 | + "dependencies": { | |
| 129 | + "@codemirror/language": "^6.0.0", | |
| 130 | + "@codemirror/state": "^6.0.0", | |
| 131 | + "@codemirror/view": "^6.17.0", | |
| 132 | + "@lezer/common": "^1.0.0" | |
| 133 | + } | |
| 134 | + }, | |
| 135 | + "node_modules/@codemirror/commands": { | |
| 136 | + "version": "6.10.2", | |
| 137 | + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.2.tgz", | |
| 138 | + "integrity": "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ==", | |
| 139 | + "license": "MIT", | |
| 140 | + "dependencies": { | |
| 141 | + "@codemirror/language": "^6.0.0", | |
| 142 | + "@codemirror/state": "^6.4.0", | |
| 143 | + "@codemirror/view": "^6.27.0", | |
| 144 | + "@lezer/common": "^1.1.0" | |
| 145 | + } | |
| 146 | + }, | |
| 147 | + "node_modules/@codemirror/lang-cpp": { | |
| 148 | + "version": "6.0.3", | |
| 149 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", | |
| 150 | + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", | |
| 151 | + "license": "MIT", | |
| 152 | + "dependencies": { | |
| 153 | + "@codemirror/language": "^6.0.0", | |
| 154 | + "@lezer/cpp": "^1.0.0" | |
| 155 | + } | |
| 156 | + }, | |
| 157 | + "node_modules/@codemirror/lang-css": { | |
| 158 | + "version": "6.3.1", | |
| 159 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", | |
| 160 | + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", | |
| 161 | + "license": "MIT", | |
| 162 | + "dependencies": { | |
| 163 | + "@codemirror/autocomplete": "^6.0.0", | |
| 164 | + "@codemirror/language": "^6.0.0", | |
| 165 | + "@codemirror/state": "^6.0.0", | |
| 166 | + "@lezer/common": "^1.0.2", | |
| 167 | + "@lezer/css": "^1.1.7" | |
| 168 | + } | |
| 169 | + }, | |
| 170 | + "node_modules/@codemirror/lang-go": { | |
| 171 | + "version": "6.0.1", | |
| 172 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", | |
| 173 | + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", | |
| 174 | + "license": "MIT", | |
| 175 | + "dependencies": { | |
| 176 | + "@codemirror/autocomplete": "^6.0.0", | |
| 177 | + "@codemirror/language": "^6.6.0", | |
| 178 | + "@codemirror/state": "^6.0.0", | |
| 179 | + "@lezer/common": "^1.0.0", | |
| 180 | + "@lezer/go": "^1.0.0" | |
| 181 | + } | |
| 182 | + }, | |
| 183 | + "node_modules/@codemirror/lang-html": { | |
| 184 | + "version": "6.4.11", | |
| 185 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", | |
| 186 | + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", | |
| 187 | + "license": "MIT", | |
| 188 | + "dependencies": { | |
| 189 | + "@codemirror/autocomplete": "^6.0.0", | |
| 190 | + "@codemirror/lang-css": "^6.0.0", | |
| 191 | + "@codemirror/lang-javascript": "^6.0.0", | |
| 192 | + "@codemirror/language": "^6.4.0", | |
| 193 | + "@codemirror/state": "^6.0.0", | |
| 194 | + "@codemirror/view": "^6.17.0", | |
| 195 | + "@lezer/common": "^1.0.0", | |
| 196 | + "@lezer/css": "^1.1.0", | |
| 197 | + "@lezer/html": "^1.3.12" | |
| 198 | + } | |
| 199 | + }, | |
| 200 | + "node_modules/@codemirror/lang-java": { | |
| 201 | + "version": "6.0.2", | |
| 202 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", | |
| 203 | + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", | |
| 204 | + "license": "MIT", | |
| 205 | + "dependencies": { | |
| 206 | + "@codemirror/language": "^6.0.0", | |
| 207 | + "@lezer/java": "^1.0.0" | |
| 208 | + } | |
| 209 | + }, | |
| 210 | + "node_modules/@codemirror/lang-javascript": { | |
| 211 | + "version": "6.2.4", | |
| 212 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", | |
| 213 | + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", | |
| 214 | + "license": "MIT", | |
| 215 | + "dependencies": { | |
| 216 | + "@codemirror/autocomplete": "^6.0.0", | |
| 217 | + "@codemirror/language": "^6.6.0", | |
| 218 | + "@codemirror/lint": "^6.0.0", | |
| 219 | + "@codemirror/state": "^6.0.0", | |
| 220 | + "@codemirror/view": "^6.17.0", | |
| 221 | + "@lezer/common": "^1.0.0", | |
| 222 | + "@lezer/javascript": "^1.0.0" | |
| 223 | + } | |
| 224 | + }, | |
| 225 | + "node_modules/@codemirror/lang-json": { | |
| 226 | + "version": "6.0.2", | |
| 227 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", | |
| 228 | + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", | |
| 229 | + "license": "MIT", | |
| 230 | + "dependencies": { | |
| 231 | + "@codemirror/language": "^6.0.0", | |
| 232 | + "@lezer/json": "^1.0.0" | |
| 233 | + } | |
| 234 | + }, | |
| 235 | + "node_modules/@codemirror/lang-markdown": { | |
| 236 | + "version": "6.5.0", | |
| 237 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", | |
| 238 | + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", | |
| 239 | + "license": "MIT", | |
| 240 | + "dependencies": { | |
| 241 | + "@codemirror/autocomplete": "^6.7.1", | |
| 242 | + "@codemirror/lang-html": "^6.0.0", | |
| 243 | + "@codemirror/language": "^6.3.0", | |
| 244 | + "@codemirror/state": "^6.0.0", | |
| 245 | + "@codemirror/view": "^6.0.0", | |
| 246 | + "@lezer/common": "^1.2.1", | |
| 247 | + "@lezer/markdown": "^1.0.0" | |
| 248 | + } | |
| 249 | + }, | |
| 250 | + "node_modules/@codemirror/lang-php": { | |
| 251 | + "version": "6.0.2", | |
| 252 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", | |
| 253 | + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", | |
| 254 | + "license": "MIT", | |
| 255 | + "dependencies": { | |
| 256 | + "@codemirror/lang-html": "^6.0.0", | |
| 257 | + "@codemirror/language": "^6.0.0", | |
| 258 | + "@codemirror/state": "^6.0.0", | |
| 259 | + "@lezer/common": "^1.0.0", | |
| 260 | + "@lezer/php": "^1.0.0" | |
| 261 | + } | |
| 262 | + }, | |
| 263 | + "node_modules/@codemirror/lang-python": { | |
| 264 | + "version": "6.2.1", | |
| 265 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", | |
| 266 | + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", | |
| 267 | + "license": "MIT", | |
| 268 | + "dependencies": { | |
| 269 | + "@codemirror/autocomplete": "^6.3.2", | |
| 270 | + "@codemirror/language": "^6.8.0", | |
| 271 | + "@codemirror/state": "^6.0.0", | |
| 272 | + "@lezer/common": "^1.2.1", | |
| 273 | + "@lezer/python": "^1.1.4" | |
| 274 | + } | |
| 275 | + }, | |
| 276 | + "node_modules/@codemirror/lang-rust": { | |
| 277 | + "version": "6.0.2", | |
| 278 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", | |
| 279 | + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", | |
| 280 | + "license": "MIT", | |
| 281 | + "dependencies": { | |
| 282 | + "@codemirror/language": "^6.0.0", | |
| 283 | + "@lezer/rust": "^1.0.0" | |
| 284 | + } | |
| 285 | + }, | |
| 286 | + "node_modules/@codemirror/lang-sql": { | |
| 287 | + "version": "6.10.0", | |
| 288 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", | |
| 289 | + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", | |
| 290 | + "license": "MIT", | |
| 291 | + "dependencies": { | |
| 292 | + "@codemirror/autocomplete": "^6.0.0", | |
| 293 | + "@codemirror/language": "^6.0.0", | |
| 294 | + "@codemirror/state": "^6.0.0", | |
| 295 | + "@lezer/common": "^1.2.0", | |
| 296 | + "@lezer/highlight": "^1.0.0", | |
| 297 | + "@lezer/lr": "^1.0.0" | |
| 298 | + } | |
| 299 | + }, | |
| 300 | + "node_modules/@codemirror/lang-xml": { | |
| 301 | + "version": "6.1.0", | |
| 302 | + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", | |
| 303 | + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", | |
| 304 | + "license": "MIT", | |
| 305 | + "dependencies": { | |
| 306 | + "@codemirror/autocomplete": "^6.0.0", | |
| 307 | + "@codemirror/language": "^6.4.0", | |
| 308 | + "@codemirror/state": "^6.0.0", | |
| 309 | + "@codemirror/view": "^6.0.0", | |
| 310 | + "@lezer/common": "^1.0.0", | |
| 311 | + "@lezer/xml": "^1.0.0" | |
| 312 | + } | |
| 313 | + }, | |
| 314 | + "node_modules/@codemirror/language": { | |
| 315 | + "version": "6.12.1", | |
| 316 | + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", | |
| 317 | + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", | |
| 318 | + "license": "MIT", | |
| 319 | + "dependencies": { | |
| 320 | + "@codemirror/state": "^6.0.0", | |
| 321 | + "@codemirror/view": "^6.23.0", | |
| 322 | + "@lezer/common": "^1.5.0", | |
| 323 | + "@lezer/highlight": "^1.0.0", | |
| 324 | + "@lezer/lr": "^1.0.0", | |
| 325 | + "style-mod": "^4.0.0" | |
| 326 | + } | |
| 327 | + }, | |
| 328 | + "node_modules/@codemirror/lint": { | |
| 329 | + "version": "6.9.4", | |
| 330 | + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.4.tgz", | |
| 331 | + "integrity": "sha512-ABc9vJ8DEmvOWuH26P3i8FpMWPQkduD9Rvba5iwb6O3hxASgclm3T3krGo8NASXkHCidz6b++LWlzWIUfEPSWw==", | |
| 332 | + "license": "MIT", | |
| 333 | + "dependencies": { | |
| 334 | + "@codemirror/state": "^6.0.0", | |
| 335 | + "@codemirror/view": "^6.35.0", | |
| 336 | + "crelt": "^1.0.5" | |
| 337 | + } | |
| 338 | + }, | |
| 339 | + "node_modules/@codemirror/search": { | |
| 340 | + "version": "6.6.0", | |
| 341 | + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.6.0.tgz", | |
| 342 | + "integrity": "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==", | |
| 343 | + "license": "MIT", | |
| 344 | + "dependencies": { | |
| 345 | + "@codemirror/state": "^6.0.0", | |
| 346 | + "@codemirror/view": "^6.37.0", | |
| 347 | + "crelt": "^1.0.5" | |
| 348 | + } | |
| 349 | + }, | |
| 350 | + "node_modules/@codemirror/state": { | |
| 351 | + "version": "6.5.4", | |
| 352 | + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", | |
| 353 | + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", | |
| 354 | + "license": "MIT", | |
| 355 | + "dependencies": { | |
| 356 | + "@marijn/find-cluster-break": "^1.0.0" | |
| 357 | + } | |
| 358 | + }, | |
| 359 | + "node_modules/@codemirror/theme-one-dark": { | |
| 360 | + "version": "6.1.3", | |
| 361 | + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", | |
| 362 | + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", | |
| 363 | + "license": "MIT", | |
| 364 | + "dependencies": { | |
| 365 | + "@codemirror/language": "^6.0.0", | |
| 366 | + "@codemirror/state": "^6.0.0", | |
| 367 | + "@codemirror/view": "^6.0.0", | |
| 368 | + "@lezer/highlight": "^1.0.0" | |
| 369 | + } | |
| 370 | + }, | |
| 371 | + "node_modules/@codemirror/view": { | |
| 372 | + "version": "6.39.14", | |
| 373 | + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.14.tgz", | |
| 374 | + "integrity": "sha512-WJcvgHm/6Q7dvGT0YFv/6PSkoc36QlR0VCESS6x9tGsnF1lWLmmYxOgX3HH6v8fo6AvSLgpcs+H0Olre6MKXlg==", | |
| 375 | + "license": "MIT", | |
| 376 | + "dependencies": { | |
| 377 | + "@codemirror/state": "^6.5.0", | |
| 378 | + "crelt": "^1.0.6", | |
| 379 | + "style-mod": "^4.1.0", | |
| 380 | + "w3c-keyname": "^2.2.4" | |
| 381 | + } | |
| 382 | + }, | |
| 383 | + "node_modules/@emnapi/core": { | |
| 384 | + "version": "1.8.1", | |
| 385 | + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", | |
| 386 | + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", | |
| 387 | + "dev": true, | |
| 388 | + "license": "MIT", | |
| 389 | + "optional": true, | |
| 390 | + "dependencies": { | |
| 391 | + "@emnapi/wasi-threads": "1.1.0", | |
| 392 | + "tslib": "^2.4.0" | |
| 393 | + } | |
| 394 | + }, | |
| 395 | + "node_modules/@emnapi/runtime": { | |
| 396 | + "version": "1.8.1", | |
| 397 | + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", | |
| 398 | + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", | |
| 399 | + "license": "MIT", | |
| 400 | + "optional": true, | |
| 401 | + "dependencies": { | |
| 402 | + "tslib": "^2.4.0" | |
| 403 | + } | |
| 404 | + }, | |
| 405 | + "node_modules/@emnapi/wasi-threads": { | |
| 406 | + "version": "1.1.0", | |
| 407 | + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", | |
| 408 | + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", | |
| 409 | + "dev": true, | |
| 410 | + "license": "MIT", | |
| 411 | + "optional": true, | |
| 412 | + "dependencies": { | |
| 413 | + "tslib": "^2.4.0" | |
| 414 | + } | |
| 415 | + }, | |
| 416 | + "node_modules/@esbuild/aix-ppc64": { | |
| 417 | + "version": "0.27.3", | |
| 418 | + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", | |
| 419 | + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", | |
| 420 | + "cpu": [ | |
| 421 | + "ppc64" | |
| 422 | + ], | |
| 423 | + "dev": true, | |
| 424 | + "license": "MIT", | |
| 425 | + "optional": true, | |
| 426 | + "os": [ | |
| 427 | + "aix" | |
| 428 | + ], | |
| 429 | + "engines": { | |
| 430 | + "node": ">=18" | |
| 431 | + } | |
| 432 | + }, | |
| 433 | + "node_modules/@esbuild/android-arm": { | |
| 434 | + "version": "0.27.3", | |
| 435 | + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", | |
| 436 | + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", | |
| 437 | + "cpu": [ | |
| 438 | + "arm" | |
| 439 | + ], | |
| 440 | + "dev": true, | |
| 441 | + "license": "MIT", | |
| 442 | + "optional": true, | |
| 443 | + "os": [ | |
| 444 | + "android" | |
| 445 | + ], | |
| 446 | + "engines": { | |
| 447 | + "node": ">=18" | |
| 448 | + } | |
| 449 | + }, | |
| 450 | + "node_modules/@esbuild/android-arm64": { | |
| 451 | + "version": "0.27.3", | |
| 452 | + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", | |
| 453 | + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", | |
| 454 | + "cpu": [ | |
| 455 | + "arm64" | |
| 456 | + ], | |
| 457 | + "dev": true, | |
| 458 | + "license": "MIT", | |
| 459 | + "optional": true, | |
| 460 | + "os": [ | |
| 461 | + "android" | |
| 462 | + ], | |
| 463 | + "engines": { | |
| 464 | + "node": ">=18" | |
| 465 | + } | |
| 466 | + }, | |
| 467 | + "node_modules/@esbuild/android-x64": { | |
| 468 | + "version": "0.27.3", | |
| 469 | + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", | |
| 470 | + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", | |
| 471 | + "cpu": [ | |
| 472 | + "x64" | |
| 473 | + ], | |
| 474 | + "dev": true, | |
| 475 | + "license": "MIT", | |
| 476 | + "optional": true, | |
| 477 | + "os": [ | |
| 478 | + "android" | |
| 479 | + ], | |
| 480 | + "engines": { | |
| 481 | + "node": ">=18" | |
| 482 | + } | |
| 483 | + }, | |
| 484 | + "node_modules/@esbuild/darwin-arm64": { | |
| 485 | + "version": "0.27.3", | |
| 486 | + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", | |
| 487 | + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", | |
| 488 | + "cpu": [ | |
| 489 | + "arm64" | |
| 490 | + ], | |
| 491 | + "dev": true, | |
| 492 | + "license": "MIT", | |
| 493 | + "optional": true, | |
| 494 | + "os": [ | |
| 495 | + "darwin" | |
| 496 | + ], | |
| 497 | + "engines": { | |
| 498 | + "node": ">=18" | |
| 499 | + } | |
| 500 | + }, | |
| 501 | + "node_modules/@esbuild/darwin-x64": { | |
| 502 | + "version": "0.27.3", | |
| 503 | + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", | |
| 504 | + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", | |
| 505 | + "cpu": [ | |
| 506 | + "x64" | |
| 507 | + ], | |
| 508 | + "dev": true, | |
| 509 | + "license": "MIT", | |
| 510 | + "optional": true, | |
| 511 | + "os": [ | |
| 512 | + "darwin" | |
| 513 | + ], | |
| 514 | + "engines": { | |
| 515 | + "node": ">=18" | |
| 516 | + } | |
| 517 | + }, | |
| 518 | + "node_modules/@esbuild/freebsd-arm64": { | |
| 519 | + "version": "0.27.3", | |
| 520 | + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", | |
| 521 | + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", | |
| 522 | + "cpu": [ | |
| 523 | + "arm64" | |
| 524 | + ], | |
| 525 | + "dev": true, | |
| 526 | + "license": "MIT", | |
| 527 | + "optional": true, | |
| 528 | + "os": [ | |
| 529 | + "freebsd" | |
| 530 | + ], | |
| 531 | + "engines": { | |
| 532 | + "node": ">=18" | |
| 533 | + } | |
| 534 | + }, | |
| 535 | + "node_modules/@esbuild/freebsd-x64": { | |
| 536 | + "version": "0.27.3", | |
| 537 | + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", | |
| 538 | + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", | |
| 539 | + "cpu": [ | |
| 540 | + "x64" | |
| 541 | + ], | |
| 542 | + "dev": true, | |
| 543 | + "license": "MIT", | |
| 544 | + "optional": true, | |
| 545 | + "os": [ | |
| 546 | + "freebsd" | |
| 547 | + ], | |
| 548 | + "engines": { | |
| 549 | + "node": ">=18" | |
| 550 | + } | |
| 551 | + }, | |
| 552 | + "node_modules/@esbuild/linux-arm": { | |
| 553 | + "version": "0.27.3", | |
| 554 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", | |
| 555 | + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", | |
| 556 | + "cpu": [ | |
| 557 | + "arm" | |
| 558 | + ], | |
| 559 | + "dev": true, | |
| 560 | + "license": "MIT", | |
| 561 | + "optional": true, | |
| 562 | + "os": [ | |
| 563 | + "linux" | |
| 564 | + ], | |
| 565 | + "engines": { | |
| 566 | + "node": ">=18" | |
| 567 | + } | |
| 568 | + }, | |
| 569 | + "node_modules/@esbuild/linux-arm64": { | |
| 570 | + "version": "0.27.3", | |
| 571 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", | |
| 572 | + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", | |
| 573 | + "cpu": [ | |
| 574 | + "arm64" | |
| 575 | + ], | |
| 576 | + "dev": true, | |
| 577 | + "license": "MIT", | |
| 578 | + "optional": true, | |
| 579 | + "os": [ | |
| 580 | + "linux" | |
| 581 | + ], | |
| 582 | + "engines": { | |
| 583 | + "node": ">=18" | |
| 584 | + } | |
| 585 | + }, | |
| 586 | + "node_modules/@esbuild/linux-ia32": { | |
| 587 | + "version": "0.27.3", | |
| 588 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", | |
| 589 | + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", | |
| 590 | + "cpu": [ | |
| 591 | + "ia32" | |
| 592 | + ], | |
| 593 | + "dev": true, | |
| 594 | + "license": "MIT", | |
| 595 | + "optional": true, | |
| 596 | + "os": [ | |
| 597 | + "linux" | |
| 598 | + ], | |
| 599 | + "engines": { | |
| 600 | + "node": ">=18" | |
| 601 | + } | |
| 602 | + }, | |
| 603 | + "node_modules/@esbuild/linux-loong64": { | |
| 604 | + "version": "0.27.3", | |
| 605 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", | |
| 606 | + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", | |
| 607 | + "cpu": [ | |
| 608 | + "loong64" | |
| 609 | + ], | |
| 610 | + "dev": true, | |
| 611 | + "license": "MIT", | |
| 612 | + "optional": true, | |
| 613 | + "os": [ | |
| 614 | + "linux" | |
| 615 | + ], | |
| 616 | + "engines": { | |
| 617 | + "node": ">=18" | |
| 618 | + } | |
| 619 | + }, | |
| 620 | + "node_modules/@esbuild/linux-mips64el": { | |
| 621 | + "version": "0.27.3", | |
| 622 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", | |
| 623 | + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", | |
| 624 | + "cpu": [ | |
| 625 | + "mips64el" | |
| 626 | + ], | |
| 627 | + "dev": true, | |
| 628 | + "license": "MIT", | |
| 629 | + "optional": true, | |
| 630 | + "os": [ | |
| 631 | + "linux" | |
| 632 | + ], | |
| 633 | + "engines": { | |
| 634 | + "node": ">=18" | |
| 635 | + } | |
| 636 | + }, | |
| 637 | + "node_modules/@esbuild/linux-ppc64": { | |
| 638 | + "version": "0.27.3", | |
| 639 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", | |
| 640 | + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", | |
| 641 | + "cpu": [ | |
| 642 | + "ppc64" | |
| 643 | + ], | |
| 644 | + "dev": true, | |
| 645 | + "license": "MIT", | |
| 646 | + "optional": true, | |
| 647 | + "os": [ | |
| 648 | + "linux" | |
| 649 | + ], | |
| 650 | + "engines": { | |
| 651 | + "node": ">=18" | |
| 652 | + } | |
| 653 | + }, | |
| 654 | + "node_modules/@esbuild/linux-riscv64": { | |
| 655 | + "version": "0.27.3", | |
| 656 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", | |
| 657 | + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", | |
| 658 | + "cpu": [ | |
| 659 | + "riscv64" | |
| 660 | + ], | |
| 661 | + "dev": true, | |
| 662 | + "license": "MIT", | |
| 663 | + "optional": true, | |
| 664 | + "os": [ | |
| 665 | + "linux" | |
| 666 | + ], | |
| 667 | + "engines": { | |
| 668 | + "node": ">=18" | |
| 669 | + } | |
| 670 | + }, | |
| 671 | + "node_modules/@esbuild/linux-s390x": { | |
| 672 | + "version": "0.27.3", | |
| 673 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", | |
| 674 | + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", | |
| 675 | + "cpu": [ | |
| 676 | + "s390x" | |
| 677 | + ], | |
| 678 | + "dev": true, | |
| 679 | + "license": "MIT", | |
| 680 | + "optional": true, | |
| 681 | + "os": [ | |
| 682 | + "linux" | |
| 683 | + ], | |
| 684 | + "engines": { | |
| 685 | + "node": ">=18" | |
| 686 | + } | |
| 687 | + }, | |
| 688 | + "node_modules/@esbuild/linux-x64": { | |
| 689 | + "version": "0.27.3", | |
| 690 | + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", | |
| 691 | + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", | |
| 692 | + "cpu": [ | |
| 693 | + "x64" | |
| 694 | + ], | |
| 695 | + "dev": true, | |
| 696 | + "license": "MIT", | |
| 697 | + "optional": true, | |
| 698 | + "os": [ | |
| 699 | + "linux" | |
| 700 | + ], | |
| 701 | + "engines": { | |
| 702 | + "node": ">=18" | |
| 703 | + } | |
| 704 | + }, | |
| 705 | + "node_modules/@esbuild/netbsd-arm64": { | |
| 706 | + "version": "0.27.3", | |
| 707 | + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", | |
| 708 | + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", | |
| 709 | + "cpu": [ | |
| 710 | + "arm64" | |
| 711 | + ], | |
| 712 | + "dev": true, | |
| 713 | + "license": "MIT", | |
| 714 | + "optional": true, | |
| 715 | + "os": [ | |
| 716 | + "netbsd" | |
| 717 | + ], | |
| 718 | + "engines": { | |
| 719 | + "node": ">=18" | |
| 720 | + } | |
| 721 | + }, | |
| 722 | + "node_modules/@esbuild/netbsd-x64": { | |
| 723 | + "version": "0.27.3", | |
| 724 | + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", | |
| 725 | + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", | |
| 726 | + "cpu": [ | |
| 727 | + "x64" | |
| 728 | + ], | |
| 729 | + "dev": true, | |
| 730 | + "license": "MIT", | |
| 731 | + "optional": true, | |
| 732 | + "os": [ | |
| 733 | + "netbsd" | |
| 734 | + ], | |
| 735 | + "engines": { | |
| 736 | + "node": ">=18" | |
| 737 | + } | |
| 738 | + }, | |
| 739 | + "node_modules/@esbuild/openbsd-arm64": { | |
| 740 | + "version": "0.27.3", | |
| 741 | + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", | |
| 742 | + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", | |
| 743 | + "cpu": [ | |
| 744 | + "arm64" | |
| 745 | + ], | |
| 746 | + "dev": true, | |
| 747 | + "license": "MIT", | |
| 748 | + "optional": true, | |
| 749 | + "os": [ | |
| 750 | + "openbsd" | |
| 751 | + ], | |
| 752 | + "engines": { | |
| 753 | + "node": ">=18" | |
| 754 | + } | |
| 755 | + }, | |
| 756 | + "node_modules/@esbuild/openbsd-x64": { | |
| 757 | + "version": "0.27.3", | |
| 758 | + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", | |
| 759 | + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", | |
| 760 | + "cpu": [ | |
| 761 | + "x64" | |
| 762 | + ], | |
| 763 | + "dev": true, | |
| 764 | + "license": "MIT", | |
| 765 | + "optional": true, | |
| 766 | + "os": [ | |
| 767 | + "openbsd" | |
| 768 | + ], | |
| 769 | + "engines": { | |
| 770 | + "node": ">=18" | |
| 771 | + } | |
| 772 | + }, | |
| 773 | + "node_modules/@esbuild/openharmony-arm64": { | |
| 774 | + "version": "0.27.3", | |
| 775 | + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", | |
| 776 | + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", | |
| 777 | + "cpu": [ | |
| 778 | + "arm64" | |
| 779 | + ], | |
| 780 | + "dev": true, | |
| 781 | + "license": "MIT", | |
| 782 | + "optional": true, | |
| 783 | + "os": [ | |
| 784 | + "openharmony" | |
| 785 | + ], | |
| 786 | + "engines": { | |
| 787 | + "node": ">=18" | |
| 788 | + } | |
| 789 | + }, | |
| 790 | + "node_modules/@esbuild/sunos-x64": { | |
| 791 | + "version": "0.27.3", | |
| 792 | + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", | |
| 793 | + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", | |
| 794 | + "cpu": [ | |
| 795 | + "x64" | |
| 796 | + ], | |
| 797 | + "dev": true, | |
| 798 | + "license": "MIT", | |
| 799 | + "optional": true, | |
| 800 | + "os": [ | |
| 801 | + "sunos" | |
| 802 | + ], | |
| 803 | + "engines": { | |
| 804 | + "node": ">=18" | |
| 805 | + } | |
| 806 | + }, | |
| 807 | + "node_modules/@esbuild/win32-arm64": { | |
| 808 | + "version": "0.27.3", | |
| 809 | + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", | |
| 810 | + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", | |
| 811 | + "cpu": [ | |
| 812 | + "arm64" | |
| 813 | + ], | |
| 814 | + "dev": true, | |
| 815 | + "license": "MIT", | |
| 816 | + "optional": true, | |
| 817 | + "os": [ | |
| 818 | + "win32" | |
| 819 | + ], | |
| 820 | + "engines": { | |
| 821 | + "node": ">=18" | |
| 822 | + } | |
| 823 | + }, | |
| 824 | + "node_modules/@esbuild/win32-ia32": { | |
| 825 | + "version": "0.27.3", | |
| 826 | + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", | |
| 827 | + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", | |
| 828 | + "cpu": [ | |
| 829 | + "ia32" | |
| 830 | + ], | |
| 831 | + "dev": true, | |
| 832 | + "license": "MIT", | |
| 833 | + "optional": true, | |
| 834 | + "os": [ | |
| 835 | + "win32" | |
| 836 | + ], | |
| 837 | + "engines": { | |
| 838 | + "node": ">=18" | |
| 839 | + } | |
| 840 | + }, | |
| 841 | + "node_modules/@esbuild/win32-x64": { | |
| 842 | + "version": "0.27.3", | |
| 843 | + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", | |
| 844 | + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", | |
| 845 | + "cpu": [ | |
| 846 | + "x64" | |
| 847 | + ], | |
| 848 | + "dev": true, | |
| 849 | + "license": "MIT", | |
| 850 | + "optional": true, | |
| 851 | + "os": [ | |
| 852 | + "win32" | |
| 853 | + ], | |
| 854 | + "engines": { | |
| 855 | + "node": ">=18" | |
| 856 | + } | |
| 857 | + }, | |
| 858 | + "node_modules/@eslint-community/eslint-utils": { | |
| 859 | + "version": "4.9.1", | |
| 860 | + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", | |
| 861 | + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", | |
| 862 | + "dev": true, | |
| 863 | + "license": "MIT", | |
| 864 | + "dependencies": { | |
| 865 | + "eslint-visitor-keys": "^3.4.3" | |
| 866 | + }, | |
| 867 | + "engines": { | |
| 868 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 869 | + }, | |
| 870 | + "funding": { | |
| 871 | + "url": "https://opencollective.com/eslint" | |
| 872 | + }, | |
| 873 | + "peerDependencies": { | |
| 874 | + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" | |
| 875 | + } | |
| 876 | + }, | |
| 877 | + "node_modules/@eslint-community/regexpp": { | |
| 878 | + "version": "4.12.2", | |
| 879 | + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", | |
| 880 | + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", | |
| 881 | + "dev": true, | |
| 882 | + "license": "MIT", | |
| 883 | + "engines": { | |
| 884 | + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" | |
| 885 | + } | |
| 886 | + }, | |
| 887 | + "node_modules/@eslint/eslintrc": { | |
| 888 | + "version": "2.1.4", | |
| 889 | + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", | |
| 890 | + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", | |
| 891 | + "dev": true, | |
| 892 | + "license": "MIT", | |
| 893 | + "dependencies": { | |
| 894 | + "ajv": "^6.12.4", | |
| 895 | + "debug": "^4.3.2", | |
| 896 | + "espree": "^9.6.0", | |
| 897 | + "globals": "^13.19.0", | |
| 898 | + "ignore": "^5.2.0", | |
| 899 | + "import-fresh": "^3.2.1", | |
| 900 | + "js-yaml": "^4.1.0", | |
| 901 | + "minimatch": "^3.1.2", | |
| 902 | + "strip-json-comments": "^3.1.1" | |
| 903 | + }, | |
| 904 | + "engines": { | |
| 905 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 906 | + }, | |
| 907 | + "funding": { | |
| 908 | + "url": "https://opencollective.com/eslint" | |
| 909 | + } | |
| 910 | + }, | |
| 911 | + "node_modules/@eslint/js": { | |
| 912 | + "version": "8.57.1", | |
| 913 | + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", | |
| 914 | + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", | |
| 915 | + "dev": true, | |
| 916 | + "license": "MIT", | |
| 917 | + "engines": { | |
| 918 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 919 | + } | |
| 920 | + }, | |
| 921 | + "node_modules/@humanwhocodes/config-array": { | |
| 922 | + "version": "0.13.0", | |
| 923 | + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", | |
| 924 | + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", | |
| 925 | + "deprecated": "Use @eslint/config-array instead", | |
| 926 | + "dev": true, | |
| 927 | + "license": "Apache-2.0", | |
| 928 | + "dependencies": { | |
| 929 | + "@humanwhocodes/object-schema": "^2.0.3", | |
| 930 | + "debug": "^4.3.1", | |
| 931 | + "minimatch": "^3.0.5" | |
| 932 | + }, | |
| 933 | + "engines": { | |
| 934 | + "node": ">=10.10.0" | |
| 935 | + } | |
| 936 | + }, | |
| 937 | + "node_modules/@humanwhocodes/module-importer": { | |
| 938 | + "version": "1.0.1", | |
| 939 | + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", | |
| 940 | + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", | |
| 941 | + "dev": true, | |
| 942 | + "license": "Apache-2.0", | |
| 943 | + "engines": { | |
| 944 | + "node": ">=12.22" | |
| 945 | + }, | |
| 946 | + "funding": { | |
| 947 | + "type": "github", | |
| 948 | + "url": "https://github.com/sponsors/nzakas" | |
| 949 | + } | |
| 950 | + }, | |
| 951 | + "node_modules/@humanwhocodes/object-schema": { | |
| 952 | + "version": "2.0.3", | |
| 953 | + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", | |
| 954 | + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", | |
| 955 | + "deprecated": "Use @eslint/object-schema instead", | |
| 956 | + "dev": true, | |
| 957 | + "license": "BSD-3-Clause" | |
| 958 | + }, | |
| 959 | + "node_modules/@img/colour": { | |
| 960 | + "version": "1.0.0", | |
| 961 | + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", | |
| 962 | + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", | |
| 963 | + "license": "MIT", | |
| 964 | + "engines": { | |
| 965 | + "node": ">=18" | |
| 966 | + } | |
| 967 | + }, | |
| 968 | + "node_modules/@img/sharp-darwin-arm64": { | |
| 969 | + "version": "0.34.5", | |
| 970 | + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", | |
| 971 | + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", | |
| 972 | + "cpu": [ | |
| 973 | + "arm64" | |
| 974 | + ], | |
| 975 | + "license": "Apache-2.0", | |
| 976 | + "optional": true, | |
| 977 | + "os": [ | |
| 978 | + "darwin" | |
| 979 | + ], | |
| 980 | + "engines": { | |
| 981 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 982 | + }, | |
| 983 | + "funding": { | |
| 984 | + "url": "https://opencollective.com/libvips" | |
| 985 | + }, | |
| 986 | + "optionalDependencies": { | |
| 987 | + "@img/sharp-libvips-darwin-arm64": "1.2.4" | |
| 988 | + } | |
| 989 | + }, | |
| 990 | + "node_modules/@img/sharp-darwin-x64": { | |
| 991 | + "version": "0.34.5", | |
| 992 | + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", | |
| 993 | + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", | |
| 994 | + "cpu": [ | |
| 995 | + "x64" | |
| 996 | + ], | |
| 997 | + "license": "Apache-2.0", | |
| 998 | + "optional": true, | |
| 999 | + "os": [ | |
| 1000 | + "darwin" | |
| 1001 | + ], | |
| 1002 | + "engines": { | |
| 1003 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1004 | + }, | |
| 1005 | + "funding": { | |
| 1006 | + "url": "https://opencollective.com/libvips" | |
| 1007 | + }, | |
| 1008 | + "optionalDependencies": { | |
| 1009 | + "@img/sharp-libvips-darwin-x64": "1.2.4" | |
| 1010 | + } | |
| 1011 | + }, | |
| 1012 | + "node_modules/@img/sharp-libvips-darwin-arm64": { | |
| 1013 | + "version": "1.2.4", | |
| 1014 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", | |
| 1015 | + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", | |
| 1016 | + "cpu": [ | |
| 1017 | + "arm64" | |
| 1018 | + ], | |
| 1019 | + "license": "LGPL-3.0-or-later", | |
| 1020 | + "optional": true, | |
| 1021 | + "os": [ | |
| 1022 | + "darwin" | |
| 1023 | + ], | |
| 1024 | + "funding": { | |
| 1025 | + "url": "https://opencollective.com/libvips" | |
| 1026 | + } | |
| 1027 | + }, | |
| 1028 | + "node_modules/@img/sharp-libvips-darwin-x64": { | |
| 1029 | + "version": "1.2.4", | |
| 1030 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", | |
| 1031 | + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", | |
| 1032 | + "cpu": [ | |
| 1033 | + "x64" | |
| 1034 | + ], | |
| 1035 | + "license": "LGPL-3.0-or-later", | |
| 1036 | + "optional": true, | |
| 1037 | + "os": [ | |
| 1038 | + "darwin" | |
| 1039 | + ], | |
| 1040 | + "funding": { | |
| 1041 | + "url": "https://opencollective.com/libvips" | |
| 1042 | + } | |
| 1043 | + }, | |
| 1044 | + "node_modules/@img/sharp-libvips-linux-arm": { | |
| 1045 | + "version": "1.2.4", | |
| 1046 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", | |
| 1047 | + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", | |
| 1048 | + "cpu": [ | |
| 1049 | + "arm" | |
| 1050 | + ], | |
| 1051 | + "license": "LGPL-3.0-or-later", | |
| 1052 | + "optional": true, | |
| 1053 | + "os": [ | |
| 1054 | + "linux" | |
| 1055 | + ], | |
| 1056 | + "funding": { | |
| 1057 | + "url": "https://opencollective.com/libvips" | |
| 1058 | + } | |
| 1059 | + }, | |
| 1060 | + "node_modules/@img/sharp-libvips-linux-arm64": { | |
| 1061 | + "version": "1.2.4", | |
| 1062 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", | |
| 1063 | + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", | |
| 1064 | + "cpu": [ | |
| 1065 | + "arm64" | |
| 1066 | + ], | |
| 1067 | + "license": "LGPL-3.0-or-later", | |
| 1068 | + "optional": true, | |
| 1069 | + "os": [ | |
| 1070 | + "linux" | |
| 1071 | + ], | |
| 1072 | + "funding": { | |
| 1073 | + "url": "https://opencollective.com/libvips" | |
| 1074 | + } | |
| 1075 | + }, | |
| 1076 | + "node_modules/@img/sharp-libvips-linux-ppc64": { | |
| 1077 | + "version": "1.2.4", | |
| 1078 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", | |
| 1079 | + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", | |
| 1080 | + "cpu": [ | |
| 1081 | + "ppc64" | |
| 1082 | + ], | |
| 1083 | + "license": "LGPL-3.0-or-later", | |
| 1084 | + "optional": true, | |
| 1085 | + "os": [ | |
| 1086 | + "linux" | |
| 1087 | + ], | |
| 1088 | + "funding": { | |
| 1089 | + "url": "https://opencollective.com/libvips" | |
| 1090 | + } | |
| 1091 | + }, | |
| 1092 | + "node_modules/@img/sharp-libvips-linux-riscv64": { | |
| 1093 | + "version": "1.2.4", | |
| 1094 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", | |
| 1095 | + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", | |
| 1096 | + "cpu": [ | |
| 1097 | + "riscv64" | |
| 1098 | + ], | |
| 1099 | + "license": "LGPL-3.0-or-later", | |
| 1100 | + "optional": true, | |
| 1101 | + "os": [ | |
| 1102 | + "linux" | |
| 1103 | + ], | |
| 1104 | + "funding": { | |
| 1105 | + "url": "https://opencollective.com/libvips" | |
| 1106 | + } | |
| 1107 | + }, | |
| 1108 | + "node_modules/@img/sharp-libvips-linux-s390x": { | |
| 1109 | + "version": "1.2.4", | |
| 1110 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", | |
| 1111 | + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", | |
| 1112 | + "cpu": [ | |
| 1113 | + "s390x" | |
| 1114 | + ], | |
| 1115 | + "license": "LGPL-3.0-or-later", | |
| 1116 | + "optional": true, | |
| 1117 | + "os": [ | |
| 1118 | + "linux" | |
| 1119 | + ], | |
| 1120 | + "funding": { | |
| 1121 | + "url": "https://opencollective.com/libvips" | |
| 1122 | + } | |
| 1123 | + }, | |
| 1124 | + "node_modules/@img/sharp-libvips-linux-x64": { | |
| 1125 | + "version": "1.2.4", | |
| 1126 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", | |
| 1127 | + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", | |
| 1128 | + "cpu": [ | |
| 1129 | + "x64" | |
| 1130 | + ], | |
| 1131 | + "license": "LGPL-3.0-or-later", | |
| 1132 | + "optional": true, | |
| 1133 | + "os": [ | |
| 1134 | + "linux" | |
| 1135 | + ], | |
| 1136 | + "funding": { | |
| 1137 | + "url": "https://opencollective.com/libvips" | |
| 1138 | + } | |
| 1139 | + }, | |
| 1140 | + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { | |
| 1141 | + "version": "1.2.4", | |
| 1142 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", | |
| 1143 | + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", | |
| 1144 | + "cpu": [ | |
| 1145 | + "arm64" | |
| 1146 | + ], | |
| 1147 | + "license": "LGPL-3.0-or-later", | |
| 1148 | + "optional": true, | |
| 1149 | + "os": [ | |
| 1150 | + "linux" | |
| 1151 | + ], | |
| 1152 | + "funding": { | |
| 1153 | + "url": "https://opencollective.com/libvips" | |
| 1154 | + } | |
| 1155 | + }, | |
| 1156 | + "node_modules/@img/sharp-libvips-linuxmusl-x64": { | |
| 1157 | + "version": "1.2.4", | |
| 1158 | + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", | |
| 1159 | + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", | |
| 1160 | + "cpu": [ | |
| 1161 | + "x64" | |
| 1162 | + ], | |
| 1163 | + "license": "LGPL-3.0-or-later", | |
| 1164 | + "optional": true, | |
| 1165 | + "os": [ | |
| 1166 | + "linux" | |
| 1167 | + ], | |
| 1168 | + "funding": { | |
| 1169 | + "url": "https://opencollective.com/libvips" | |
| 1170 | + } | |
| 1171 | + }, | |
| 1172 | + "node_modules/@img/sharp-linux-arm": { | |
| 1173 | + "version": "0.34.5", | |
| 1174 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", | |
| 1175 | + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", | |
| 1176 | + "cpu": [ | |
| 1177 | + "arm" | |
| 1178 | + ], | |
| 1179 | + "license": "Apache-2.0", | |
| 1180 | + "optional": true, | |
| 1181 | + "os": [ | |
| 1182 | + "linux" | |
| 1183 | + ], | |
| 1184 | + "engines": { | |
| 1185 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1186 | + }, | |
| 1187 | + "funding": { | |
| 1188 | + "url": "https://opencollective.com/libvips" | |
| 1189 | + }, | |
| 1190 | + "optionalDependencies": { | |
| 1191 | + "@img/sharp-libvips-linux-arm": "1.2.4" | |
| 1192 | + } | |
| 1193 | + }, | |
| 1194 | + "node_modules/@img/sharp-linux-arm64": { | |
| 1195 | + "version": "0.34.5", | |
| 1196 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", | |
| 1197 | + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", | |
| 1198 | + "cpu": [ | |
| 1199 | + "arm64" | |
| 1200 | + ], | |
| 1201 | + "license": "Apache-2.0", | |
| 1202 | + "optional": true, | |
| 1203 | + "os": [ | |
| 1204 | + "linux" | |
| 1205 | + ], | |
| 1206 | + "engines": { | |
| 1207 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1208 | + }, | |
| 1209 | + "funding": { | |
| 1210 | + "url": "https://opencollective.com/libvips" | |
| 1211 | + }, | |
| 1212 | + "optionalDependencies": { | |
| 1213 | + "@img/sharp-libvips-linux-arm64": "1.2.4" | |
| 1214 | + } | |
| 1215 | + }, | |
| 1216 | + "node_modules/@img/sharp-linux-ppc64": { | |
| 1217 | + "version": "0.34.5", | |
| 1218 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", | |
| 1219 | + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", | |
| 1220 | + "cpu": [ | |
| 1221 | + "ppc64" | |
| 1222 | + ], | |
| 1223 | + "license": "Apache-2.0", | |
| 1224 | + "optional": true, | |
| 1225 | + "os": [ | |
| 1226 | + "linux" | |
| 1227 | + ], | |
| 1228 | + "engines": { | |
| 1229 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1230 | + }, | |
| 1231 | + "funding": { | |
| 1232 | + "url": "https://opencollective.com/libvips" | |
| 1233 | + }, | |
| 1234 | + "optionalDependencies": { | |
| 1235 | + "@img/sharp-libvips-linux-ppc64": "1.2.4" | |
| 1236 | + } | |
| 1237 | + }, | |
| 1238 | + "node_modules/@img/sharp-linux-riscv64": { | |
| 1239 | + "version": "0.34.5", | |
| 1240 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", | |
| 1241 | + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", | |
| 1242 | + "cpu": [ | |
| 1243 | + "riscv64" | |
| 1244 | + ], | |
| 1245 | + "license": "Apache-2.0", | |
| 1246 | + "optional": true, | |
| 1247 | + "os": [ | |
| 1248 | + "linux" | |
| 1249 | + ], | |
| 1250 | + "engines": { | |
| 1251 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1252 | + }, | |
| 1253 | + "funding": { | |
| 1254 | + "url": "https://opencollective.com/libvips" | |
| 1255 | + }, | |
| 1256 | + "optionalDependencies": { | |
| 1257 | + "@img/sharp-libvips-linux-riscv64": "1.2.4" | |
| 1258 | + } | |
| 1259 | + }, | |
| 1260 | + "node_modules/@img/sharp-linux-s390x": { | |
| 1261 | + "version": "0.34.5", | |
| 1262 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", | |
| 1263 | + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", | |
| 1264 | + "cpu": [ | |
| 1265 | + "s390x" | |
| 1266 | + ], | |
| 1267 | + "license": "Apache-2.0", | |
| 1268 | + "optional": true, | |
| 1269 | + "os": [ | |
| 1270 | + "linux" | |
| 1271 | + ], | |
| 1272 | + "engines": { | |
| 1273 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1274 | + }, | |
| 1275 | + "funding": { | |
| 1276 | + "url": "https://opencollective.com/libvips" | |
| 1277 | + }, | |
| 1278 | + "optionalDependencies": { | |
| 1279 | + "@img/sharp-libvips-linux-s390x": "1.2.4" | |
| 1280 | + } | |
| 1281 | + }, | |
| 1282 | + "node_modules/@img/sharp-linux-x64": { | |
| 1283 | + "version": "0.34.5", | |
| 1284 | + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", | |
| 1285 | + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", | |
| 1286 | + "cpu": [ | |
| 1287 | + "x64" | |
| 1288 | + ], | |
| 1289 | + "license": "Apache-2.0", | |
| 1290 | + "optional": true, | |
| 1291 | + "os": [ | |
| 1292 | + "linux" | |
| 1293 | + ], | |
| 1294 | + "engines": { | |
| 1295 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1296 | + }, | |
| 1297 | + "funding": { | |
| 1298 | + "url": "https://opencollective.com/libvips" | |
| 1299 | + }, | |
| 1300 | + "optionalDependencies": { | |
| 1301 | + "@img/sharp-libvips-linux-x64": "1.2.4" | |
| 1302 | + } | |
| 1303 | + }, | |
| 1304 | + "node_modules/@img/sharp-linuxmusl-arm64": { | |
| 1305 | + "version": "0.34.5", | |
| 1306 | + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", | |
| 1307 | + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", | |
| 1308 | + "cpu": [ | |
| 1309 | + "arm64" | |
| 1310 | + ], | |
| 1311 | + "license": "Apache-2.0", | |
| 1312 | + "optional": true, | |
| 1313 | + "os": [ | |
| 1314 | + "linux" | |
| 1315 | + ], | |
| 1316 | + "engines": { | |
| 1317 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1318 | + }, | |
| 1319 | + "funding": { | |
| 1320 | + "url": "https://opencollective.com/libvips" | |
| 1321 | + }, | |
| 1322 | + "optionalDependencies": { | |
| 1323 | + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" | |
| 1324 | + } | |
| 1325 | + }, | |
| 1326 | + "node_modules/@img/sharp-linuxmusl-x64": { | |
| 1327 | + "version": "0.34.5", | |
| 1328 | + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", | |
| 1329 | + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", | |
| 1330 | + "cpu": [ | |
| 1331 | + "x64" | |
| 1332 | + ], | |
| 1333 | + "license": "Apache-2.0", | |
| 1334 | + "optional": true, | |
| 1335 | + "os": [ | |
| 1336 | + "linux" | |
| 1337 | + ], | |
| 1338 | + "engines": { | |
| 1339 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1340 | + }, | |
| 1341 | + "funding": { | |
| 1342 | + "url": "https://opencollective.com/libvips" | |
| 1343 | + }, | |
| 1344 | + "optionalDependencies": { | |
| 1345 | + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" | |
| 1346 | + } | |
| 1347 | + }, | |
| 1348 | + "node_modules/@img/sharp-wasm32": { | |
| 1349 | + "version": "0.34.5", | |
| 1350 | + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", | |
| 1351 | + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", | |
| 1352 | + "cpu": [ | |
| 1353 | + "wasm32" | |
| 1354 | + ], | |
| 1355 | + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", | |
| 1356 | + "optional": true, | |
| 1357 | + "dependencies": { | |
| 1358 | + "@emnapi/runtime": "^1.7.0" | |
| 1359 | + }, | |
| 1360 | + "engines": { | |
| 1361 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1362 | + }, | |
| 1363 | + "funding": { | |
| 1364 | + "url": "https://opencollective.com/libvips" | |
| 1365 | + } | |
| 1366 | + }, | |
| 1367 | + "node_modules/@img/sharp-win32-arm64": { | |
| 1368 | + "version": "0.34.5", | |
| 1369 | + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", | |
| 1370 | + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", | |
| 1371 | + "cpu": [ | |
| 1372 | + "arm64" | |
| 1373 | + ], | |
| 1374 | + "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 1375 | + "optional": true, | |
| 1376 | + "os": [ | |
| 1377 | + "win32" | |
| 1378 | + ], | |
| 1379 | + "engines": { | |
| 1380 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1381 | + }, | |
| 1382 | + "funding": { | |
| 1383 | + "url": "https://opencollective.com/libvips" | |
| 1384 | + } | |
| 1385 | + }, | |
| 1386 | + "node_modules/@img/sharp-win32-ia32": { | |
| 1387 | + "version": "0.34.5", | |
| 1388 | + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", | |
| 1389 | + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", | |
| 1390 | + "cpu": [ | |
| 1391 | + "ia32" | |
| 1392 | + ], | |
| 1393 | + "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 1394 | + "optional": true, | |
| 1395 | + "os": [ | |
| 1396 | + "win32" | |
| 1397 | + ], | |
| 1398 | + "engines": { | |
| 1399 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1400 | + }, | |
| 1401 | + "funding": { | |
| 1402 | + "url": "https://opencollective.com/libvips" | |
| 1403 | + } | |
| 1404 | + }, | |
| 1405 | + "node_modules/@img/sharp-win32-x64": { | |
| 1406 | + "version": "0.34.5", | |
| 1407 | + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", | |
| 1408 | + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", | |
| 1409 | + "cpu": [ | |
| 1410 | + "x64" | |
| 1411 | + ], | |
| 1412 | + "license": "Apache-2.0 AND LGPL-3.0-or-later", | |
| 1413 | + "optional": true, | |
| 1414 | + "os": [ | |
| 1415 | + "win32" | |
| 1416 | + ], | |
| 1417 | + "engines": { | |
| 1418 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 1419 | + }, | |
| 1420 | + "funding": { | |
| 1421 | + "url": "https://opencollective.com/libvips" | |
| 1422 | + } | |
| 1423 | + }, | |
| 1424 | + "node_modules/@isaacs/cliui": { | |
| 1425 | + "version": "8.0.2", | |
| 1426 | + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", | |
| 1427 | + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", | |
| 1428 | + "license": "ISC", | |
| 1429 | + "dependencies": { | |
| 1430 | + "string-width": "^5.1.2", | |
| 1431 | + "string-width-cjs": "npm:string-width@^4.2.0", | |
| 1432 | + "strip-ansi": "^7.0.1", | |
| 1433 | + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", | |
| 1434 | + "wrap-ansi": "^8.1.0", | |
| 1435 | + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" | |
| 1436 | + }, | |
| 1437 | + "engines": { | |
| 1438 | + "node": ">=12" | |
| 1439 | + } | |
| 1440 | + }, | |
| 1441 | + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { | |
| 1442 | + "version": "6.2.2", | |
| 1443 | + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", | |
| 1444 | + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", | |
| 1445 | + "license": "MIT", | |
| 1446 | + "engines": { | |
| 1447 | + "node": ">=12" | |
| 1448 | + }, | |
| 1449 | + "funding": { | |
| 1450 | + "url": "https://github.com/chalk/ansi-regex?sponsor=1" | |
| 1451 | + } | |
| 1452 | + }, | |
| 1453 | + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { | |
| 1454 | + "version": "7.1.2", | |
| 1455 | + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", | |
| 1456 | + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", | |
| 1457 | + "license": "MIT", | |
| 1458 | + "dependencies": { | |
| 1459 | + "ansi-regex": "^6.0.1" | |
| 1460 | + }, | |
| 1461 | + "engines": { | |
| 1462 | + "node": ">=12" | |
| 1463 | + }, | |
| 1464 | + "funding": { | |
| 1465 | + "url": "https://github.com/chalk/strip-ansi?sponsor=1" | |
| 1466 | + } | |
| 1467 | + }, | |
| 1468 | + "node_modules/@jridgewell/gen-mapping": { | |
| 1469 | + "version": "0.3.13", | |
| 1470 | + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", | |
| 1471 | + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", | |
| 1472 | + "dev": true, | |
| 1473 | + "license": "MIT", | |
| 1474 | + "dependencies": { | |
| 1475 | + "@jridgewell/sourcemap-codec": "^1.5.0", | |
| 1476 | + "@jridgewell/trace-mapping": "^0.3.24" | |
| 1477 | + } | |
| 1478 | + }, | |
| 1479 | + "node_modules/@jridgewell/resolve-uri": { | |
| 1480 | + "version": "3.1.2", | |
| 1481 | + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 1482 | + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 1483 | + "dev": true, | |
| 1484 | + "license": "MIT", | |
| 1485 | + "engines": { | |
| 1486 | + "node": ">=6.0.0" | |
| 1487 | + } | |
| 1488 | + }, | |
| 1489 | + "node_modules/@jridgewell/sourcemap-codec": { | |
| 1490 | + "version": "1.5.5", | |
| 1491 | + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 1492 | + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 1493 | + "dev": true, | |
| 1494 | + "license": "MIT" | |
| 1495 | + }, | |
| 1496 | + "node_modules/@jridgewell/trace-mapping": { | |
| 1497 | + "version": "0.3.31", | |
| 1498 | + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 1499 | + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 1500 | + "dev": true, | |
| 1501 | + "license": "MIT", | |
| 1502 | + "dependencies": { | |
| 1503 | + "@jridgewell/resolve-uri": "^3.1.0", | |
| 1504 | + "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 1505 | + } | |
| 1506 | + }, | |
| 1507 | + "node_modules/@lezer/common": { | |
| 1508 | + "version": "1.5.1", | |
| 1509 | + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.1.tgz", | |
| 1510 | + "integrity": "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==", | |
| 1511 | + "license": "MIT" | |
| 1512 | + }, | |
| 1513 | + "node_modules/@lezer/cpp": { | |
| 1514 | + "version": "1.1.5", | |
| 1515 | + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.5.tgz", | |
| 1516 | + "integrity": "sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==", | |
| 1517 | + "license": "MIT", | |
| 1518 | + "dependencies": { | |
| 1519 | + "@lezer/common": "^1.2.0", | |
| 1520 | + "@lezer/highlight": "^1.0.0", | |
| 1521 | + "@lezer/lr": "^1.0.0" | |
| 1522 | + } | |
| 1523 | + }, | |
| 1524 | + "node_modules/@lezer/css": { | |
| 1525 | + "version": "1.3.1", | |
| 1526 | + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.1.tgz", | |
| 1527 | + "integrity": "sha512-PYAKeUVBo3HFThruRyp/iK91SwiZJnzXh8QzkQlwijB5y+N5iB28+iLk78o2zmKqqV0uolNhCwFqB8LA7b0Svg==", | |
| 1528 | + "license": "MIT", | |
| 1529 | + "dependencies": { | |
| 1530 | + "@lezer/common": "^1.2.0", | |
| 1531 | + "@lezer/highlight": "^1.0.0", | |
| 1532 | + "@lezer/lr": "^1.3.0" | |
| 1533 | + } | |
| 1534 | + }, | |
| 1535 | + "node_modules/@lezer/go": { | |
| 1536 | + "version": "1.0.1", | |
| 1537 | + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", | |
| 1538 | + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", | |
| 1539 | + "license": "MIT", | |
| 1540 | + "dependencies": { | |
| 1541 | + "@lezer/common": "^1.2.0", | |
| 1542 | + "@lezer/highlight": "^1.0.0", | |
| 1543 | + "@lezer/lr": "^1.3.0" | |
| 1544 | + } | |
| 1545 | + }, | |
| 1546 | + "node_modules/@lezer/highlight": { | |
| 1547 | + "version": "1.2.3", | |
| 1548 | + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", | |
| 1549 | + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", | |
| 1550 | + "license": "MIT", | |
| 1551 | + "dependencies": { | |
| 1552 | + "@lezer/common": "^1.3.0" | |
| 1553 | + } | |
| 1554 | + }, | |
| 1555 | + "node_modules/@lezer/html": { | |
| 1556 | + "version": "1.3.13", | |
| 1557 | + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", | |
| 1558 | + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", | |
| 1559 | + "license": "MIT", | |
| 1560 | + "dependencies": { | |
| 1561 | + "@lezer/common": "^1.2.0", | |
| 1562 | + "@lezer/highlight": "^1.0.0", | |
| 1563 | + "@lezer/lr": "^1.0.0" | |
| 1564 | + } | |
| 1565 | + }, | |
| 1566 | + "node_modules/@lezer/java": { | |
| 1567 | + "version": "1.1.3", | |
| 1568 | + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", | |
| 1569 | + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", | |
| 1570 | + "license": "MIT", | |
| 1571 | + "dependencies": { | |
| 1572 | + "@lezer/common": "^1.2.0", | |
| 1573 | + "@lezer/highlight": "^1.0.0", | |
| 1574 | + "@lezer/lr": "^1.0.0" | |
| 1575 | + } | |
| 1576 | + }, | |
| 1577 | + "node_modules/@lezer/javascript": { | |
| 1578 | + "version": "1.5.4", | |
| 1579 | + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", | |
| 1580 | + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", | |
| 1581 | + "license": "MIT", | |
| 1582 | + "dependencies": { | |
| 1583 | + "@lezer/common": "^1.2.0", | |
| 1584 | + "@lezer/highlight": "^1.1.3", | |
| 1585 | + "@lezer/lr": "^1.3.0" | |
| 1586 | + } | |
| 1587 | + }, | |
| 1588 | + "node_modules/@lezer/json": { | |
| 1589 | + "version": "1.0.3", | |
| 1590 | + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", | |
| 1591 | + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", | |
| 1592 | + "license": "MIT", | |
| 1593 | + "dependencies": { | |
| 1594 | + "@lezer/common": "^1.2.0", | |
| 1595 | + "@lezer/highlight": "^1.0.0", | |
| 1596 | + "@lezer/lr": "^1.0.0" | |
| 1597 | + } | |
| 1598 | + }, | |
| 1599 | + "node_modules/@lezer/lr": { | |
| 1600 | + "version": "1.4.8", | |
| 1601 | + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", | |
| 1602 | + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", | |
| 1603 | + "license": "MIT", | |
| 1604 | + "dependencies": { | |
| 1605 | + "@lezer/common": "^1.0.0" | |
| 1606 | + } | |
| 1607 | + }, | |
| 1608 | + "node_modules/@lezer/markdown": { | |
| 1609 | + "version": "1.6.3", | |
| 1610 | + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", | |
| 1611 | + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", | |
| 1612 | + "license": "MIT", | |
| 1613 | + "dependencies": { | |
| 1614 | + "@lezer/common": "^1.5.0", | |
| 1615 | + "@lezer/highlight": "^1.0.0" | |
| 1616 | + } | |
| 1617 | + }, | |
| 1618 | + "node_modules/@lezer/php": { | |
| 1619 | + "version": "1.0.5", | |
| 1620 | + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", | |
| 1621 | + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", | |
| 1622 | + "license": "MIT", | |
| 1623 | + "dependencies": { | |
| 1624 | + "@lezer/common": "^1.2.0", | |
| 1625 | + "@lezer/highlight": "^1.0.0", | |
| 1626 | + "@lezer/lr": "^1.1.0" | |
| 1627 | + } | |
| 1628 | + }, | |
| 1629 | + "node_modules/@lezer/python": { | |
| 1630 | + "version": "1.1.18", | |
| 1631 | + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", | |
| 1632 | + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", | |
| 1633 | + "license": "MIT", | |
| 1634 | + "dependencies": { | |
| 1635 | + "@lezer/common": "^1.2.0", | |
| 1636 | + "@lezer/highlight": "^1.0.0", | |
| 1637 | + "@lezer/lr": "^1.0.0" | |
| 1638 | + } | |
| 1639 | + }, | |
| 1640 | + "node_modules/@lezer/rust": { | |
| 1641 | + "version": "1.0.2", | |
| 1642 | + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", | |
| 1643 | + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", | |
| 1644 | + "license": "MIT", | |
| 1645 | + "dependencies": { | |
| 1646 | + "@lezer/common": "^1.2.0", | |
| 1647 | + "@lezer/highlight": "^1.0.0", | |
| 1648 | + "@lezer/lr": "^1.0.0" | |
| 1649 | + } | |
| 1650 | + }, | |
| 1651 | + "node_modules/@lezer/xml": { | |
| 1652 | + "version": "1.0.6", | |
| 1653 | + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", | |
| 1654 | + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", | |
| 1655 | + "license": "MIT", | |
| 1656 | + "dependencies": { | |
| 1657 | + "@lezer/common": "^1.2.0", | |
| 1658 | + "@lezer/highlight": "^1.0.0", | |
| 1659 | + "@lezer/lr": "^1.0.0" | |
| 1660 | + } | |
| 1661 | + }, | |
| 1662 | + "node_modules/@marijn/find-cluster-break": { | |
| 1663 | + "version": "1.0.2", | |
| 1664 | + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", | |
| 1665 | + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", | |
| 1666 | + "license": "MIT" | |
| 1667 | + }, | |
| 1668 | + "node_modules/@napi-rs/canvas": { | |
| 1669 | + "version": "0.1.93", | |
| 1670 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.93.tgz", | |
| 1671 | + "integrity": "sha512-unVFo8CUlUeJCCxt50+j4yy91NF4x6n9zdGcvEsOFAWzowtZm3mgx8X2D7xjwV0cFSfxmpGPoe+JS77uzeFsxg==", | |
| 1672 | + "license": "MIT", | |
| 1673 | + "optional": true, | |
| 1674 | + "workspaces": [ | |
| 1675 | + "e2e/*" | |
| 1676 | + ], | |
| 1677 | + "engines": { | |
| 1678 | + "node": ">= 10" | |
| 1679 | + }, | |
| 1680 | + "funding": { | |
| 1681 | + "type": "github", | |
| 1682 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1683 | + }, | |
| 1684 | + "optionalDependencies": { | |
| 1685 | + "@napi-rs/canvas-android-arm64": "0.1.93", | |
| 1686 | + "@napi-rs/canvas-darwin-arm64": "0.1.93", | |
| 1687 | + "@napi-rs/canvas-darwin-x64": "0.1.93", | |
| 1688 | + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.93", | |
| 1689 | + "@napi-rs/canvas-linux-arm64-gnu": "0.1.93", | |
| 1690 | + "@napi-rs/canvas-linux-arm64-musl": "0.1.93", | |
| 1691 | + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.93", | |
| 1692 | + "@napi-rs/canvas-linux-x64-gnu": "0.1.93", | |
| 1693 | + "@napi-rs/canvas-linux-x64-musl": "0.1.93", | |
| 1694 | + "@napi-rs/canvas-win32-arm64-msvc": "0.1.93", | |
| 1695 | + "@napi-rs/canvas-win32-x64-msvc": "0.1.93" | |
| 1696 | + } | |
| 1697 | + }, | |
| 1698 | + "node_modules/@napi-rs/canvas-android-arm64": { | |
| 1699 | + "version": "0.1.93", | |
| 1700 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.93.tgz", | |
| 1701 | + "integrity": "sha512-xRIoOPFvneR29Dtq5d9p2AJbijDCFeV4jQ+5Ms/xVAXJVb8R0Jlu+pPr/SkhrG+Mouaml4roPSXugTIeRl6CMA==", | |
| 1702 | + "cpu": [ | |
| 1703 | + "arm64" | |
| 1704 | + ], | |
| 1705 | + "license": "MIT", | |
| 1706 | + "optional": true, | |
| 1707 | + "os": [ | |
| 1708 | + "android" | |
| 1709 | + ], | |
| 1710 | + "engines": { | |
| 1711 | + "node": ">= 10" | |
| 1712 | + }, | |
| 1713 | + "funding": { | |
| 1714 | + "type": "github", | |
| 1715 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1716 | + } | |
| 1717 | + }, | |
| 1718 | + "node_modules/@napi-rs/canvas-darwin-arm64": { | |
| 1719 | + "version": "0.1.93", | |
| 1720 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.93.tgz", | |
| 1721 | + "integrity": "sha512-daNDi76HN5grC6GXDmpxdfP+N2mQPd3sCfg62VyHwUuvbZh32P7R/IUjkzAxtYMtTza+Zvx9hfLJ3J7ENL6WMA==", | |
| 1722 | + "cpu": [ | |
| 1723 | + "arm64" | |
| 1724 | + ], | |
| 1725 | + "license": "MIT", | |
| 1726 | + "optional": true, | |
| 1727 | + "os": [ | |
| 1728 | + "darwin" | |
| 1729 | + ], | |
| 1730 | + "engines": { | |
| 1731 | + "node": ">= 10" | |
| 1732 | + }, | |
| 1733 | + "funding": { | |
| 1734 | + "type": "github", | |
| 1735 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1736 | + } | |
| 1737 | + }, | |
| 1738 | + "node_modules/@napi-rs/canvas-darwin-x64": { | |
| 1739 | + "version": "0.1.93", | |
| 1740 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.93.tgz", | |
| 1741 | + "integrity": "sha512-1YfuNPIQLawsg/gSNdJRk4kQWUy9M/Gy8FGsOI79nhQEJ2PZdqpSPl5UNzf4elfuNXuVbEbmmjP68EQdUunDuQ==", | |
| 1742 | + "cpu": [ | |
| 1743 | + "x64" | |
| 1744 | + ], | |
| 1745 | + "license": "MIT", | |
| 1746 | + "optional": true, | |
| 1747 | + "os": [ | |
| 1748 | + "darwin" | |
| 1749 | + ], | |
| 1750 | + "engines": { | |
| 1751 | + "node": ">= 10" | |
| 1752 | + }, | |
| 1753 | + "funding": { | |
| 1754 | + "type": "github", | |
| 1755 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1756 | + } | |
| 1757 | + }, | |
| 1758 | + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { | |
| 1759 | + "version": "0.1.93", | |
| 1760 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.93.tgz", | |
| 1761 | + "integrity": "sha512-8kEkOQPZjuyHjupvXExuJZiuiVNecdABGq3DLI7aO1EvQFOOlWMm2d/8Q5qXdV73Tn+nu3m16+kPajsN1oJefQ==", | |
| 1762 | + "cpu": [ | |
| 1763 | + "arm" | |
| 1764 | + ], | |
| 1765 | + "license": "MIT", | |
| 1766 | + "optional": true, | |
| 1767 | + "os": [ | |
| 1768 | + "linux" | |
| 1769 | + ], | |
| 1770 | + "engines": { | |
| 1771 | + "node": ">= 10" | |
| 1772 | + }, | |
| 1773 | + "funding": { | |
| 1774 | + "type": "github", | |
| 1775 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1776 | + } | |
| 1777 | + }, | |
| 1778 | + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { | |
| 1779 | + "version": "0.1.93", | |
| 1780 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.93.tgz", | |
| 1781 | + "integrity": "sha512-qIKLKkBkYSyWSYAoDThoxf5y1gr4X0g7W8rDU7d2HDeAAcotdVHUwuKkMeNe6+5VNk7/95EIhbslQjSxiCu32g==", | |
| 1782 | + "cpu": [ | |
| 1783 | + "arm64" | |
| 1784 | + ], | |
| 1785 | + "license": "MIT", | |
| 1786 | + "optional": true, | |
| 1787 | + "os": [ | |
| 1788 | + "linux" | |
| 1789 | + ], | |
| 1790 | + "engines": { | |
| 1791 | + "node": ">= 10" | |
| 1792 | + }, | |
| 1793 | + "funding": { | |
| 1794 | + "type": "github", | |
| 1795 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1796 | + } | |
| 1797 | + }, | |
| 1798 | + "node_modules/@napi-rs/canvas-linux-arm64-musl": { | |
| 1799 | + "version": "0.1.93", | |
| 1800 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.93.tgz", | |
| 1801 | + "integrity": "sha512-mAwQBGM3qArS9XEO21AK4E1uGvCuUCXjhIZk0dlVvs49MQ6wAAuCkYKNFpSKeSicKrLWwBMfgWX4qZoPh+M00A==", | |
| 1802 | + "cpu": [ | |
| 1803 | + "arm64" | |
| 1804 | + ], | |
| 1805 | + "license": "MIT", | |
| 1806 | + "optional": true, | |
| 1807 | + "os": [ | |
| 1808 | + "linux" | |
| 1809 | + ], | |
| 1810 | + "engines": { | |
| 1811 | + "node": ">= 10" | |
| 1812 | + }, | |
| 1813 | + "funding": { | |
| 1814 | + "type": "github", | |
| 1815 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1816 | + } | |
| 1817 | + }, | |
| 1818 | + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { | |
| 1819 | + "version": "0.1.93", | |
| 1820 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.93.tgz", | |
| 1821 | + "integrity": "sha512-kaIH5MpPzOZfkM+QMsBxGdM9jlJT+N+fwz2IEaju/S+DL65E5TgPOx4QcD5dQ8vsMxlak6uDrudBc4ns5xzZCw==", | |
| 1822 | + "cpu": [ | |
| 1823 | + "riscv64" | |
| 1824 | + ], | |
| 1825 | + "license": "MIT", | |
| 1826 | + "optional": true, | |
| 1827 | + "os": [ | |
| 1828 | + "linux" | |
| 1829 | + ], | |
| 1830 | + "engines": { | |
| 1831 | + "node": ">= 10" | |
| 1832 | + }, | |
| 1833 | + "funding": { | |
| 1834 | + "type": "github", | |
| 1835 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1836 | + } | |
| 1837 | + }, | |
| 1838 | + "node_modules/@napi-rs/canvas-linux-x64-gnu": { | |
| 1839 | + "version": "0.1.93", | |
| 1840 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.93.tgz", | |
| 1841 | + "integrity": "sha512-KtMZJqYWvOSeW5w3VSV2f5iGnwNdKJm4gwgVid4xNy1NFi+NJSyuglA1lX1u4wIPxizyxh8OW5c5Usf6oSOMNQ==", | |
| 1842 | + "cpu": [ | |
| 1843 | + "x64" | |
| 1844 | + ], | |
| 1845 | + "license": "MIT", | |
| 1846 | + "optional": true, | |
| 1847 | + "os": [ | |
| 1848 | + "linux" | |
| 1849 | + ], | |
| 1850 | + "engines": { | |
| 1851 | + "node": ">= 10" | |
| 1852 | + }, | |
| 1853 | + "funding": { | |
| 1854 | + "type": "github", | |
| 1855 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1856 | + } | |
| 1857 | + }, | |
| 1858 | + "node_modules/@napi-rs/canvas-linux-x64-musl": { | |
| 1859 | + "version": "0.1.93", | |
| 1860 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.93.tgz", | |
| 1861 | + "integrity": "sha512-qRZhOvlDBooRLX6V3/t9X9B+plZK+OrPLgfFixu0A1RO/3VHbubOknfnMnocSDAqk/L6cRyKI83VP2ciR9UO7w==", | |
| 1862 | + "cpu": [ | |
| 1863 | + "x64" | |
| 1864 | + ], | |
| 1865 | + "license": "MIT", | |
| 1866 | + "optional": true, | |
| 1867 | + "os": [ | |
| 1868 | + "linux" | |
| 1869 | + ], | |
| 1870 | + "engines": { | |
| 1871 | + "node": ">= 10" | |
| 1872 | + }, | |
| 1873 | + "funding": { | |
| 1874 | + "type": "github", | |
| 1875 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1876 | + } | |
| 1877 | + }, | |
| 1878 | + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { | |
| 1879 | + "version": "0.1.93", | |
| 1880 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.93.tgz", | |
| 1881 | + "integrity": "sha512-um5XE44vF8bjkQEsH2iRSUP9fDeQGYbn/qjM/v4whXG83qsqapAXlOPOQqSARZB1SiNvPUAuXoRsJLlKFmAEFw==", | |
| 1882 | + "cpu": [ | |
| 1883 | + "arm64" | |
| 1884 | + ], | |
| 1885 | + "license": "MIT", | |
| 1886 | + "optional": true, | |
| 1887 | + "os": [ | |
| 1888 | + "win32" | |
| 1889 | + ], | |
| 1890 | + "engines": { | |
| 1891 | + "node": ">= 10" | |
| 1892 | + }, | |
| 1893 | + "funding": { | |
| 1894 | + "type": "github", | |
| 1895 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1896 | + } | |
| 1897 | + }, | |
| 1898 | + "node_modules/@napi-rs/canvas-win32-x64-msvc": { | |
| 1899 | + "version": "0.1.93", | |
| 1900 | + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.93.tgz", | |
| 1901 | + "integrity": "sha512-maHlizZgmKsAPJwjwBZMnsWfq3Ca9QutoteQwKe7YqsmbECoylrLCCOGCDOredstW4BRWqRTfCl6NJaVVeAQvQ==", | |
| 1902 | + "cpu": [ | |
| 1903 | + "x64" | |
| 1904 | + ], | |
| 1905 | + "license": "MIT", | |
| 1906 | + "optional": true, | |
| 1907 | + "os": [ | |
| 1908 | + "win32" | |
| 1909 | + ], | |
| 1910 | + "engines": { | |
| 1911 | + "node": ">= 10" | |
| 1912 | + }, | |
| 1913 | + "funding": { | |
| 1914 | + "type": "github", | |
| 1915 | + "url": "https://github.com/sponsors/Brooooooklyn" | |
| 1916 | + } | |
| 1917 | + }, | |
| 1918 | + "node_modules/@napi-rs/wasm-runtime": { | |
| 1919 | + "version": "0.2.12", | |
| 1920 | + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", | |
| 1921 | + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", | |
| 1922 | + "dev": true, | |
| 1923 | + "license": "MIT", | |
| 1924 | + "optional": true, | |
| 1925 | + "dependencies": { | |
| 1926 | + "@emnapi/core": "^1.4.3", | |
| 1927 | + "@emnapi/runtime": "^1.4.3", | |
| 1928 | + "@tybys/wasm-util": "^0.10.0" | |
| 1929 | + } | |
| 1930 | + }, | |
| 1931 | + "node_modules/@next/env": { | |
| 1932 | + "version": "14.2.35", | |
| 1933 | + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", | |
| 1934 | + "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", | |
| 1935 | + "license": "MIT" | |
| 1936 | + }, | |
| 1937 | + "node_modules/@next/eslint-plugin-next": { | |
| 1938 | + "version": "14.2.35", | |
| 1939 | + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.35.tgz", | |
| 1940 | + "integrity": "sha512-Jw9A3ICz2183qSsqwi7fgq4SBPiNfmOLmTPXKvlnzstUwyvBrtySiY+8RXJweNAs9KThb1+bYhZh9XWcNOr2zQ==", | |
| 1941 | + "dev": true, | |
| 1942 | + "license": "MIT", | |
| 1943 | + "dependencies": { | |
| 1944 | + "glob": "10.3.10" | |
| 1945 | + } | |
| 1946 | + }, | |
| 1947 | + "node_modules/@next/swc-darwin-arm64": { | |
| 1948 | + "version": "14.2.33", | |
| 1949 | + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", | |
| 1950 | + "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", | |
| 1951 | + "cpu": [ | |
| 1952 | + "arm64" | |
| 1953 | + ], | |
| 1954 | + "license": "MIT", | |
| 1955 | + "optional": true, | |
| 1956 | + "os": [ | |
| 1957 | + "darwin" | |
| 1958 | + ], | |
| 1959 | + "engines": { | |
| 1960 | + "node": ">= 10" | |
| 1961 | + } | |
| 1962 | + }, | |
| 1963 | + "node_modules/@next/swc-darwin-x64": { | |
| 1964 | + "version": "14.2.33", | |
| 1965 | + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", | |
| 1966 | + "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", | |
| 1967 | + "cpu": [ | |
| 1968 | + "x64" | |
| 1969 | + ], | |
| 1970 | + "license": "MIT", | |
| 1971 | + "optional": true, | |
| 1972 | + "os": [ | |
| 1973 | + "darwin" | |
| 1974 | + ], | |
| 1975 | + "engines": { | |
| 1976 | + "node": ">= 10" | |
| 1977 | + } | |
| 1978 | + }, | |
| 1979 | + "node_modules/@next/swc-linux-arm64-gnu": { | |
| 1980 | + "version": "14.2.33", | |
| 1981 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", | |
| 1982 | + "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", | |
| 1983 | + "cpu": [ | |
| 1984 | + "arm64" | |
| 1985 | + ], | |
| 1986 | + "license": "MIT", | |
| 1987 | + "optional": true, | |
| 1988 | + "os": [ | |
| 1989 | + "linux" | |
| 1990 | + ], | |
| 1991 | + "engines": { | |
| 1992 | + "node": ">= 10" | |
| 1993 | + } | |
| 1994 | + }, | |
| 1995 | + "node_modules/@next/swc-linux-arm64-musl": { | |
| 1996 | + "version": "14.2.33", | |
| 1997 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", | |
| 1998 | + "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", | |
| 1999 | + "cpu": [ | |
| 2000 | + "arm64" | |
| 2001 | + ], | |
| 2002 | + "license": "MIT", | |
| 2003 | + "optional": true, | |
| 2004 | + "os": [ | |
| 2005 | + "linux" | |
| 2006 | + ], | |
| 2007 | + "engines": { | |
| 2008 | + "node": ">= 10" | |
| 2009 | + } | |
| 2010 | + }, | |
| 2011 | + "node_modules/@next/swc-linux-x64-gnu": { | |
| 2012 | + "version": "14.2.33", | |
| 2013 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", | |
| 2014 | + "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", | |
| 2015 | + "cpu": [ | |
| 2016 | + "x64" | |
| 2017 | + ], | |
| 2018 | + "license": "MIT", | |
| 2019 | + "optional": true, | |
| 2020 | + "os": [ | |
| 2021 | + "linux" | |
| 2022 | + ], | |
| 2023 | + "engines": { | |
| 2024 | + "node": ">= 10" | |
| 2025 | + } | |
| 2026 | + }, | |
| 2027 | + "node_modules/@next/swc-linux-x64-musl": { | |
| 2028 | + "version": "14.2.33", | |
| 2029 | + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", | |
| 2030 | + "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", | |
| 2031 | + "cpu": [ | |
| 2032 | + "x64" | |
| 2033 | + ], | |
| 2034 | + "license": "MIT", | |
| 2035 | + "optional": true, | |
| 2036 | + "os": [ | |
| 2037 | + "linux" | |
| 2038 | + ], | |
| 2039 | + "engines": { | |
| 2040 | + "node": ">= 10" | |
| 2041 | + } | |
| 2042 | + }, | |
| 2043 | + "node_modules/@next/swc-win32-arm64-msvc": { | |
| 2044 | + "version": "14.2.33", | |
| 2045 | + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", | |
| 2046 | + "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", | |
| 2047 | + "cpu": [ | |
| 2048 | + "arm64" | |
| 2049 | + ], | |
| 2050 | + "license": "MIT", | |
| 2051 | + "optional": true, | |
| 2052 | + "os": [ | |
| 2053 | + "win32" | |
| 2054 | + ], | |
| 2055 | + "engines": { | |
| 2056 | + "node": ">= 10" | |
| 2057 | + } | |
| 2058 | + }, | |
| 2059 | + "node_modules/@next/swc-win32-ia32-msvc": { | |
| 2060 | + "version": "14.2.33", | |
| 2061 | + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", | |
| 2062 | + "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", | |
| 2063 | + "cpu": [ | |
| 2064 | + "ia32" | |
| 2065 | + ], | |
| 2066 | + "license": "MIT", | |
| 2067 | + "optional": true, | |
| 2068 | + "os": [ | |
| 2069 | + "win32" | |
| 2070 | + ], | |
| 2071 | + "engines": { | |
| 2072 | + "node": ">= 10" | |
| 2073 | + } | |
| 2074 | + }, | |
| 2075 | + "node_modules/@next/swc-win32-x64-msvc": { | |
| 2076 | + "version": "14.2.33", | |
| 2077 | + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", | |
| 2078 | + "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", | |
| 2079 | + "cpu": [ | |
| 2080 | + "x64" | |
| 2081 | + ], | |
| 2082 | + "license": "MIT", | |
| 2083 | + "optional": true, | |
| 2084 | + "os": [ | |
| 2085 | + "win32" | |
| 2086 | + ], | |
| 2087 | + "engines": { | |
| 2088 | + "node": ">= 10" | |
| 2089 | + } | |
| 2090 | + }, | |
| 2091 | + "node_modules/@noble/hashes": { | |
| 2092 | + "version": "2.0.1", | |
| 2093 | + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", | |
| 2094 | + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", | |
| 2095 | + "license": "MIT", | |
| 2096 | + "engines": { | |
| 2097 | + "node": ">= 20.19.0" | |
| 2098 | + }, | |
| 2099 | + "funding": { | |
| 2100 | + "url": "https://paulmillr.com/funding/" | |
| 2101 | + } | |
| 2102 | + }, | |
| 2103 | + "node_modules/@nodelib/fs.scandir": { | |
| 2104 | + "version": "2.1.5", | |
| 2105 | + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", | |
| 2106 | + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", | |
| 2107 | + "dev": true, | |
| 2108 | + "license": "MIT", | |
| 2109 | + "dependencies": { | |
| 2110 | + "@nodelib/fs.stat": "2.0.5", | |
| 2111 | + "run-parallel": "^1.1.9" | |
| 2112 | + }, | |
| 2113 | + "engines": { | |
| 2114 | + "node": ">= 8" | |
| 2115 | + } | |
| 2116 | + }, | |
| 2117 | + "node_modules/@nodelib/fs.stat": { | |
| 2118 | + "version": "2.0.5", | |
| 2119 | + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", | |
| 2120 | + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", | |
| 2121 | + "dev": true, | |
| 2122 | + "license": "MIT", | |
| 2123 | + "engines": { | |
| 2124 | + "node": ">= 8" | |
| 2125 | + } | |
| 2126 | + }, | |
| 2127 | + "node_modules/@nodelib/fs.walk": { | |
| 2128 | + "version": "1.2.8", | |
| 2129 | + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", | |
| 2130 | + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", | |
| 2131 | + "dev": true, | |
| 2132 | + "license": "MIT", | |
| 2133 | + "dependencies": { | |
| 2134 | + "@nodelib/fs.scandir": "2.1.5", | |
| 2135 | + "fastq": "^1.6.0" | |
| 2136 | + }, | |
| 2137 | + "engines": { | |
| 2138 | + "node": ">= 8" | |
| 2139 | + } | |
| 2140 | + }, | |
| 2141 | + "node_modules/@nolyfill/is-core-module": { | |
| 2142 | + "version": "1.0.39", | |
| 2143 | + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", | |
| 2144 | + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", | |
| 2145 | + "dev": true, | |
| 2146 | + "license": "MIT", | |
| 2147 | + "engines": { | |
| 2148 | + "node": ">=12.4.0" | |
| 2149 | + } | |
| 2150 | + }, | |
| 2151 | + "node_modules/@otplib/core": { | |
| 2152 | + "version": "13.3.0", | |
| 2153 | + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-13.3.0.tgz", | |
| 2154 | + "integrity": "sha512-pnQDOuCmFVeF/XnboJq9TOJgLoo2idNPJKMymOF8vGqJJ+ReKRYM9bUGjNPRWC0tHjMwu1TXbnzyBp494JgRag==", | |
| 2155 | + "license": "MIT" | |
| 2156 | + }, | |
| 2157 | + "node_modules/@otplib/hotp": { | |
| 2158 | + "version": "13.3.0", | |
| 2159 | + "resolved": "https://registry.npmjs.org/@otplib/hotp/-/hotp-13.3.0.tgz", | |
| 2160 | + "integrity": "sha512-XJMZGz2bg4QJwK7ulvl1GUI2VMn/flaIk/E/BTKAejHsX2kUtPF1bRhlZ2+elq8uU5Fs9Z9FHcQK2CPZNQbbUQ==", | |
| 2161 | + "license": "MIT", | |
| 2162 | + "dependencies": { | |
| 2163 | + "@otplib/core": "13.3.0", | |
| 2164 | + "@otplib/uri": "13.3.0" | |
| 2165 | + } | |
| 2166 | + }, | |
| 2167 | + "node_modules/@otplib/plugin-base32-scure": { | |
| 2168 | + "version": "13.3.0", | |
| 2169 | + "resolved": "https://registry.npmjs.org/@otplib/plugin-base32-scure/-/plugin-base32-scure-13.3.0.tgz", | |
| 2170 | + "integrity": "sha512-/jYbL5S6GB0Ie3XGEWtLIr9s5ZICl/BfmNL7+8/W7usZaUU4GiyLd2S+JGsNCslPyqNekSudD864nDAvRI0s8w==", | |
| 2171 | + "license": "MIT", | |
| 2172 | + "dependencies": { | |
| 2173 | + "@otplib/core": "13.3.0", | |
| 2174 | + "@scure/base": "^2.0.0" | |
| 2175 | + } | |
| 2176 | + }, | |
| 2177 | + "node_modules/@otplib/plugin-crypto-noble": { | |
| 2178 | + "version": "13.3.0", | |
| 2179 | + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto-noble/-/plugin-crypto-noble-13.3.0.tgz", | |
| 2180 | + "integrity": "sha512-wmV+jBVncepgwv99G7Plrdzd0tHfbpXk2U+OD7MO7DzpDqOYEgOPi+IIneksJSTL8QvWdfi+uQEuhnER4fKouA==", | |
| 2181 | + "license": "MIT", | |
| 2182 | + "dependencies": { | |
| 2183 | + "@noble/hashes": "^2.0.1", | |
| 2184 | + "@otplib/core": "13.3.0" | |
| 2185 | + } | |
| 2186 | + }, | |
| 2187 | + "node_modules/@otplib/totp": { | |
| 2188 | + "version": "13.3.0", | |
| 2189 | + "resolved": "https://registry.npmjs.org/@otplib/totp/-/totp-13.3.0.tgz", | |
| 2190 | + "integrity": "sha512-XfjGNoN8d9S3Ove2j7AwkVV7+QDFsV7Lm7YwSiezNaHffkWtJ60aJYpmf+01dARdPST71U2ptueMsRJso4sq4A==", | |
| 2191 | + "license": "MIT", | |
| 2192 | + "dependencies": { | |
| 2193 | + "@otplib/core": "13.3.0", | |
| 2194 | + "@otplib/hotp": "13.3.0", | |
| 2195 | + "@otplib/uri": "13.3.0" | |
| 2196 | + } | |
| 2197 | + }, | |
| 2198 | + "node_modules/@otplib/uri": { | |
| 2199 | + "version": "13.3.0", | |
| 2200 | + "resolved": "https://registry.npmjs.org/@otplib/uri/-/uri-13.3.0.tgz", | |
| 2201 | + "integrity": "sha512-3oh6nBXy+cm3UX9cxEAGZiDrfxHU2gfelYFV+XNCx+8dq39VaQVymwlU2yjPZiMAi/3agaUeEftf2RwM5F+Cyg==", | |
| 2202 | + "license": "MIT", | |
| 2203 | + "dependencies": { | |
| 2204 | + "@otplib/core": "13.3.0" | |
| 2205 | + } | |
| 2206 | + }, | |
| 2207 | + "node_modules/@pdf-lib/standard-fonts": { | |
| 2208 | + "version": "1.0.0", | |
| 2209 | + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", | |
| 2210 | + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", | |
| 2211 | + "license": "MIT", | |
| 2212 | + "dependencies": { | |
| 2213 | + "pako": "^1.0.6" | |
| 2214 | + } | |
| 2215 | + }, | |
| 2216 | + "node_modules/@pdf-lib/upng": { | |
| 2217 | + "version": "1.0.1", | |
| 2218 | + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", | |
| 2219 | + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", | |
| 2220 | + "license": "MIT", | |
| 2221 | + "dependencies": { | |
| 2222 | + "pako": "^1.0.10" | |
| 2223 | + } | |
| 2224 | + }, | |
| 2225 | + "node_modules/@pkgjs/parseargs": { | |
| 2226 | + "version": "0.11.0", | |
| 2227 | + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", | |
| 2228 | + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", | |
| 2229 | + "license": "MIT", | |
| 2230 | + "optional": true, | |
| 2231 | + "engines": { | |
| 2232 | + "node": ">=14" | |
| 2233 | + } | |
| 2234 | + }, | |
| 2235 | + "node_modules/@prisma/client": { | |
| 2236 | + "version": "5.22.0", | |
| 2237 | + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", | |
| 2238 | + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", | |
| 2239 | + "hasInstallScript": true, | |
| 2240 | + "license": "Apache-2.0", | |
| 2241 | + "engines": { | |
| 2242 | + "node": ">=16.13" | |
| 2243 | + }, | |
| 2244 | + "peerDependencies": { | |
| 2245 | + "prisma": "*" | |
| 2246 | + }, | |
| 2247 | + "peerDependenciesMeta": { | |
| 2248 | + "prisma": { | |
| 2249 | + "optional": true | |
| 2250 | + } | |
| 2251 | + } | |
| 2252 | + }, | |
| 2253 | + "node_modules/@prisma/debug": { | |
| 2254 | + "version": "5.22.0", | |
| 2255 | + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", | |
| 2256 | + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", | |
| 2257 | + "license": "Apache-2.0" | |
| 2258 | + }, | |
| 2259 | + "node_modules/@prisma/engines": { | |
| 2260 | + "version": "5.22.0", | |
| 2261 | + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", | |
| 2262 | + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", | |
| 2263 | + "hasInstallScript": true, | |
| 2264 | + "license": "Apache-2.0", | |
| 2265 | + "dependencies": { | |
| 2266 | + "@prisma/debug": "5.22.0", | |
| 2267 | + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", | |
| 2268 | + "@prisma/fetch-engine": "5.22.0", | |
| 2269 | + "@prisma/get-platform": "5.22.0" | |
| 2270 | + } | |
| 2271 | + }, | |
| 2272 | + "node_modules/@prisma/engines-version": { | |
| 2273 | + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", | |
| 2274 | + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", | |
| 2275 | + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", | |
| 2276 | + "license": "Apache-2.0" | |
| 2277 | + }, | |
| 2278 | + "node_modules/@prisma/fetch-engine": { | |
| 2279 | + "version": "5.22.0", | |
| 2280 | + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", | |
| 2281 | + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", | |
| 2282 | + "license": "Apache-2.0", | |
| 2283 | + "dependencies": { | |
| 2284 | + "@prisma/debug": "5.22.0", | |
| 2285 | + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", | |
| 2286 | + "@prisma/get-platform": "5.22.0" | |
| 2287 | + } | |
| 2288 | + }, | |
| 2289 | + "node_modules/@prisma/get-platform": { | |
| 2290 | + "version": "5.22.0", | |
| 2291 | + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", | |
| 2292 | + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", | |
| 2293 | + "license": "Apache-2.0", | |
| 2294 | + "dependencies": { | |
| 2295 | + "@prisma/debug": "5.22.0" | |
| 2296 | + } | |
| 2297 | + }, | |
| 2298 | + "node_modules/@rtsao/scc": { | |
| 2299 | + "version": "1.1.0", | |
| 2300 | + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", | |
| 2301 | + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", | |
| 2302 | + "dev": true, | |
| 2303 | + "license": "MIT" | |
| 2304 | + }, | |
| 2305 | + "node_modules/@rushstack/eslint-patch": { | |
| 2306 | + "version": "1.15.0", | |
| 2307 | + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.15.0.tgz", | |
| 2308 | + "integrity": "sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==", | |
| 2309 | + "dev": true, | |
| 2310 | + "license": "MIT" | |
| 2311 | + }, | |
| 2312 | + "node_modules/@scure/base": { | |
| 2313 | + "version": "2.0.0", | |
| 2314 | + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", | |
| 2315 | + "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", | |
| 2316 | + "license": "MIT", | |
| 2317 | + "funding": { | |
| 2318 | + "url": "https://paulmillr.com/funding/" | |
| 2319 | + } | |
| 2320 | + }, | |
| 2321 | + "node_modules/@swc/counter": { | |
| 2322 | + "version": "0.1.3", | |
| 2323 | + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", | |
| 2324 | + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", | |
| 2325 | + "license": "Apache-2.0" | |
| 2326 | + }, | |
| 2327 | + "node_modules/@swc/helpers": { | |
| 2328 | + "version": "0.5.5", | |
| 2329 | + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", | |
| 2330 | + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", | |
| 2331 | + "license": "Apache-2.0", | |
| 2332 | + "dependencies": { | |
| 2333 | + "@swc/counter": "^0.1.3", | |
| 2334 | + "tslib": "^2.4.0" | |
| 2335 | + } | |
| 2336 | + }, | |
| 2337 | + "node_modules/@tybys/wasm-util": { | |
| 2338 | + "version": "0.10.1", | |
| 2339 | + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", | |
| 2340 | + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", | |
| 2341 | + "dev": true, | |
| 2342 | + "license": "MIT", | |
| 2343 | + "optional": true, | |
| 2344 | + "dependencies": { | |
| 2345 | + "tslib": "^2.4.0" | |
| 2346 | + } | |
| 2347 | + }, | |
| 2348 | + "node_modules/@types/archiver": { | |
| 2349 | + "version": "7.0.0", | |
| 2350 | + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-7.0.0.tgz", | |
| 2351 | + "integrity": "sha512-/3vwGwx9n+mCQdYZ2IKGGHEFL30I96UgBlk8EtRDDFQ9uxM1l4O5Ci6r00EMAkiDaTqD9DQ6nVrWRICnBPtzzg==", | |
| 2352 | + "dev": true, | |
| 2353 | + "license": "MIT", | |
| 2354 | + "dependencies": { | |
| 2355 | + "@types/readdir-glob": "*" | |
| 2356 | + } | |
| 2357 | + }, | |
| 2358 | + "node_modules/@types/bcryptjs": { | |
| 2359 | + "version": "2.4.6", | |
| 2360 | + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", | |
| 2361 | + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", | |
| 2362 | + "dev": true, | |
| 2363 | + "license": "MIT" | |
| 2364 | + }, | |
| 2365 | + "node_modules/@types/debug": { | |
| 2366 | + "version": "4.1.12", | |
| 2367 | + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", | |
| 2368 | + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", | |
| 2369 | + "license": "MIT", | |
| 2370 | + "dependencies": { | |
| 2371 | + "@types/ms": "*" | |
| 2372 | + } | |
| 2373 | + }, | |
| 2374 | + "node_modules/@types/diff": { | |
| 2375 | + "version": "7.0.2", | |
| 2376 | + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-7.0.2.tgz", | |
| 2377 | + "integrity": "sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==", | |
| 2378 | + "dev": true, | |
| 2379 | + "license": "MIT" | |
| 2380 | + }, | |
| 2381 | + "node_modules/@types/estree": { | |
| 2382 | + "version": "1.0.8", | |
| 2383 | + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", | |
| 2384 | + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", | |
| 2385 | + "license": "MIT" | |
| 2386 | + }, | |
| 2387 | + "node_modules/@types/estree-jsx": { | |
| 2388 | + "version": "1.0.5", | |
| 2389 | + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", | |
| 2390 | + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", | |
| 2391 | + "license": "MIT", | |
| 2392 | + "dependencies": { | |
| 2393 | + "@types/estree": "*" | |
| 2394 | + } | |
| 2395 | + }, | |
| 2396 | + "node_modules/@types/hast": { | |
| 2397 | + "version": "3.0.4", | |
| 2398 | + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", | |
| 2399 | + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", | |
| 2400 | + "license": "MIT", | |
| 2401 | + "dependencies": { | |
| 2402 | + "@types/unist": "*" | |
| 2403 | + } | |
| 2404 | + }, | |
| 2405 | + "node_modules/@types/json5": { | |
| 2406 | + "version": "0.0.29", | |
| 2407 | + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", | |
| 2408 | + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", | |
| 2409 | + "dev": true, | |
| 2410 | + "license": "MIT" | |
| 2411 | + }, | |
| 2412 | + "node_modules/@types/mdast": { | |
| 2413 | + "version": "4.0.4", | |
| 2414 | + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", | |
| 2415 | + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", | |
| 2416 | + "license": "MIT", | |
| 2417 | + "dependencies": { | |
| 2418 | + "@types/unist": "*" | |
| 2419 | + } | |
| 2420 | + }, | |
| 2421 | + "node_modules/@types/mime-types": { | |
| 2422 | + "version": "3.0.1", | |
| 2423 | + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-3.0.1.tgz", | |
| 2424 | + "integrity": "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ==", | |
| 2425 | + "dev": true, | |
| 2426 | + "license": "MIT" | |
| 2427 | + }, | |
| 2428 | + "node_modules/@types/ms": { | |
| 2429 | + "version": "2.1.0", | |
| 2430 | + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", | |
| 2431 | + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", | |
| 2432 | + "license": "MIT" | |
| 2433 | + }, | |
| 2434 | + "node_modules/@types/node": { | |
| 2435 | + "version": "20.19.33", | |
| 2436 | + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", | |
| 2437 | + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", | |
| 2438 | + "dev": true, | |
| 2439 | + "license": "MIT", | |
| 2440 | + "dependencies": { | |
| 2441 | + "undici-types": "~6.21.0" | |
| 2442 | + } | |
| 2443 | + }, | |
| 2444 | + "node_modules/@types/papaparse": { | |
| 2445 | + "version": "5.5.2", | |
| 2446 | + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", | |
| 2447 | + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", | |
| 2448 | + "dev": true, | |
| 2449 | + "license": "MIT", | |
| 2450 | + "dependencies": { | |
| 2451 | + "@types/node": "*" | |
| 2452 | + } | |
| 2453 | + }, | |
| 2454 | + "node_modules/@types/prismjs": { | |
| 2455 | + "version": "1.26.6", | |
| 2456 | + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", | |
| 2457 | + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", | |
| 2458 | + "license": "MIT" | |
| 2459 | + }, | |
| 2460 | + "node_modules/@types/prop-types": { | |
| 2461 | + "version": "15.7.15", | |
| 2462 | + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", | |
| 2463 | + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", | |
| 2464 | + "license": "MIT" | |
| 2465 | + }, | |
| 2466 | + "node_modules/@types/qrcode": { | |
| 2467 | + "version": "1.5.6", | |
| 2468 | + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", | |
| 2469 | + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", | |
| 2470 | + "dev": true, | |
| 2471 | + "license": "MIT", | |
| 2472 | + "dependencies": { | |
| 2473 | + "@types/node": "*" | |
| 2474 | + } | |
| 2475 | + }, | |
| 2476 | + "node_modules/@types/react": { | |
| 2477 | + "version": "18.3.28", | |
| 2478 | + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", | |
| 2479 | + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", | |
| 2480 | + "license": "MIT", | |
| 2481 | + "dependencies": { | |
| 2482 | + "@types/prop-types": "*", | |
| 2483 | + "csstype": "^3.2.2" | |
| 2484 | + } | |
| 2485 | + }, | |
| 2486 | + "node_modules/@types/react-dom": { | |
| 2487 | + "version": "18.3.7", | |
| 2488 | + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", | |
| 2489 | + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", | |
| 2490 | + "dev": true, | |
| 2491 | + "license": "MIT", | |
| 2492 | + "peerDependencies": { | |
| 2493 | + "@types/react": "^18.0.0" | |
| 2494 | + } | |
| 2495 | + }, | |
| 2496 | + "node_modules/@types/react-syntax-highlighter": { | |
| 2497 | + "version": "15.5.13", | |
| 2498 | + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", | |
| 2499 | + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", | |
| 2500 | + "dev": true, | |
| 2501 | + "license": "MIT", | |
| 2502 | + "dependencies": { | |
| 2503 | + "@types/react": "*" | |
| 2504 | + } | |
| 2505 | + }, | |
| 2506 | + "node_modules/@types/readdir-glob": { | |
| 2507 | + "version": "1.1.5", | |
| 2508 | + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", | |
| 2509 | + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", | |
| 2510 | + "dev": true, | |
| 2511 | + "license": "MIT", | |
| 2512 | + "dependencies": { | |
| 2513 | + "@types/node": "*" | |
| 2514 | + } | |
| 2515 | + }, | |
| 2516 | + "node_modules/@types/sharp": { | |
| 2517 | + "version": "0.31.1", | |
| 2518 | + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", | |
| 2519 | + "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", | |
| 2520 | + "dev": true, | |
| 2521 | + "license": "MIT", | |
| 2522 | + "dependencies": { | |
| 2523 | + "@types/node": "*" | |
| 2524 | + } | |
| 2525 | + }, | |
| 2526 | + "node_modules/@types/unist": { | |
| 2527 | + "version": "3.0.3", | |
| 2528 | + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", | |
| 2529 | + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", | |
| 2530 | + "license": "MIT" | |
| 2531 | + }, | |
| 2532 | + "node_modules/@types/uuid": { | |
| 2533 | + "version": "10.0.0", | |
| 2534 | + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", | |
| 2535 | + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", | |
| 2536 | + "dev": true, | |
| 2537 | + "license": "MIT" | |
| 2538 | + }, | |
| 2539 | + "node_modules/@typescript-eslint/eslint-plugin": { | |
| 2540 | + "version": "8.55.0", | |
| 2541 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", | |
| 2542 | + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", | |
| 2543 | + "dev": true, | |
| 2544 | + "license": "MIT", | |
| 2545 | + "dependencies": { | |
| 2546 | + "@eslint-community/regexpp": "^4.12.2", | |
| 2547 | + "@typescript-eslint/scope-manager": "8.55.0", | |
| 2548 | + "@typescript-eslint/type-utils": "8.55.0", | |
| 2549 | + "@typescript-eslint/utils": "8.55.0", | |
| 2550 | + "@typescript-eslint/visitor-keys": "8.55.0", | |
| 2551 | + "ignore": "^7.0.5", | |
| 2552 | + "natural-compare": "^1.4.0", | |
| 2553 | + "ts-api-utils": "^2.4.0" | |
| 2554 | + }, | |
| 2555 | + "engines": { | |
| 2556 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2557 | + }, | |
| 2558 | + "funding": { | |
| 2559 | + "type": "opencollective", | |
| 2560 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2561 | + }, | |
| 2562 | + "peerDependencies": { | |
| 2563 | + "@typescript-eslint/parser": "^8.55.0", | |
| 2564 | + "eslint": "^8.57.0 || ^9.0.0", | |
| 2565 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2566 | + } | |
| 2567 | + }, | |
| 2568 | + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { | |
| 2569 | + "version": "7.0.5", | |
| 2570 | + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", | |
| 2571 | + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", | |
| 2572 | + "dev": true, | |
| 2573 | + "license": "MIT", | |
| 2574 | + "engines": { | |
| 2575 | + "node": ">= 4" | |
| 2576 | + } | |
| 2577 | + }, | |
| 2578 | + "node_modules/@typescript-eslint/parser": { | |
| 2579 | + "version": "8.55.0", | |
| 2580 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", | |
| 2581 | + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", | |
| 2582 | + "dev": true, | |
| 2583 | + "license": "MIT", | |
| 2584 | + "dependencies": { | |
| 2585 | + "@typescript-eslint/scope-manager": "8.55.0", | |
| 2586 | + "@typescript-eslint/types": "8.55.0", | |
| 2587 | + "@typescript-eslint/typescript-estree": "8.55.0", | |
| 2588 | + "@typescript-eslint/visitor-keys": "8.55.0", | |
| 2589 | + "debug": "^4.4.3" | |
| 2590 | + }, | |
| 2591 | + "engines": { | |
| 2592 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2593 | + }, | |
| 2594 | + "funding": { | |
| 2595 | + "type": "opencollective", | |
| 2596 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2597 | + }, | |
| 2598 | + "peerDependencies": { | |
| 2599 | + "eslint": "^8.57.0 || ^9.0.0", | |
| 2600 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2601 | + } | |
| 2602 | + }, | |
| 2603 | + "node_modules/@typescript-eslint/project-service": { | |
| 2604 | + "version": "8.55.0", | |
| 2605 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", | |
| 2606 | + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", | |
| 2607 | + "dev": true, | |
| 2608 | + "license": "MIT", | |
| 2609 | + "dependencies": { | |
| 2610 | + "@typescript-eslint/tsconfig-utils": "^8.55.0", | |
| 2611 | + "@typescript-eslint/types": "^8.55.0", | |
| 2612 | + "debug": "^4.4.3" | |
| 2613 | + }, | |
| 2614 | + "engines": { | |
| 2615 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2616 | + }, | |
| 2617 | + "funding": { | |
| 2618 | + "type": "opencollective", | |
| 2619 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2620 | + }, | |
| 2621 | + "peerDependencies": { | |
| 2622 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2623 | + } | |
| 2624 | + }, | |
| 2625 | + "node_modules/@typescript-eslint/scope-manager": { | |
| 2626 | + "version": "8.55.0", | |
| 2627 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", | |
| 2628 | + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", | |
| 2629 | + "dev": true, | |
| 2630 | + "license": "MIT", | |
| 2631 | + "dependencies": { | |
| 2632 | + "@typescript-eslint/types": "8.55.0", | |
| 2633 | + "@typescript-eslint/visitor-keys": "8.55.0" | |
| 2634 | + }, | |
| 2635 | + "engines": { | |
| 2636 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2637 | + }, | |
| 2638 | + "funding": { | |
| 2639 | + "type": "opencollective", | |
| 2640 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2641 | + } | |
| 2642 | + }, | |
| 2643 | + "node_modules/@typescript-eslint/tsconfig-utils": { | |
| 2644 | + "version": "8.55.0", | |
| 2645 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", | |
| 2646 | + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", | |
| 2647 | + "dev": true, | |
| 2648 | + "license": "MIT", | |
| 2649 | + "engines": { | |
| 2650 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2651 | + }, | |
| 2652 | + "funding": { | |
| 2653 | + "type": "opencollective", | |
| 2654 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2655 | + }, | |
| 2656 | + "peerDependencies": { | |
| 2657 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2658 | + } | |
| 2659 | + }, | |
| 2660 | + "node_modules/@typescript-eslint/type-utils": { | |
| 2661 | + "version": "8.55.0", | |
| 2662 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", | |
| 2663 | + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", | |
| 2664 | + "dev": true, | |
| 2665 | + "license": "MIT", | |
| 2666 | + "dependencies": { | |
| 2667 | + "@typescript-eslint/types": "8.55.0", | |
| 2668 | + "@typescript-eslint/typescript-estree": "8.55.0", | |
| 2669 | + "@typescript-eslint/utils": "8.55.0", | |
| 2670 | + "debug": "^4.4.3", | |
| 2671 | + "ts-api-utils": "^2.4.0" | |
| 2672 | + }, | |
| 2673 | + "engines": { | |
| 2674 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2675 | + }, | |
| 2676 | + "funding": { | |
| 2677 | + "type": "opencollective", | |
| 2678 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2679 | + }, | |
| 2680 | + "peerDependencies": { | |
| 2681 | + "eslint": "^8.57.0 || ^9.0.0", | |
| 2682 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2683 | + } | |
| 2684 | + }, | |
| 2685 | + "node_modules/@typescript-eslint/types": { | |
| 2686 | + "version": "8.55.0", | |
| 2687 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", | |
| 2688 | + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", | |
| 2689 | + "dev": true, | |
| 2690 | + "license": "MIT", | |
| 2691 | + "engines": { | |
| 2692 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2693 | + }, | |
| 2694 | + "funding": { | |
| 2695 | + "type": "opencollective", | |
| 2696 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2697 | + } | |
| 2698 | + }, | |
| 2699 | + "node_modules/@typescript-eslint/typescript-estree": { | |
| 2700 | + "version": "8.55.0", | |
| 2701 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", | |
| 2702 | + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", | |
| 2703 | + "dev": true, | |
| 2704 | + "license": "MIT", | |
| 2705 | + "dependencies": { | |
| 2706 | + "@typescript-eslint/project-service": "8.55.0", | |
| 2707 | + "@typescript-eslint/tsconfig-utils": "8.55.0", | |
| 2708 | + "@typescript-eslint/types": "8.55.0", | |
| 2709 | + "@typescript-eslint/visitor-keys": "8.55.0", | |
| 2710 | + "debug": "^4.4.3", | |
| 2711 | + "minimatch": "^9.0.5", | |
| 2712 | + "semver": "^7.7.3", | |
| 2713 | + "tinyglobby": "^0.2.15", | |
| 2714 | + "ts-api-utils": "^2.4.0" | |
| 2715 | + }, | |
| 2716 | + "engines": { | |
| 2717 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2718 | + }, | |
| 2719 | + "funding": { | |
| 2720 | + "type": "opencollective", | |
| 2721 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2722 | + }, | |
| 2723 | + "peerDependencies": { | |
| 2724 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2725 | + } | |
| 2726 | + }, | |
| 2727 | + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { | |
| 2728 | + "version": "2.0.2", | |
| 2729 | + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | |
| 2730 | + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | |
| 2731 | + "dev": true, | |
| 2732 | + "license": "MIT", | |
| 2733 | + "dependencies": { | |
| 2734 | + "balanced-match": "^1.0.0" | |
| 2735 | + } | |
| 2736 | + }, | |
| 2737 | + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { | |
| 2738 | + "version": "9.0.5", | |
| 2739 | + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", | |
| 2740 | + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", | |
| 2741 | + "dev": true, | |
| 2742 | + "license": "ISC", | |
| 2743 | + "dependencies": { | |
| 2744 | + "brace-expansion": "^2.0.1" | |
| 2745 | + }, | |
| 2746 | + "engines": { | |
| 2747 | + "node": ">=16 || 14 >=14.17" | |
| 2748 | + }, | |
| 2749 | + "funding": { | |
| 2750 | + "url": "https://github.com/sponsors/isaacs" | |
| 2751 | + } | |
| 2752 | + }, | |
| 2753 | + "node_modules/@typescript-eslint/utils": { | |
| 2754 | + "version": "8.55.0", | |
| 2755 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", | |
| 2756 | + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", | |
| 2757 | + "dev": true, | |
| 2758 | + "license": "MIT", | |
| 2759 | + "dependencies": { | |
| 2760 | + "@eslint-community/eslint-utils": "^4.9.1", | |
| 2761 | + "@typescript-eslint/scope-manager": "8.55.0", | |
| 2762 | + "@typescript-eslint/types": "8.55.0", | |
| 2763 | + "@typescript-eslint/typescript-estree": "8.55.0" | |
| 2764 | + }, | |
| 2765 | + "engines": { | |
| 2766 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2767 | + }, | |
| 2768 | + "funding": { | |
| 2769 | + "type": "opencollective", | |
| 2770 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2771 | + }, | |
| 2772 | + "peerDependencies": { | |
| 2773 | + "eslint": "^8.57.0 || ^9.0.0", | |
| 2774 | + "typescript": ">=4.8.4 <6.0.0" | |
| 2775 | + } | |
| 2776 | + }, | |
| 2777 | + "node_modules/@typescript-eslint/visitor-keys": { | |
| 2778 | + "version": "8.55.0", | |
| 2779 | + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", | |
| 2780 | + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", | |
| 2781 | + "dev": true, | |
| 2782 | + "license": "MIT", | |
| 2783 | + "dependencies": { | |
| 2784 | + "@typescript-eslint/types": "8.55.0", | |
| 2785 | + "eslint-visitor-keys": "^4.2.1" | |
| 2786 | + }, | |
| 2787 | + "engines": { | |
| 2788 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2789 | + }, | |
| 2790 | + "funding": { | |
| 2791 | + "type": "opencollective", | |
| 2792 | + "url": "https://opencollective.com/typescript-eslint" | |
| 2793 | + } | |
| 2794 | + }, | |
| 2795 | + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { | |
| 2796 | + "version": "4.2.1", | |
| 2797 | + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", | |
| 2798 | + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", | |
| 2799 | + "dev": true, | |
| 2800 | + "license": "Apache-2.0", | |
| 2801 | + "engines": { | |
| 2802 | + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" | |
| 2803 | + }, | |
| 2804 | + "funding": { | |
| 2805 | + "url": "https://opencollective.com/eslint" | |
| 2806 | + } | |
| 2807 | + }, | |
| 2808 | + "node_modules/@ungap/structured-clone": { | |
| 2809 | + "version": "1.3.0", | |
| 2810 | + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", | |
| 2811 | + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", | |
| 2812 | + "license": "ISC" | |
| 2813 | + }, | |
| 2814 | + "node_modules/@unrs/resolver-binding-android-arm-eabi": { | |
| 2815 | + "version": "1.11.1", | |
| 2816 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", | |
| 2817 | + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", | |
| 2818 | + "cpu": [ | |
| 2819 | + "arm" | |
| 2820 | + ], | |
| 2821 | + "dev": true, | |
| 2822 | + "license": "MIT", | |
| 2823 | + "optional": true, | |
| 2824 | + "os": [ | |
| 2825 | + "android" | |
| 2826 | + ] | |
| 2827 | + }, | |
| 2828 | + "node_modules/@unrs/resolver-binding-android-arm64": { | |
| 2829 | + "version": "1.11.1", | |
| 2830 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", | |
| 2831 | + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", | |
| 2832 | + "cpu": [ | |
| 2833 | + "arm64" | |
| 2834 | + ], | |
| 2835 | + "dev": true, | |
| 2836 | + "license": "MIT", | |
| 2837 | + "optional": true, | |
| 2838 | + "os": [ | |
| 2839 | + "android" | |
| 2840 | + ] | |
| 2841 | + }, | |
| 2842 | + "node_modules/@unrs/resolver-binding-darwin-arm64": { | |
| 2843 | + "version": "1.11.1", | |
| 2844 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", | |
| 2845 | + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", | |
| 2846 | + "cpu": [ | |
| 2847 | + "arm64" | |
| 2848 | + ], | |
| 2849 | + "dev": true, | |
| 2850 | + "license": "MIT", | |
| 2851 | + "optional": true, | |
| 2852 | + "os": [ | |
| 2853 | + "darwin" | |
| 2854 | + ] | |
| 2855 | + }, | |
| 2856 | + "node_modules/@unrs/resolver-binding-darwin-x64": { | |
| 2857 | + "version": "1.11.1", | |
| 2858 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", | |
| 2859 | + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", | |
| 2860 | + "cpu": [ | |
| 2861 | + "x64" | |
| 2862 | + ], | |
| 2863 | + "dev": true, | |
| 2864 | + "license": "MIT", | |
| 2865 | + "optional": true, | |
| 2866 | + "os": [ | |
| 2867 | + "darwin" | |
| 2868 | + ] | |
| 2869 | + }, | |
| 2870 | + "node_modules/@unrs/resolver-binding-freebsd-x64": { | |
| 2871 | + "version": "1.11.1", | |
| 2872 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", | |
| 2873 | + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", | |
| 2874 | + "cpu": [ | |
| 2875 | + "x64" | |
| 2876 | + ], | |
| 2877 | + "dev": true, | |
| 2878 | + "license": "MIT", | |
| 2879 | + "optional": true, | |
| 2880 | + "os": [ | |
| 2881 | + "freebsd" | |
| 2882 | + ] | |
| 2883 | + }, | |
| 2884 | + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { | |
| 2885 | + "version": "1.11.1", | |
| 2886 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", | |
| 2887 | + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", | |
| 2888 | + "cpu": [ | |
| 2889 | + "arm" | |
| 2890 | + ], | |
| 2891 | + "dev": true, | |
| 2892 | + "license": "MIT", | |
| 2893 | + "optional": true, | |
| 2894 | + "os": [ | |
| 2895 | + "linux" | |
| 2896 | + ] | |
| 2897 | + }, | |
| 2898 | + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { | |
| 2899 | + "version": "1.11.1", | |
| 2900 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", | |
| 2901 | + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", | |
| 2902 | + "cpu": [ | |
| 2903 | + "arm" | |
| 2904 | + ], | |
| 2905 | + "dev": true, | |
| 2906 | + "license": "MIT", | |
| 2907 | + "optional": true, | |
| 2908 | + "os": [ | |
| 2909 | + "linux" | |
| 2910 | + ] | |
| 2911 | + }, | |
| 2912 | + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { | |
| 2913 | + "version": "1.11.1", | |
| 2914 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", | |
| 2915 | + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", | |
| 2916 | + "cpu": [ | |
| 2917 | + "arm64" | |
| 2918 | + ], | |
| 2919 | + "dev": true, | |
| 2920 | + "license": "MIT", | |
| 2921 | + "optional": true, | |
| 2922 | + "os": [ | |
| 2923 | + "linux" | |
| 2924 | + ] | |
| 2925 | + }, | |
| 2926 | + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { | |
| 2927 | + "version": "1.11.1", | |
| 2928 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", | |
| 2929 | + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", | |
| 2930 | + "cpu": [ | |
| 2931 | + "arm64" | |
| 2932 | + ], | |
| 2933 | + "dev": true, | |
| 2934 | + "license": "MIT", | |
| 2935 | + "optional": true, | |
| 2936 | + "os": [ | |
| 2937 | + "linux" | |
| 2938 | + ] | |
| 2939 | + }, | |
| 2940 | + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { | |
| 2941 | + "version": "1.11.1", | |
| 2942 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", | |
| 2943 | + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", | |
| 2944 | + "cpu": [ | |
| 2945 | + "ppc64" | |
| 2946 | + ], | |
| 2947 | + "dev": true, | |
| 2948 | + "license": "MIT", | |
| 2949 | + "optional": true, | |
| 2950 | + "os": [ | |
| 2951 | + "linux" | |
| 2952 | + ] | |
| 2953 | + }, | |
| 2954 | + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { | |
| 2955 | + "version": "1.11.1", | |
| 2956 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", | |
| 2957 | + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", | |
| 2958 | + "cpu": [ | |
| 2959 | + "riscv64" | |
| 2960 | + ], | |
| 2961 | + "dev": true, | |
| 2962 | + "license": "MIT", | |
| 2963 | + "optional": true, | |
| 2964 | + "os": [ | |
| 2965 | + "linux" | |
| 2966 | + ] | |
| 2967 | + }, | |
| 2968 | + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { | |
| 2969 | + "version": "1.11.1", | |
| 2970 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", | |
| 2971 | + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", | |
| 2972 | + "cpu": [ | |
| 2973 | + "riscv64" | |
| 2974 | + ], | |
| 2975 | + "dev": true, | |
| 2976 | + "license": "MIT", | |
| 2977 | + "optional": true, | |
| 2978 | + "os": [ | |
| 2979 | + "linux" | |
| 2980 | + ] | |
| 2981 | + }, | |
| 2982 | + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { | |
| 2983 | + "version": "1.11.1", | |
| 2984 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", | |
| 2985 | + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", | |
| 2986 | + "cpu": [ | |
| 2987 | + "s390x" | |
| 2988 | + ], | |
| 2989 | + "dev": true, | |
| 2990 | + "license": "MIT", | |
| 2991 | + "optional": true, | |
| 2992 | + "os": [ | |
| 2993 | + "linux" | |
| 2994 | + ] | |
| 2995 | + }, | |
| 2996 | + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { | |
| 2997 | + "version": "1.11.1", | |
| 2998 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", | |
| 2999 | + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", | |
| 3000 | + "cpu": [ | |
| 3001 | + "x64" | |
| 3002 | + ], | |
| 3003 | + "dev": true, | |
| 3004 | + "license": "MIT", | |
| 3005 | + "optional": true, | |
| 3006 | + "os": [ | |
| 3007 | + "linux" | |
| 3008 | + ] | |
| 3009 | + }, | |
| 3010 | + "node_modules/@unrs/resolver-binding-linux-x64-musl": { | |
| 3011 | + "version": "1.11.1", | |
| 3012 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", | |
| 3013 | + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", | |
| 3014 | + "cpu": [ | |
| 3015 | + "x64" | |
| 3016 | + ], | |
| 3017 | + "dev": true, | |
| 3018 | + "license": "MIT", | |
| 3019 | + "optional": true, | |
| 3020 | + "os": [ | |
| 3021 | + "linux" | |
| 3022 | + ] | |
| 3023 | + }, | |
| 3024 | + "node_modules/@unrs/resolver-binding-wasm32-wasi": { | |
| 3025 | + "version": "1.11.1", | |
| 3026 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", | |
| 3027 | + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", | |
| 3028 | + "cpu": [ | |
| 3029 | + "wasm32" | |
| 3030 | + ], | |
| 3031 | + "dev": true, | |
| 3032 | + "license": "MIT", | |
| 3033 | + "optional": true, | |
| 3034 | + "dependencies": { | |
| 3035 | + "@napi-rs/wasm-runtime": "^0.2.11" | |
| 3036 | + }, | |
| 3037 | + "engines": { | |
| 3038 | + "node": ">=14.0.0" | |
| 3039 | + } | |
| 3040 | + }, | |
| 3041 | + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { | |
| 3042 | + "version": "1.11.1", | |
| 3043 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", | |
| 3044 | + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", | |
| 3045 | + "cpu": [ | |
| 3046 | + "arm64" | |
| 3047 | + ], | |
| 3048 | + "dev": true, | |
| 3049 | + "license": "MIT", | |
| 3050 | + "optional": true, | |
| 3051 | + "os": [ | |
| 3052 | + "win32" | |
| 3053 | + ] | |
| 3054 | + }, | |
| 3055 | + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { | |
| 3056 | + "version": "1.11.1", | |
| 3057 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", | |
| 3058 | + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", | |
| 3059 | + "cpu": [ | |
| 3060 | + "ia32" | |
| 3061 | + ], | |
| 3062 | + "dev": true, | |
| 3063 | + "license": "MIT", | |
| 3064 | + "optional": true, | |
| 3065 | + "os": [ | |
| 3066 | + "win32" | |
| 3067 | + ] | |
| 3068 | + }, | |
| 3069 | + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { | |
| 3070 | + "version": "1.11.1", | |
| 3071 | + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", | |
| 3072 | + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", | |
| 3073 | + "cpu": [ | |
| 3074 | + "x64" | |
| 3075 | + ], | |
| 3076 | + "dev": true, | |
| 3077 | + "license": "MIT", | |
| 3078 | + "optional": true, | |
| 3079 | + "os": [ | |
| 3080 | + "win32" | |
| 3081 | + ] | |
| 3082 | + }, | |
| 3083 | + "node_modules/abort-controller": { | |
| 3084 | + "version": "3.0.0", | |
| 3085 | + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", | |
| 3086 | + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", | |
| 3087 | + "license": "MIT", | |
| 3088 | + "dependencies": { | |
| 3089 | + "event-target-shim": "^5.0.0" | |
| 3090 | + }, | |
| 3091 | + "engines": { | |
| 3092 | + "node": ">=6.5" | |
| 3093 | + } | |
| 3094 | + }, | |
| 3095 | + "node_modules/acorn": { | |
| 3096 | + "version": "8.15.0", | |
| 3097 | + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", | |
| 3098 | + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", | |
| 3099 | + "dev": true, | |
| 3100 | + "license": "MIT", | |
| 3101 | + "bin": { | |
| 3102 | + "acorn": "bin/acorn" | |
| 3103 | + }, | |
| 3104 | + "engines": { | |
| 3105 | + "node": ">=0.4.0" | |
| 3106 | + } | |
| 3107 | + }, | |
| 3108 | + "node_modules/acorn-jsx": { | |
| 3109 | + "version": "5.3.2", | |
| 3110 | + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", | |
| 3111 | + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", | |
| 3112 | + "dev": true, | |
| 3113 | + "license": "MIT", | |
| 3114 | + "peerDependencies": { | |
| 3115 | + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" | |
| 3116 | + } | |
| 3117 | + }, | |
| 3118 | + "node_modules/ajv": { | |
| 3119 | + "version": "6.12.6", | |
| 3120 | + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", | |
| 3121 | + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", | |
| 3122 | + "dev": true, | |
| 3123 | + "license": "MIT", | |
| 3124 | + "dependencies": { | |
| 3125 | + "fast-deep-equal": "^3.1.1", | |
| 3126 | + "fast-json-stable-stringify": "^2.0.0", | |
| 3127 | + "json-schema-traverse": "^0.4.1", | |
| 3128 | + "uri-js": "^4.2.2" | |
| 3129 | + }, | |
| 3130 | + "funding": { | |
| 3131 | + "type": "github", | |
| 3132 | + "url": "https://github.com/sponsors/epoberezkin" | |
| 3133 | + } | |
| 3134 | + }, | |
| 3135 | + "node_modules/ansi-regex": { | |
| 3136 | + "version": "5.0.1", | |
| 3137 | + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | |
| 3138 | + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", | |
| 3139 | + "license": "MIT", | |
| 3140 | + "engines": { | |
| 3141 | + "node": ">=8" | |
| 3142 | + } | |
| 3143 | + }, | |
| 3144 | + "node_modules/ansi-styles": { | |
| 3145 | + "version": "4.3.0", | |
| 3146 | + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | |
| 3147 | + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | |
| 3148 | + "license": "MIT", | |
| 3149 | + "dependencies": { | |
| 3150 | + "color-convert": "^2.0.1" | |
| 3151 | + }, | |
| 3152 | + "engines": { | |
| 3153 | + "node": ">=8" | |
| 3154 | + }, | |
| 3155 | + "funding": { | |
| 3156 | + "url": "https://github.com/chalk/ansi-styles?sponsor=1" | |
| 3157 | + } | |
| 3158 | + }, | |
| 3159 | + "node_modules/any-promise": { | |
| 3160 | + "version": "1.3.0", | |
| 3161 | + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", | |
| 3162 | + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", | |
| 3163 | + "dev": true, | |
| 3164 | + "license": "MIT" | |
| 3165 | + }, | |
| 3166 | + "node_modules/anymatch": { | |
| 3167 | + "version": "3.1.3", | |
| 3168 | + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", | |
| 3169 | + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", | |
| 3170 | + "dev": true, | |
| 3171 | + "license": "ISC", | |
| 3172 | + "dependencies": { | |
| 3173 | + "normalize-path": "^3.0.0", | |
| 3174 | + "picomatch": "^2.0.4" | |
| 3175 | + }, | |
| 3176 | + "engines": { | |
| 3177 | + "node": ">= 8" | |
| 3178 | + } | |
| 3179 | + }, | |
| 3180 | + "node_modules/archiver": { | |
| 3181 | + "version": "7.0.1", | |
| 3182 | + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", | |
| 3183 | + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", | |
| 3184 | + "license": "MIT", | |
| 3185 | + "dependencies": { | |
| 3186 | + "archiver-utils": "^5.0.2", | |
| 3187 | + "async": "^3.2.4", | |
| 3188 | + "buffer-crc32": "^1.0.0", | |
| 3189 | + "readable-stream": "^4.0.0", | |
| 3190 | + "readdir-glob": "^1.1.2", | |
| 3191 | + "tar-stream": "^3.0.0", | |
| 3192 | + "zip-stream": "^6.0.1" | |
| 3193 | + }, | |
| 3194 | + "engines": { | |
| 3195 | + "node": ">= 14" | |
| 3196 | + } | |
| 3197 | + }, | |
| 3198 | + "node_modules/archiver-utils": { | |
| 3199 | + "version": "5.0.2", | |
| 3200 | + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", | |
| 3201 | + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", | |
| 3202 | + "license": "MIT", | |
| 3203 | + "dependencies": { | |
| 3204 | + "glob": "^10.0.0", | |
| 3205 | + "graceful-fs": "^4.2.0", | |
| 3206 | + "is-stream": "^2.0.1", | |
| 3207 | + "lazystream": "^1.0.0", | |
| 3208 | + "lodash": "^4.17.15", | |
| 3209 | + "normalize-path": "^3.0.0", | |
| 3210 | + "readable-stream": "^4.0.0" | |
| 3211 | + }, | |
| 3212 | + "engines": { | |
| 3213 | + "node": ">= 14" | |
| 3214 | + } | |
| 3215 | + }, | |
| 3216 | + "node_modules/archiver-utils/node_modules/readable-stream": { | |
| 3217 | + "version": "4.7.0", | |
| 3218 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", | |
| 3219 | + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", | |
| 3220 | + "license": "MIT", | |
| 3221 | + "dependencies": { | |
| 3222 | + "abort-controller": "^3.0.0", | |
| 3223 | + "buffer": "^6.0.3", | |
| 3224 | + "events": "^3.3.0", | |
| 3225 | + "process": "^0.11.10", | |
| 3226 | + "string_decoder": "^1.3.0" | |
| 3227 | + }, | |
| 3228 | + "engines": { | |
| 3229 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 3230 | + } | |
| 3231 | + }, | |
| 3232 | + "node_modules/archiver-utils/node_modules/safe-buffer": { | |
| 3233 | + "version": "5.2.1", | |
| 3234 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", | |
| 3235 | + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", | |
| 3236 | + "funding": [ | |
| 3237 | + { | |
| 3238 | + "type": "github", | |
| 3239 | + "url": "https://github.com/sponsors/feross" | |
| 3240 | + }, | |
| 3241 | + { | |
| 3242 | + "type": "patreon", | |
| 3243 | + "url": "https://www.patreon.com/feross" | |
| 3244 | + }, | |
| 3245 | + { | |
| 3246 | + "type": "consulting", | |
| 3247 | + "url": "https://feross.org/support" | |
| 3248 | + } | |
| 3249 | + ], | |
| 3250 | + "license": "MIT" | |
| 3251 | + }, | |
| 3252 | + "node_modules/archiver-utils/node_modules/string_decoder": { | |
| 3253 | + "version": "1.3.0", | |
| 3254 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | |
| 3255 | + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | |
| 3256 | + "license": "MIT", | |
| 3257 | + "dependencies": { | |
| 3258 | + "safe-buffer": "~5.2.0" | |
| 3259 | + } | |
| 3260 | + }, | |
| 3261 | + "node_modules/archiver/node_modules/readable-stream": { | |
| 3262 | + "version": "4.7.0", | |
| 3263 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", | |
| 3264 | + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", | |
| 3265 | + "license": "MIT", | |
| 3266 | + "dependencies": { | |
| 3267 | + "abort-controller": "^3.0.0", | |
| 3268 | + "buffer": "^6.0.3", | |
| 3269 | + "events": "^3.3.0", | |
| 3270 | + "process": "^0.11.10", | |
| 3271 | + "string_decoder": "^1.3.0" | |
| 3272 | + }, | |
| 3273 | + "engines": { | |
| 3274 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 3275 | + } | |
| 3276 | + }, | |
| 3277 | + "node_modules/archiver/node_modules/safe-buffer": { | |
| 3278 | + "version": "5.2.1", | |
| 3279 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", | |
| 3280 | + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", | |
| 3281 | + "funding": [ | |
| 3282 | + { | |
| 3283 | + "type": "github", | |
| 3284 | + "url": "https://github.com/sponsors/feross" | |
| 3285 | + }, | |
| 3286 | + { | |
| 3287 | + "type": "patreon", | |
| 3288 | + "url": "https://www.patreon.com/feross" | |
| 3289 | + }, | |
| 3290 | + { | |
| 3291 | + "type": "consulting", | |
| 3292 | + "url": "https://feross.org/support" | |
| 3293 | + } | |
| 3294 | + ], | |
| 3295 | + "license": "MIT" | |
| 3296 | + }, | |
| 3297 | + "node_modules/archiver/node_modules/string_decoder": { | |
| 3298 | + "version": "1.3.0", | |
| 3299 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | |
| 3300 | + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | |
| 3301 | + "license": "MIT", | |
| 3302 | + "dependencies": { | |
| 3303 | + "safe-buffer": "~5.2.0" | |
| 3304 | + } | |
| 3305 | + }, | |
| 3306 | + "node_modules/arg": { | |
| 3307 | + "version": "5.0.2", | |
| 3308 | + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", | |
| 3309 | + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", | |
| 3310 | + "dev": true, | |
| 3311 | + "license": "MIT" | |
| 3312 | + }, | |
| 3313 | + "node_modules/argparse": { | |
| 3314 | + "version": "2.0.1", | |
| 3315 | + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", | |
| 3316 | + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", | |
| 3317 | + "dev": true, | |
| 3318 | + "license": "Python-2.0" | |
| 3319 | + }, | |
| 3320 | + "node_modules/aria-query": { | |
| 3321 | + "version": "5.3.2", | |
| 3322 | + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", | |
| 3323 | + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", | |
| 3324 | + "dev": true, | |
| 3325 | + "license": "Apache-2.0", | |
| 3326 | + "engines": { | |
| 3327 | + "node": ">= 0.4" | |
| 3328 | + } | |
| 3329 | + }, | |
| 3330 | + "node_modules/array-buffer-byte-length": { | |
| 3331 | + "version": "1.0.2", | |
| 3332 | + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", | |
| 3333 | + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", | |
| 3334 | + "dev": true, | |
| 3335 | + "license": "MIT", | |
| 3336 | + "dependencies": { | |
| 3337 | + "call-bound": "^1.0.3", | |
| 3338 | + "is-array-buffer": "^3.0.5" | |
| 3339 | + }, | |
| 3340 | + "engines": { | |
| 3341 | + "node": ">= 0.4" | |
| 3342 | + }, | |
| 3343 | + "funding": { | |
| 3344 | + "url": "https://github.com/sponsors/ljharb" | |
| 3345 | + } | |
| 3346 | + }, | |
| 3347 | + "node_modules/array-includes": { | |
| 3348 | + "version": "3.1.9", | |
| 3349 | + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", | |
| 3350 | + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", | |
| 3351 | + "dev": true, | |
| 3352 | + "license": "MIT", | |
| 3353 | + "dependencies": { | |
| 3354 | + "call-bind": "^1.0.8", | |
| 3355 | + "call-bound": "^1.0.4", | |
| 3356 | + "define-properties": "^1.2.1", | |
| 3357 | + "es-abstract": "^1.24.0", | |
| 3358 | + "es-object-atoms": "^1.1.1", | |
| 3359 | + "get-intrinsic": "^1.3.0", | |
| 3360 | + "is-string": "^1.1.1", | |
| 3361 | + "math-intrinsics": "^1.1.0" | |
| 3362 | + }, | |
| 3363 | + "engines": { | |
| 3364 | + "node": ">= 0.4" | |
| 3365 | + }, | |
| 3366 | + "funding": { | |
| 3367 | + "url": "https://github.com/sponsors/ljharb" | |
| 3368 | + } | |
| 3369 | + }, | |
| 3370 | + "node_modules/array.prototype.findlast": { | |
| 3371 | + "version": "1.2.5", | |
| 3372 | + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", | |
| 3373 | + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", | |
| 3374 | + "dev": true, | |
| 3375 | + "license": "MIT", | |
| 3376 | + "dependencies": { | |
| 3377 | + "call-bind": "^1.0.7", | |
| 3378 | + "define-properties": "^1.2.1", | |
| 3379 | + "es-abstract": "^1.23.2", | |
| 3380 | + "es-errors": "^1.3.0", | |
| 3381 | + "es-object-atoms": "^1.0.0", | |
| 3382 | + "es-shim-unscopables": "^1.0.2" | |
| 3383 | + }, | |
| 3384 | + "engines": { | |
| 3385 | + "node": ">= 0.4" | |
| 3386 | + }, | |
| 3387 | + "funding": { | |
| 3388 | + "url": "https://github.com/sponsors/ljharb" | |
| 3389 | + } | |
| 3390 | + }, | |
| 3391 | + "node_modules/array.prototype.findlastindex": { | |
| 3392 | + "version": "1.2.6", | |
| 3393 | + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", | |
| 3394 | + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", | |
| 3395 | + "dev": true, | |
| 3396 | + "license": "MIT", | |
| 3397 | + "dependencies": { | |
| 3398 | + "call-bind": "^1.0.8", | |
| 3399 | + "call-bound": "^1.0.4", | |
| 3400 | + "define-properties": "^1.2.1", | |
| 3401 | + "es-abstract": "^1.23.9", | |
| 3402 | + "es-errors": "^1.3.0", | |
| 3403 | + "es-object-atoms": "^1.1.1", | |
| 3404 | + "es-shim-unscopables": "^1.1.0" | |
| 3405 | + }, | |
| 3406 | + "engines": { | |
| 3407 | + "node": ">= 0.4" | |
| 3408 | + }, | |
| 3409 | + "funding": { | |
| 3410 | + "url": "https://github.com/sponsors/ljharb" | |
| 3411 | + } | |
| 3412 | + }, | |
| 3413 | + "node_modules/array.prototype.flat": { | |
| 3414 | + "version": "1.3.3", | |
| 3415 | + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", | |
| 3416 | + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", | |
| 3417 | + "dev": true, | |
| 3418 | + "license": "MIT", | |
| 3419 | + "dependencies": { | |
| 3420 | + "call-bind": "^1.0.8", | |
| 3421 | + "define-properties": "^1.2.1", | |
| 3422 | + "es-abstract": "^1.23.5", | |
| 3423 | + "es-shim-unscopables": "^1.0.2" | |
| 3424 | + }, | |
| 3425 | + "engines": { | |
| 3426 | + "node": ">= 0.4" | |
| 3427 | + }, | |
| 3428 | + "funding": { | |
| 3429 | + "url": "https://github.com/sponsors/ljharb" | |
| 3430 | + } | |
| 3431 | + }, | |
| 3432 | + "node_modules/array.prototype.flatmap": { | |
| 3433 | + "version": "1.3.3", | |
| 3434 | + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", | |
| 3435 | + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", | |
| 3436 | + "dev": true, | |
| 3437 | + "license": "MIT", | |
| 3438 | + "dependencies": { | |
| 3439 | + "call-bind": "^1.0.8", | |
| 3440 | + "define-properties": "^1.2.1", | |
| 3441 | + "es-abstract": "^1.23.5", | |
| 3442 | + "es-shim-unscopables": "^1.0.2" | |
| 3443 | + }, | |
| 3444 | + "engines": { | |
| 3445 | + "node": ">= 0.4" | |
| 3446 | + }, | |
| 3447 | + "funding": { | |
| 3448 | + "url": "https://github.com/sponsors/ljharb" | |
| 3449 | + } | |
| 3450 | + }, | |
| 3451 | + "node_modules/array.prototype.tosorted": { | |
| 3452 | + "version": "1.1.4", | |
| 3453 | + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", | |
| 3454 | + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", | |
| 3455 | + "dev": true, | |
| 3456 | + "license": "MIT", | |
| 3457 | + "dependencies": { | |
| 3458 | + "call-bind": "^1.0.7", | |
| 3459 | + "define-properties": "^1.2.1", | |
| 3460 | + "es-abstract": "^1.23.3", | |
| 3461 | + "es-errors": "^1.3.0", | |
| 3462 | + "es-shim-unscopables": "^1.0.2" | |
| 3463 | + }, | |
| 3464 | + "engines": { | |
| 3465 | + "node": ">= 0.4" | |
| 3466 | + } | |
| 3467 | + }, | |
| 3468 | + "node_modules/arraybuffer.prototype.slice": { | |
| 3469 | + "version": "1.0.4", | |
| 3470 | + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", | |
| 3471 | + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", | |
| 3472 | + "dev": true, | |
| 3473 | + "license": "MIT", | |
| 3474 | + "dependencies": { | |
| 3475 | + "array-buffer-byte-length": "^1.0.1", | |
| 3476 | + "call-bind": "^1.0.8", | |
| 3477 | + "define-properties": "^1.2.1", | |
| 3478 | + "es-abstract": "^1.23.5", | |
| 3479 | + "es-errors": "^1.3.0", | |
| 3480 | + "get-intrinsic": "^1.2.6", | |
| 3481 | + "is-array-buffer": "^3.0.4" | |
| 3482 | + }, | |
| 3483 | + "engines": { | |
| 3484 | + "node": ">= 0.4" | |
| 3485 | + }, | |
| 3486 | + "funding": { | |
| 3487 | + "url": "https://github.com/sponsors/ljharb" | |
| 3488 | + } | |
| 3489 | + }, | |
| 3490 | + "node_modules/ast-types-flow": { | |
| 3491 | + "version": "0.0.8", | |
| 3492 | + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", | |
| 3493 | + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", | |
| 3494 | + "dev": true, | |
| 3495 | + "license": "MIT" | |
| 3496 | + }, | |
| 3497 | + "node_modules/async": { | |
| 3498 | + "version": "3.2.6", | |
| 3499 | + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", | |
| 3500 | + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", | |
| 3501 | + "license": "MIT" | |
| 3502 | + }, | |
| 3503 | + "node_modules/async-function": { | |
| 3504 | + "version": "1.0.0", | |
| 3505 | + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", | |
| 3506 | + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", | |
| 3507 | + "dev": true, | |
| 3508 | + "license": "MIT", | |
| 3509 | + "engines": { | |
| 3510 | + "node": ">= 0.4" | |
| 3511 | + } | |
| 3512 | + }, | |
| 3513 | + "node_modules/attr-accept": { | |
| 3514 | + "version": "2.2.5", | |
| 3515 | + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", | |
| 3516 | + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", | |
| 3517 | + "license": "MIT", | |
| 3518 | + "engines": { | |
| 3519 | + "node": ">=4" | |
| 3520 | + } | |
| 3521 | + }, | |
| 3522 | + "node_modules/available-typed-arrays": { | |
| 3523 | + "version": "1.0.7", | |
| 3524 | + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", | |
| 3525 | + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", | |
| 3526 | + "dev": true, | |
| 3527 | + "license": "MIT", | |
| 3528 | + "dependencies": { | |
| 3529 | + "possible-typed-array-names": "^1.0.0" | |
| 3530 | + }, | |
| 3531 | + "engines": { | |
| 3532 | + "node": ">= 0.4" | |
| 3533 | + }, | |
| 3534 | + "funding": { | |
| 3535 | + "url": "https://github.com/sponsors/ljharb" | |
| 3536 | + } | |
| 3537 | + }, | |
| 3538 | + "node_modules/axe-core": { | |
| 3539 | + "version": "4.11.1", | |
| 3540 | + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", | |
| 3541 | + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", | |
| 3542 | + "dev": true, | |
| 3543 | + "license": "MPL-2.0", | |
| 3544 | + "engines": { | |
| 3545 | + "node": ">=4" | |
| 3546 | + } | |
| 3547 | + }, | |
| 3548 | + "node_modules/axobject-query": { | |
| 3549 | + "version": "4.1.0", | |
| 3550 | + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", | |
| 3551 | + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", | |
| 3552 | + "dev": true, | |
| 3553 | + "license": "Apache-2.0", | |
| 3554 | + "engines": { | |
| 3555 | + "node": ">= 0.4" | |
| 3556 | + } | |
| 3557 | + }, | |
| 3558 | + "node_modules/bail": { | |
| 3559 | + "version": "2.0.2", | |
| 3560 | + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", | |
| 3561 | + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", | |
| 3562 | + "license": "MIT", | |
| 3563 | + "funding": { | |
| 3564 | + "type": "github", | |
| 3565 | + "url": "https://github.com/sponsors/wooorm" | |
| 3566 | + } | |
| 3567 | + }, | |
| 3568 | + "node_modules/balanced-match": { | |
| 3569 | + "version": "1.0.2", | |
| 3570 | + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | |
| 3571 | + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", | |
| 3572 | + "license": "MIT" | |
| 3573 | + }, | |
| 3574 | + "node_modules/bare-events": { | |
| 3575 | + "version": "2.8.2", | |
| 3576 | + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 3577 | + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 3578 | + "license": "Apache-2.0", | |
| 3579 | + "peerDependencies": { | |
| 3580 | + "bare-abort-controller": "*" | |
| 3581 | + }, | |
| 3582 | + "peerDependenciesMeta": { | |
| 3583 | + "bare-abort-controller": { | |
| 3584 | + "optional": true | |
| 3585 | + } | |
| 3586 | + } | |
| 3587 | + }, | |
| 3588 | + "node_modules/base64-js": { | |
| 3589 | + "version": "1.5.1", | |
| 3590 | + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", | |
| 3591 | + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", | |
| 3592 | + "funding": [ | |
| 3593 | + { | |
| 3594 | + "type": "github", | |
| 3595 | + "url": "https://github.com/sponsors/feross" | |
| 3596 | + }, | |
| 3597 | + { | |
| 3598 | + "type": "patreon", | |
| 3599 | + "url": "https://www.patreon.com/feross" | |
| 3600 | + }, | |
| 3601 | + { | |
| 3602 | + "type": "consulting", | |
| 3603 | + "url": "https://feross.org/support" | |
| 3604 | + } | |
| 3605 | + ], | |
| 3606 | + "license": "MIT" | |
| 3607 | + }, | |
| 3608 | + "node_modules/bcryptjs": { | |
| 3609 | + "version": "3.0.3", | |
| 3610 | + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", | |
| 3611 | + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", | |
| 3612 | + "license": "BSD-3-Clause", | |
| 3613 | + "bin": { | |
| 3614 | + "bcrypt": "bin/bcrypt" | |
| 3615 | + } | |
| 3616 | + }, | |
| 3617 | + "node_modules/binary-extensions": { | |
| 3618 | + "version": "2.3.0", | |
| 3619 | + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", | |
| 3620 | + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", | |
| 3621 | + "dev": true, | |
| 3622 | + "license": "MIT", | |
| 3623 | + "engines": { | |
| 3624 | + "node": ">=8" | |
| 3625 | + }, | |
| 3626 | + "funding": { | |
| 3627 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 3628 | + } | |
| 3629 | + }, | |
| 3630 | + "node_modules/brace-expansion": { | |
| 3631 | + "version": "1.1.12", | |
| 3632 | + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", | |
| 3633 | + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", | |
| 3634 | + "dev": true, | |
| 3635 | + "license": "MIT", | |
| 3636 | + "dependencies": { | |
| 3637 | + "balanced-match": "^1.0.0", | |
| 3638 | + "concat-map": "0.0.1" | |
| 3639 | + } | |
| 3640 | + }, | |
| 3641 | + "node_modules/braces": { | |
| 3642 | + "version": "3.0.3", | |
| 3643 | + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", | |
| 3644 | + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", | |
| 3645 | + "dev": true, | |
| 3646 | + "license": "MIT", | |
| 3647 | + "dependencies": { | |
| 3648 | + "fill-range": "^7.1.1" | |
| 3649 | + }, | |
| 3650 | + "engines": { | |
| 3651 | + "node": ">=8" | |
| 3652 | + } | |
| 3653 | + }, | |
| 3654 | + "node_modules/buffer": { | |
| 3655 | + "version": "6.0.3", | |
| 3656 | + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", | |
| 3657 | + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", | |
| 3658 | + "funding": [ | |
| 3659 | + { | |
| 3660 | + "type": "github", | |
| 3661 | + "url": "https://github.com/sponsors/feross" | |
| 3662 | + }, | |
| 3663 | + { | |
| 3664 | + "type": "patreon", | |
| 3665 | + "url": "https://www.patreon.com/feross" | |
| 3666 | + }, | |
| 3667 | + { | |
| 3668 | + "type": "consulting", | |
| 3669 | + "url": "https://feross.org/support" | |
| 3670 | + } | |
| 3671 | + ], | |
| 3672 | + "license": "MIT", | |
| 3673 | + "dependencies": { | |
| 3674 | + "base64-js": "^1.3.1", | |
| 3675 | + "ieee754": "^1.2.1" | |
| 3676 | + } | |
| 3677 | + }, | |
| 3678 | + "node_modules/buffer-crc32": { | |
| 3679 | + "version": "1.0.0", | |
| 3680 | + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", | |
| 3681 | + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", | |
| 3682 | + "license": "MIT", | |
| 3683 | + "engines": { | |
| 3684 | + "node": ">=8.0.0" | |
| 3685 | + } | |
| 3686 | + }, | |
| 3687 | + "node_modules/busboy": { | |
| 3688 | + "version": "1.6.0", | |
| 3689 | + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", | |
| 3690 | + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", | |
| 3691 | + "dependencies": { | |
| 3692 | + "streamsearch": "^1.1.0" | |
| 3693 | + }, | |
| 3694 | + "engines": { | |
| 3695 | + "node": ">=10.16.0" | |
| 3696 | + } | |
| 3697 | + }, | |
| 3698 | + "node_modules/call-bind": { | |
| 3699 | + "version": "1.0.8", | |
| 3700 | + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", | |
| 3701 | + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", | |
| 3702 | + "dev": true, | |
| 3703 | + "license": "MIT", | |
| 3704 | + "dependencies": { | |
| 3705 | + "call-bind-apply-helpers": "^1.0.0", | |
| 3706 | + "es-define-property": "^1.0.0", | |
| 3707 | + "get-intrinsic": "^1.2.4", | |
| 3708 | + "set-function-length": "^1.2.2" | |
| 3709 | + }, | |
| 3710 | + "engines": { | |
| 3711 | + "node": ">= 0.4" | |
| 3712 | + }, | |
| 3713 | + "funding": { | |
| 3714 | + "url": "https://github.com/sponsors/ljharb" | |
| 3715 | + } | |
| 3716 | + }, | |
| 3717 | + "node_modules/call-bind-apply-helpers": { | |
| 3718 | + "version": "1.0.2", | |
| 3719 | + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", | |
| 3720 | + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", | |
| 3721 | + "dev": true, | |
| 3722 | + "license": "MIT", | |
| 3723 | + "dependencies": { | |
| 3724 | + "es-errors": "^1.3.0", | |
| 3725 | + "function-bind": "^1.1.2" | |
| 3726 | + }, | |
| 3727 | + "engines": { | |
| 3728 | + "node": ">= 0.4" | |
| 3729 | + } | |
| 3730 | + }, | |
| 3731 | + "node_modules/call-bound": { | |
| 3732 | + "version": "1.0.4", | |
| 3733 | + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", | |
| 3734 | + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", | |
| 3735 | + "dev": true, | |
| 3736 | + "license": "MIT", | |
| 3737 | + "dependencies": { | |
| 3738 | + "call-bind-apply-helpers": "^1.0.2", | |
| 3739 | + "get-intrinsic": "^1.3.0" | |
| 3740 | + }, | |
| 3741 | + "engines": { | |
| 3742 | + "node": ">= 0.4" | |
| 3743 | + }, | |
| 3744 | + "funding": { | |
| 3745 | + "url": "https://github.com/sponsors/ljharb" | |
| 3746 | + } | |
| 3747 | + }, | |
| 3748 | + "node_modules/callsites": { | |
| 3749 | + "version": "3.1.0", | |
| 3750 | + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", | |
| 3751 | + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", | |
| 3752 | + "dev": true, | |
| 3753 | + "license": "MIT", | |
| 3754 | + "engines": { | |
| 3755 | + "node": ">=6" | |
| 3756 | + } | |
| 3757 | + }, | |
| 3758 | + "node_modules/camelcase": { | |
| 3759 | + "version": "5.3.1", | |
| 3760 | + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", | |
| 3761 | + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", | |
| 3762 | + "license": "MIT", | |
| 3763 | + "engines": { | |
| 3764 | + "node": ">=6" | |
| 3765 | + } | |
| 3766 | + }, | |
| 3767 | + "node_modules/camelcase-css": { | |
| 3768 | + "version": "2.0.1", | |
| 3769 | + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", | |
| 3770 | + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", | |
| 3771 | + "dev": true, | |
| 3772 | + "license": "MIT", | |
| 3773 | + "engines": { | |
| 3774 | + "node": ">= 6" | |
| 3775 | + } | |
| 3776 | + }, | |
| 3777 | + "node_modules/caniuse-lite": { | |
| 3778 | + "version": "1.0.30001769", | |
| 3779 | + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", | |
| 3780 | + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", | |
| 3781 | + "funding": [ | |
| 3782 | + { | |
| 3783 | + "type": "opencollective", | |
| 3784 | + "url": "https://opencollective.com/browserslist" | |
| 3785 | + }, | |
| 3786 | + { | |
| 3787 | + "type": "tidelift", | |
| 3788 | + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" | |
| 3789 | + }, | |
| 3790 | + { | |
| 3791 | + "type": "github", | |
| 3792 | + "url": "https://github.com/sponsors/ai" | |
| 3793 | + } | |
| 3794 | + ], | |
| 3795 | + "license": "CC-BY-4.0" | |
| 3796 | + }, | |
| 3797 | + "node_modules/ccount": { | |
| 3798 | + "version": "2.0.1", | |
| 3799 | + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", | |
| 3800 | + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", | |
| 3801 | + "license": "MIT", | |
| 3802 | + "funding": { | |
| 3803 | + "type": "github", | |
| 3804 | + "url": "https://github.com/sponsors/wooorm" | |
| 3805 | + } | |
| 3806 | + }, | |
| 3807 | + "node_modules/chalk": { | |
| 3808 | + "version": "4.1.2", | |
| 3809 | + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", | |
| 3810 | + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", | |
| 3811 | + "dev": true, | |
| 3812 | + "license": "MIT", | |
| 3813 | + "dependencies": { | |
| 3814 | + "ansi-styles": "^4.1.0", | |
| 3815 | + "supports-color": "^7.1.0" | |
| 3816 | + }, | |
| 3817 | + "engines": { | |
| 3818 | + "node": ">=10" | |
| 3819 | + }, | |
| 3820 | + "funding": { | |
| 3821 | + "url": "https://github.com/chalk/chalk?sponsor=1" | |
| 3822 | + } | |
| 3823 | + }, | |
| 3824 | + "node_modules/character-entities": { | |
| 3825 | + "version": "2.0.2", | |
| 3826 | + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", | |
| 3827 | + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", | |
| 3828 | + "license": "MIT", | |
| 3829 | + "funding": { | |
| 3830 | + "type": "github", | |
| 3831 | + "url": "https://github.com/sponsors/wooorm" | |
| 3832 | + } | |
| 3833 | + }, | |
| 3834 | + "node_modules/character-entities-html4": { | |
| 3835 | + "version": "2.1.0", | |
| 3836 | + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", | |
| 3837 | + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", | |
| 3838 | + "license": "MIT", | |
| 3839 | + "funding": { | |
| 3840 | + "type": "github", | |
| 3841 | + "url": "https://github.com/sponsors/wooorm" | |
| 3842 | + } | |
| 3843 | + }, | |
| 3844 | + "node_modules/character-entities-legacy": { | |
| 3845 | + "version": "3.0.0", | |
| 3846 | + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", | |
| 3847 | + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", | |
| 3848 | + "license": "MIT", | |
| 3849 | + "funding": { | |
| 3850 | + "type": "github", | |
| 3851 | + "url": "https://github.com/sponsors/wooorm" | |
| 3852 | + } | |
| 3853 | + }, | |
| 3854 | + "node_modules/character-reference-invalid": { | |
| 3855 | + "version": "2.0.1", | |
| 3856 | + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", | |
| 3857 | + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", | |
| 3858 | + "license": "MIT", | |
| 3859 | + "funding": { | |
| 3860 | + "type": "github", | |
| 3861 | + "url": "https://github.com/sponsors/wooorm" | |
| 3862 | + } | |
| 3863 | + }, | |
| 3864 | + "node_modules/chokidar": { | |
| 3865 | + "version": "3.6.0", | |
| 3866 | + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", | |
| 3867 | + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", | |
| 3868 | + "dev": true, | |
| 3869 | + "license": "MIT", | |
| 3870 | + "dependencies": { | |
| 3871 | + "anymatch": "~3.1.2", | |
| 3872 | + "braces": "~3.0.2", | |
| 3873 | + "glob-parent": "~5.1.2", | |
| 3874 | + "is-binary-path": "~2.1.0", | |
| 3875 | + "is-glob": "~4.0.1", | |
| 3876 | + "normalize-path": "~3.0.0", | |
| 3877 | + "readdirp": "~3.6.0" | |
| 3878 | + }, | |
| 3879 | + "engines": { | |
| 3880 | + "node": ">= 8.10.0" | |
| 3881 | + }, | |
| 3882 | + "funding": { | |
| 3883 | + "url": "https://paulmillr.com/funding/" | |
| 3884 | + }, | |
| 3885 | + "optionalDependencies": { | |
| 3886 | + "fsevents": "~2.3.2" | |
| 3887 | + } | |
| 3888 | + }, | |
| 3889 | + "node_modules/chokidar/node_modules/glob-parent": { | |
| 3890 | + "version": "5.1.2", | |
| 3891 | + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", | |
| 3892 | + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", | |
| 3893 | + "dev": true, | |
| 3894 | + "license": "ISC", | |
| 3895 | + "dependencies": { | |
| 3896 | + "is-glob": "^4.0.1" | |
| 3897 | + }, | |
| 3898 | + "engines": { | |
| 3899 | + "node": ">= 6" | |
| 3900 | + } | |
| 3901 | + }, | |
| 3902 | + "node_modules/client-only": { | |
| 3903 | + "version": "0.0.1", | |
| 3904 | + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", | |
| 3905 | + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", | |
| 3906 | + "license": "MIT" | |
| 3907 | + }, | |
| 3908 | + "node_modules/cliui": { | |
| 3909 | + "version": "6.0.0", | |
| 3910 | + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", | |
| 3911 | + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", | |
| 3912 | + "license": "ISC", | |
| 3913 | + "dependencies": { | |
| 3914 | + "string-width": "^4.2.0", | |
| 3915 | + "strip-ansi": "^6.0.0", | |
| 3916 | + "wrap-ansi": "^6.2.0" | |
| 3917 | + } | |
| 3918 | + }, | |
| 3919 | + "node_modules/cliui/node_modules/emoji-regex": { | |
| 3920 | + "version": "8.0.0", | |
| 3921 | + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | |
| 3922 | + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", | |
| 3923 | + "license": "MIT" | |
| 3924 | + }, | |
| 3925 | + "node_modules/cliui/node_modules/string-width": { | |
| 3926 | + "version": "4.2.3", | |
| 3927 | + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | |
| 3928 | + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | |
| 3929 | + "license": "MIT", | |
| 3930 | + "dependencies": { | |
| 3931 | + "emoji-regex": "^8.0.0", | |
| 3932 | + "is-fullwidth-code-point": "^3.0.0", | |
| 3933 | + "strip-ansi": "^6.0.1" | |
| 3934 | + }, | |
| 3935 | + "engines": { | |
| 3936 | + "node": ">=8" | |
| 3937 | + } | |
| 3938 | + }, | |
| 3939 | + "node_modules/cliui/node_modules/wrap-ansi": { | |
| 3940 | + "version": "6.2.0", | |
| 3941 | + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", | |
| 3942 | + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", | |
| 3943 | + "license": "MIT", | |
| 3944 | + "dependencies": { | |
| 3945 | + "ansi-styles": "^4.0.0", | |
| 3946 | + "string-width": "^4.1.0", | |
| 3947 | + "strip-ansi": "^6.0.0" | |
| 3948 | + }, | |
| 3949 | + "engines": { | |
| 3950 | + "node": ">=8" | |
| 3951 | + } | |
| 3952 | + }, | |
| 3953 | + "node_modules/color-convert": { | |
| 3954 | + "version": "2.0.1", | |
| 3955 | + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | |
| 3956 | + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | |
| 3957 | + "license": "MIT", | |
| 3958 | + "dependencies": { | |
| 3959 | + "color-name": "~1.1.4" | |
| 3960 | + }, | |
| 3961 | + "engines": { | |
| 3962 | + "node": ">=7.0.0" | |
| 3963 | + } | |
| 3964 | + }, | |
| 3965 | + "node_modules/color-name": { | |
| 3966 | + "version": "1.1.4", | |
| 3967 | + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | |
| 3968 | + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", | |
| 3969 | + "license": "MIT" | |
| 3970 | + }, | |
| 3971 | + "node_modules/comma-separated-tokens": { | |
| 3972 | + "version": "2.0.3", | |
| 3973 | + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", | |
| 3974 | + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", | |
| 3975 | + "license": "MIT", | |
| 3976 | + "funding": { | |
| 3977 | + "type": "github", | |
| 3978 | + "url": "https://github.com/sponsors/wooorm" | |
| 3979 | + } | |
| 3980 | + }, | |
| 3981 | + "node_modules/commander": { | |
| 3982 | + "version": "4.1.1", | |
| 3983 | + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", | |
| 3984 | + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", | |
| 3985 | + "dev": true, | |
| 3986 | + "license": "MIT", | |
| 3987 | + "engines": { | |
| 3988 | + "node": ">= 6" | |
| 3989 | + } | |
| 3990 | + }, | |
| 3991 | + "node_modules/compress-commons": { | |
| 3992 | + "version": "6.0.2", | |
| 3993 | + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", | |
| 3994 | + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", | |
| 3995 | + "license": "MIT", | |
| 3996 | + "dependencies": { | |
| 3997 | + "crc-32": "^1.2.0", | |
| 3998 | + "crc32-stream": "^6.0.0", | |
| 3999 | + "is-stream": "^2.0.1", | |
| 4000 | + "normalize-path": "^3.0.0", | |
| 4001 | + "readable-stream": "^4.0.0" | |
| 4002 | + }, | |
| 4003 | + "engines": { | |
| 4004 | + "node": ">= 14" | |
| 4005 | + } | |
| 4006 | + }, | |
| 4007 | + "node_modules/compress-commons/node_modules/readable-stream": { | |
| 4008 | + "version": "4.7.0", | |
| 4009 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", | |
| 4010 | + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", | |
| 4011 | + "license": "MIT", | |
| 4012 | + "dependencies": { | |
| 4013 | + "abort-controller": "^3.0.0", | |
| 4014 | + "buffer": "^6.0.3", | |
| 4015 | + "events": "^3.3.0", | |
| 4016 | + "process": "^0.11.10", | |
| 4017 | + "string_decoder": "^1.3.0" | |
| 4018 | + }, | |
| 4019 | + "engines": { | |
| 4020 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 4021 | + } | |
| 4022 | + }, | |
| 4023 | + "node_modules/compress-commons/node_modules/safe-buffer": { | |
| 4024 | + "version": "5.2.1", | |
| 4025 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", | |
| 4026 | + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", | |
| 4027 | + "funding": [ | |
| 4028 | + { | |
| 4029 | + "type": "github", | |
| 4030 | + "url": "https://github.com/sponsors/feross" | |
| 4031 | + }, | |
| 4032 | + { | |
| 4033 | + "type": "patreon", | |
| 4034 | + "url": "https://www.patreon.com/feross" | |
| 4035 | + }, | |
| 4036 | + { | |
| 4037 | + "type": "consulting", | |
| 4038 | + "url": "https://feross.org/support" | |
| 4039 | + } | |
| 4040 | + ], | |
| 4041 | + "license": "MIT" | |
| 4042 | + }, | |
| 4043 | + "node_modules/compress-commons/node_modules/string_decoder": { | |
| 4044 | + "version": "1.3.0", | |
| 4045 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | |
| 4046 | + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | |
| 4047 | + "license": "MIT", | |
| 4048 | + "dependencies": { | |
| 4049 | + "safe-buffer": "~5.2.0" | |
| 4050 | + } | |
| 4051 | + }, | |
| 4052 | + "node_modules/concat-map": { | |
| 4053 | + "version": "0.0.1", | |
| 4054 | + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | |
| 4055 | + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", | |
| 4056 | + "dev": true, | |
| 4057 | + "license": "MIT" | |
| 4058 | + }, | |
| 4059 | + "node_modules/cookie": { | |
| 4060 | + "version": "0.7.2", | |
| 4061 | + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", | |
| 4062 | + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", | |
| 4063 | + "license": "MIT", | |
| 4064 | + "engines": { | |
| 4065 | + "node": ">= 0.6" | |
| 4066 | + } | |
| 4067 | + }, | |
| 4068 | + "node_modules/core-util-is": { | |
| 4069 | + "version": "1.0.3", | |
| 4070 | + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", | |
| 4071 | + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", | |
| 4072 | + "license": "MIT" | |
| 4073 | + }, | |
| 4074 | + "node_modules/crc-32": { | |
| 4075 | + "version": "1.2.2", | |
| 4076 | + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", | |
| 4077 | + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", | |
| 4078 | + "license": "Apache-2.0", | |
| 4079 | + "bin": { | |
| 4080 | + "crc32": "bin/crc32.njs" | |
| 4081 | + }, | |
| 4082 | + "engines": { | |
| 4083 | + "node": ">=0.8" | |
| 4084 | + } | |
| 4085 | + }, | |
| 4086 | + "node_modules/crc32-stream": { | |
| 4087 | + "version": "6.0.0", | |
| 4088 | + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", | |
| 4089 | + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", | |
| 4090 | + "license": "MIT", | |
| 4091 | + "dependencies": { | |
| 4092 | + "crc-32": "^1.2.0", | |
| 4093 | + "readable-stream": "^4.0.0" | |
| 4094 | + }, | |
| 4095 | + "engines": { | |
| 4096 | + "node": ">= 14" | |
| 4097 | + } | |
| 4098 | + }, | |
| 4099 | + "node_modules/crc32-stream/node_modules/readable-stream": { | |
| 4100 | + "version": "4.7.0", | |
| 4101 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", | |
| 4102 | + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", | |
| 4103 | + "license": "MIT", | |
| 4104 | + "dependencies": { | |
| 4105 | + "abort-controller": "^3.0.0", | |
| 4106 | + "buffer": "^6.0.3", | |
| 4107 | + "events": "^3.3.0", | |
| 4108 | + "process": "^0.11.10", | |
| 4109 | + "string_decoder": "^1.3.0" | |
| 4110 | + }, | |
| 4111 | + "engines": { | |
| 4112 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 4113 | + } | |
| 4114 | + }, | |
| 4115 | + "node_modules/crc32-stream/node_modules/safe-buffer": { | |
| 4116 | + "version": "5.2.1", | |
| 4117 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", | |
| 4118 | + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", | |
| 4119 | + "funding": [ | |
| 4120 | + { | |
| 4121 | + "type": "github", | |
| 4122 | + "url": "https://github.com/sponsors/feross" | |
| 4123 | + }, | |
| 4124 | + { | |
| 4125 | + "type": "patreon", | |
| 4126 | + "url": "https://www.patreon.com/feross" | |
| 4127 | + }, | |
| 4128 | + { | |
| 4129 | + "type": "consulting", | |
| 4130 | + "url": "https://feross.org/support" | |
| 4131 | + } | |
| 4132 | + ], | |
| 4133 | + "license": "MIT" | |
| 4134 | + }, | |
| 4135 | + "node_modules/crc32-stream/node_modules/string_decoder": { | |
| 4136 | + "version": "1.3.0", | |
| 4137 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | |
| 4138 | + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | |
| 4139 | + "license": "MIT", | |
| 4140 | + "dependencies": { | |
| 4141 | + "safe-buffer": "~5.2.0" | |
| 4142 | + } | |
| 4143 | + }, | |
| 4144 | + "node_modules/crelt": { | |
| 4145 | + "version": "1.0.6", | |
| 4146 | + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", | |
| 4147 | + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", | |
| 4148 | + "license": "MIT" | |
| 4149 | + }, | |
| 4150 | + "node_modules/cross-spawn": { | |
| 4151 | + "version": "7.0.6", | |
| 4152 | + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", | |
| 4153 | + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", | |
| 4154 | + "license": "MIT", | |
| 4155 | + "dependencies": { | |
| 4156 | + "path-key": "^3.1.0", | |
| 4157 | + "shebang-command": "^2.0.0", | |
| 4158 | + "which": "^2.0.1" | |
| 4159 | + }, | |
| 4160 | + "engines": { | |
| 4161 | + "node": ">= 8" | |
| 4162 | + } | |
| 4163 | + }, | |
| 4164 | + "node_modules/cssesc": { | |
| 4165 | + "version": "3.0.0", | |
| 4166 | + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", | |
| 4167 | + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", | |
| 4168 | + "dev": true, | |
| 4169 | + "license": "MIT", | |
| 4170 | + "bin": { | |
| 4171 | + "cssesc": "bin/cssesc" | |
| 4172 | + }, | |
| 4173 | + "engines": { | |
| 4174 | + "node": ">=4" | |
| 4175 | + } | |
| 4176 | + }, | |
| 4177 | + "node_modules/csstype": { | |
| 4178 | + "version": "3.2.3", | |
| 4179 | + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", | |
| 4180 | + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", | |
| 4181 | + "license": "MIT" | |
| 4182 | + }, | |
| 4183 | + "node_modules/damerau-levenshtein": { | |
| 4184 | + "version": "1.0.8", | |
| 4185 | + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", | |
| 4186 | + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", | |
| 4187 | + "dev": true, | |
| 4188 | + "license": "BSD-2-Clause" | |
| 4189 | + }, | |
| 4190 | + "node_modules/data-view-buffer": { | |
| 4191 | + "version": "1.0.2", | |
| 4192 | + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", | |
| 4193 | + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", | |
| 4194 | + "dev": true, | |
| 4195 | + "license": "MIT", | |
| 4196 | + "dependencies": { | |
| 4197 | + "call-bound": "^1.0.3", | |
| 4198 | + "es-errors": "^1.3.0", | |
| 4199 | + "is-data-view": "^1.0.2" | |
| 4200 | + }, | |
| 4201 | + "engines": { | |
| 4202 | + "node": ">= 0.4" | |
| 4203 | + }, | |
| 4204 | + "funding": { | |
| 4205 | + "url": "https://github.com/sponsors/ljharb" | |
| 4206 | + } | |
| 4207 | + }, | |
| 4208 | + "node_modules/data-view-byte-length": { | |
| 4209 | + "version": "1.0.2", | |
| 4210 | + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", | |
| 4211 | + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", | |
| 4212 | + "dev": true, | |
| 4213 | + "license": "MIT", | |
| 4214 | + "dependencies": { | |
| 4215 | + "call-bound": "^1.0.3", | |
| 4216 | + "es-errors": "^1.3.0", | |
| 4217 | + "is-data-view": "^1.0.2" | |
| 4218 | + }, | |
| 4219 | + "engines": { | |
| 4220 | + "node": ">= 0.4" | |
| 4221 | + }, | |
| 4222 | + "funding": { | |
| 4223 | + "url": "https://github.com/sponsors/inspect-js" | |
| 4224 | + } | |
| 4225 | + }, | |
| 4226 | + "node_modules/data-view-byte-offset": { | |
| 4227 | + "version": "1.0.1", | |
| 4228 | + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", | |
| 4229 | + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", | |
| 4230 | + "dev": true, | |
| 4231 | + "license": "MIT", | |
| 4232 | + "dependencies": { | |
| 4233 | + "call-bound": "^1.0.2", | |
| 4234 | + "es-errors": "^1.3.0", | |
| 4235 | + "is-data-view": "^1.0.1" | |
| 4236 | + }, | |
| 4237 | + "engines": { | |
| 4238 | + "node": ">= 0.4" | |
| 4239 | + }, | |
| 4240 | + "funding": { | |
| 4241 | + "url": "https://github.com/sponsors/ljharb" | |
| 4242 | + } | |
| 4243 | + }, | |
| 4244 | + "node_modules/debug": { | |
| 4245 | + "version": "4.4.3", | |
| 4246 | + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", | |
| 4247 | + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", | |
| 4248 | + "license": "MIT", | |
| 4249 | + "dependencies": { | |
| 4250 | + "ms": "^2.1.3" | |
| 4251 | + }, | |
| 4252 | + "engines": { | |
| 4253 | + "node": ">=6.0" | |
| 4254 | + }, | |
| 4255 | + "peerDependenciesMeta": { | |
| 4256 | + "supports-color": { | |
| 4257 | + "optional": true | |
| 4258 | + } | |
| 4259 | + } | |
| 4260 | + }, | |
| 4261 | + "node_modules/decamelize": { | |
| 4262 | + "version": "1.2.0", | |
| 4263 | + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", | |
| 4264 | + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", | |
| 4265 | + "license": "MIT", | |
| 4266 | + "engines": { | |
| 4267 | + "node": ">=0.10.0" | |
| 4268 | + } | |
| 4269 | + }, | |
| 4270 | + "node_modules/decode-named-character-reference": { | |
| 4271 | + "version": "1.3.0", | |
| 4272 | + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", | |
| 4273 | + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", | |
| 4274 | + "license": "MIT", | |
| 4275 | + "dependencies": { | |
| 4276 | + "character-entities": "^2.0.0" | |
| 4277 | + }, | |
| 4278 | + "funding": { | |
| 4279 | + "type": "github", | |
| 4280 | + "url": "https://github.com/sponsors/wooorm" | |
| 4281 | + } | |
| 4282 | + }, | |
| 4283 | + "node_modules/deep-is": { | |
| 4284 | + "version": "0.1.4", | |
| 4285 | + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", | |
| 4286 | + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", | |
| 4287 | + "dev": true, | |
| 4288 | + "license": "MIT" | |
| 4289 | + }, | |
| 4290 | + "node_modules/define-data-property": { | |
| 4291 | + "version": "1.1.4", | |
| 4292 | + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", | |
| 4293 | + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", | |
| 4294 | + "dev": true, | |
| 4295 | + "license": "MIT", | |
| 4296 | + "dependencies": { | |
| 4297 | + "es-define-property": "^1.0.0", | |
| 4298 | + "es-errors": "^1.3.0", | |
| 4299 | + "gopd": "^1.0.1" | |
| 4300 | + }, | |
| 4301 | + "engines": { | |
| 4302 | + "node": ">= 0.4" | |
| 4303 | + }, | |
| 4304 | + "funding": { | |
| 4305 | + "url": "https://github.com/sponsors/ljharb" | |
| 4306 | + } | |
| 4307 | + }, | |
| 4308 | + "node_modules/define-properties": { | |
| 4309 | + "version": "1.2.1", | |
| 4310 | + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", | |
| 4311 | + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", | |
| 4312 | + "dev": true, | |
| 4313 | + "license": "MIT", | |
| 4314 | + "dependencies": { | |
| 4315 | + "define-data-property": "^1.0.1", | |
| 4316 | + "has-property-descriptors": "^1.0.0", | |
| 4317 | + "object-keys": "^1.1.1" | |
| 4318 | + }, | |
| 4319 | + "engines": { | |
| 4320 | + "node": ">= 0.4" | |
| 4321 | + }, | |
| 4322 | + "funding": { | |
| 4323 | + "url": "https://github.com/sponsors/ljharb" | |
| 4324 | + } | |
| 4325 | + }, | |
| 4326 | + "node_modules/dequal": { | |
| 4327 | + "version": "2.0.3", | |
| 4328 | + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", | |
| 4329 | + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", | |
| 4330 | + "license": "MIT", | |
| 4331 | + "engines": { | |
| 4332 | + "node": ">=6" | |
| 4333 | + } | |
| 4334 | + }, | |
| 4335 | + "node_modules/detect-libc": { | |
| 4336 | + "version": "2.1.2", | |
| 4337 | + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", | |
| 4338 | + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", | |
| 4339 | + "license": "Apache-2.0", | |
| 4340 | + "engines": { | |
| 4341 | + "node": ">=8" | |
| 4342 | + } | |
| 4343 | + }, | |
| 4344 | + "node_modules/devlop": { | |
| 4345 | + "version": "1.1.0", | |
| 4346 | + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", | |
| 4347 | + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", | |
| 4348 | + "license": "MIT", | |
| 4349 | + "dependencies": { | |
| 4350 | + "dequal": "^2.0.0" | |
| 4351 | + }, | |
| 4352 | + "funding": { | |
| 4353 | + "type": "github", | |
| 4354 | + "url": "https://github.com/sponsors/wooorm" | |
| 4355 | + } | |
| 4356 | + }, | |
| 4357 | + "node_modules/didyoumean": { | |
| 4358 | + "version": "1.2.2", | |
| 4359 | + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", | |
| 4360 | + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", | |
| 4361 | + "dev": true, | |
| 4362 | + "license": "Apache-2.0" | |
| 4363 | + }, | |
| 4364 | + "node_modules/diff": { | |
| 4365 | + "version": "8.0.3", | |
| 4366 | + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", | |
| 4367 | + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", | |
| 4368 | + "license": "BSD-3-Clause", | |
| 4369 | + "engines": { | |
| 4370 | + "node": ">=0.3.1" | |
| 4371 | + } | |
| 4372 | + }, | |
| 4373 | + "node_modules/dijkstrajs": { | |
| 4374 | + "version": "1.0.3", | |
| 4375 | + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", | |
| 4376 | + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", | |
| 4377 | + "license": "MIT" | |
| 4378 | + }, | |
| 4379 | + "node_modules/dlv": { | |
| 4380 | + "version": "1.1.3", | |
| 4381 | + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", | |
| 4382 | + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", | |
| 4383 | + "dev": true, | |
| 4384 | + "license": "MIT" | |
| 4385 | + }, | |
| 4386 | + "node_modules/doctrine": { | |
| 4387 | + "version": "3.0.0", | |
| 4388 | + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", | |
| 4389 | + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", | |
| 4390 | + "dev": true, | |
| 4391 | + "license": "Apache-2.0", | |
| 4392 | + "dependencies": { | |
| 4393 | + "esutils": "^2.0.2" | |
| 4394 | + }, | |
| 4395 | + "engines": { | |
| 4396 | + "node": ">=6.0.0" | |
| 4397 | + } | |
| 4398 | + }, | |
| 4399 | + "node_modules/dunder-proto": { | |
| 4400 | + "version": "1.0.1", | |
| 4401 | + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", | |
| 4402 | + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", | |
| 4403 | + "dev": true, | |
| 4404 | + "license": "MIT", | |
| 4405 | + "dependencies": { | |
| 4406 | + "call-bind-apply-helpers": "^1.0.1", | |
| 4407 | + "es-errors": "^1.3.0", | |
| 4408 | + "gopd": "^1.2.0" | |
| 4409 | + }, | |
| 4410 | + "engines": { | |
| 4411 | + "node": ">= 0.4" | |
| 4412 | + } | |
| 4413 | + }, | |
| 4414 | + "node_modules/eastasianwidth": { | |
| 4415 | + "version": "0.2.0", | |
| 4416 | + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", | |
| 4417 | + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", | |
| 4418 | + "license": "MIT" | |
| 4419 | + }, | |
| 4420 | + "node_modules/emoji-regex": { | |
| 4421 | + "version": "9.2.2", | |
| 4422 | + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", | |
| 4423 | + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", | |
| 4424 | + "license": "MIT" | |
| 4425 | + }, | |
| 4426 | + "node_modules/es-abstract": { | |
| 4427 | + "version": "1.24.1", | |
| 4428 | + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", | |
| 4429 | + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", | |
| 4430 | + "dev": true, | |
| 4431 | + "license": "MIT", | |
| 4432 | + "dependencies": { | |
| 4433 | + "array-buffer-byte-length": "^1.0.2", | |
| 4434 | + "arraybuffer.prototype.slice": "^1.0.4", | |
| 4435 | + "available-typed-arrays": "^1.0.7", | |
| 4436 | + "call-bind": "^1.0.8", | |
| 4437 | + "call-bound": "^1.0.4", | |
| 4438 | + "data-view-buffer": "^1.0.2", | |
| 4439 | + "data-view-byte-length": "^1.0.2", | |
| 4440 | + "data-view-byte-offset": "^1.0.1", | |
| 4441 | + "es-define-property": "^1.0.1", | |
| 4442 | + "es-errors": "^1.3.0", | |
| 4443 | + "es-object-atoms": "^1.1.1", | |
| 4444 | + "es-set-tostringtag": "^2.1.0", | |
| 4445 | + "es-to-primitive": "^1.3.0", | |
| 4446 | + "function.prototype.name": "^1.1.8", | |
| 4447 | + "get-intrinsic": "^1.3.0", | |
| 4448 | + "get-proto": "^1.0.1", | |
| 4449 | + "get-symbol-description": "^1.1.0", | |
| 4450 | + "globalthis": "^1.0.4", | |
| 4451 | + "gopd": "^1.2.0", | |
| 4452 | + "has-property-descriptors": "^1.0.2", | |
| 4453 | + "has-proto": "^1.2.0", | |
| 4454 | + "has-symbols": "^1.1.0", | |
| 4455 | + "hasown": "^2.0.2", | |
| 4456 | + "internal-slot": "^1.1.0", | |
| 4457 | + "is-array-buffer": "^3.0.5", | |
| 4458 | + "is-callable": "^1.2.7", | |
| 4459 | + "is-data-view": "^1.0.2", | |
| 4460 | + "is-negative-zero": "^2.0.3", | |
| 4461 | + "is-regex": "^1.2.1", | |
| 4462 | + "is-set": "^2.0.3", | |
| 4463 | + "is-shared-array-buffer": "^1.0.4", | |
| 4464 | + "is-string": "^1.1.1", | |
| 4465 | + "is-typed-array": "^1.1.15", | |
| 4466 | + "is-weakref": "^1.1.1", | |
| 4467 | + "math-intrinsics": "^1.1.0", | |
| 4468 | + "object-inspect": "^1.13.4", | |
| 4469 | + "object-keys": "^1.1.1", | |
| 4470 | + "object.assign": "^4.1.7", | |
| 4471 | + "own-keys": "^1.0.1", | |
| 4472 | + "regexp.prototype.flags": "^1.5.4", | |
| 4473 | + "safe-array-concat": "^1.1.3", | |
| 4474 | + "safe-push-apply": "^1.0.0", | |
| 4475 | + "safe-regex-test": "^1.1.0", | |
| 4476 | + "set-proto": "^1.0.0", | |
| 4477 | + "stop-iteration-iterator": "^1.1.0", | |
| 4478 | + "string.prototype.trim": "^1.2.10", | |
| 4479 | + "string.prototype.trimend": "^1.0.9", | |
| 4480 | + "string.prototype.trimstart": "^1.0.8", | |
| 4481 | + "typed-array-buffer": "^1.0.3", | |
| 4482 | + "typed-array-byte-length": "^1.0.3", | |
| 4483 | + "typed-array-byte-offset": "^1.0.4", | |
| 4484 | + "typed-array-length": "^1.0.7", | |
| 4485 | + "unbox-primitive": "^1.1.0", | |
| 4486 | + "which-typed-array": "^1.1.19" | |
| 4487 | + }, | |
| 4488 | + "engines": { | |
| 4489 | + "node": ">= 0.4" | |
| 4490 | + }, | |
| 4491 | + "funding": { | |
| 4492 | + "url": "https://github.com/sponsors/ljharb" | |
| 4493 | + } | |
| 4494 | + }, | |
| 4495 | + "node_modules/es-define-property": { | |
| 4496 | + "version": "1.0.1", | |
| 4497 | + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | |
| 4498 | + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", | |
| 4499 | + "dev": true, | |
| 4500 | + "license": "MIT", | |
| 4501 | + "engines": { | |
| 4502 | + "node": ">= 0.4" | |
| 4503 | + } | |
| 4504 | + }, | |
| 4505 | + "node_modules/es-errors": { | |
| 4506 | + "version": "1.3.0", | |
| 4507 | + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", | |
| 4508 | + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", | |
| 4509 | + "dev": true, | |
| 4510 | + "license": "MIT", | |
| 4511 | + "engines": { | |
| 4512 | + "node": ">= 0.4" | |
| 4513 | + } | |
| 4514 | + }, | |
| 4515 | + "node_modules/es-iterator-helpers": { | |
| 4516 | + "version": "1.2.2", | |
| 4517 | + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", | |
| 4518 | + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", | |
| 4519 | + "dev": true, | |
| 4520 | + "license": "MIT", | |
| 4521 | + "dependencies": { | |
| 4522 | + "call-bind": "^1.0.8", | |
| 4523 | + "call-bound": "^1.0.4", | |
| 4524 | + "define-properties": "^1.2.1", | |
| 4525 | + "es-abstract": "^1.24.1", | |
| 4526 | + "es-errors": "^1.3.0", | |
| 4527 | + "es-set-tostringtag": "^2.1.0", | |
| 4528 | + "function-bind": "^1.1.2", | |
| 4529 | + "get-intrinsic": "^1.3.0", | |
| 4530 | + "globalthis": "^1.0.4", | |
| 4531 | + "gopd": "^1.2.0", | |
| 4532 | + "has-property-descriptors": "^1.0.2", | |
| 4533 | + "has-proto": "^1.2.0", | |
| 4534 | + "has-symbols": "^1.1.0", | |
| 4535 | + "internal-slot": "^1.1.0", | |
| 4536 | + "iterator.prototype": "^1.1.5", | |
| 4537 | + "safe-array-concat": "^1.1.3" | |
| 4538 | + }, | |
| 4539 | + "engines": { | |
| 4540 | + "node": ">= 0.4" | |
| 4541 | + } | |
| 4542 | + }, | |
| 4543 | + "node_modules/es-object-atoms": { | |
| 4544 | + "version": "1.1.1", | |
| 4545 | + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", | |
| 4546 | + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", | |
| 4547 | + "dev": true, | |
| 4548 | + "license": "MIT", | |
| 4549 | + "dependencies": { | |
| 4550 | + "es-errors": "^1.3.0" | |
| 4551 | + }, | |
| 4552 | + "engines": { | |
| 4553 | + "node": ">= 0.4" | |
| 4554 | + } | |
| 4555 | + }, | |
| 4556 | + "node_modules/es-set-tostringtag": { | |
| 4557 | + "version": "2.1.0", | |
| 4558 | + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", | |
| 4559 | + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", | |
| 4560 | + "dev": true, | |
| 4561 | + "license": "MIT", | |
| 4562 | + "dependencies": { | |
| 4563 | + "es-errors": "^1.3.0", | |
| 4564 | + "get-intrinsic": "^1.2.6", | |
| 4565 | + "has-tostringtag": "^1.0.2", | |
| 4566 | + "hasown": "^2.0.2" | |
| 4567 | + }, | |
| 4568 | + "engines": { | |
| 4569 | + "node": ">= 0.4" | |
| 4570 | + } | |
| 4571 | + }, | |
| 4572 | + "node_modules/es-shim-unscopables": { | |
| 4573 | + "version": "1.1.0", | |
| 4574 | + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", | |
| 4575 | + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", | |
| 4576 | + "dev": true, | |
| 4577 | + "license": "MIT", | |
| 4578 | + "dependencies": { | |
| 4579 | + "hasown": "^2.0.2" | |
| 4580 | + }, | |
| 4581 | + "engines": { | |
| 4582 | + "node": ">= 0.4" | |
| 4583 | + } | |
| 4584 | + }, | |
| 4585 | + "node_modules/es-to-primitive": { | |
| 4586 | + "version": "1.3.0", | |
| 4587 | + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", | |
| 4588 | + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", | |
| 4589 | + "dev": true, | |
| 4590 | + "license": "MIT", | |
| 4591 | + "dependencies": { | |
| 4592 | + "is-callable": "^1.2.7", | |
| 4593 | + "is-date-object": "^1.0.5", | |
| 4594 | + "is-symbol": "^1.0.4" | |
| 4595 | + }, | |
| 4596 | + "engines": { | |
| 4597 | + "node": ">= 0.4" | |
| 4598 | + }, | |
| 4599 | + "funding": { | |
| 4600 | + "url": "https://github.com/sponsors/ljharb" | |
| 4601 | + } | |
| 4602 | + }, | |
| 4603 | + "node_modules/esbuild": { | |
| 4604 | + "version": "0.27.3", | |
| 4605 | + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", | |
| 4606 | + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", | |
| 4607 | + "dev": true, | |
| 4608 | + "hasInstallScript": true, | |
| 4609 | + "license": "MIT", | |
| 4610 | + "bin": { | |
| 4611 | + "esbuild": "bin/esbuild" | |
| 4612 | + }, | |
| 4613 | + "engines": { | |
| 4614 | + "node": ">=18" | |
| 4615 | + }, | |
| 4616 | + "optionalDependencies": { | |
| 4617 | + "@esbuild/aix-ppc64": "0.27.3", | |
| 4618 | + "@esbuild/android-arm": "0.27.3", | |
| 4619 | + "@esbuild/android-arm64": "0.27.3", | |
| 4620 | + "@esbuild/android-x64": "0.27.3", | |
| 4621 | + "@esbuild/darwin-arm64": "0.27.3", | |
| 4622 | + "@esbuild/darwin-x64": "0.27.3", | |
| 4623 | + "@esbuild/freebsd-arm64": "0.27.3", | |
| 4624 | + "@esbuild/freebsd-x64": "0.27.3", | |
| 4625 | + "@esbuild/linux-arm": "0.27.3", | |
| 4626 | + "@esbuild/linux-arm64": "0.27.3", | |
| 4627 | + "@esbuild/linux-ia32": "0.27.3", | |
| 4628 | + "@esbuild/linux-loong64": "0.27.3", | |
| 4629 | + "@esbuild/linux-mips64el": "0.27.3", | |
| 4630 | + "@esbuild/linux-ppc64": "0.27.3", | |
| 4631 | + "@esbuild/linux-riscv64": "0.27.3", | |
| 4632 | + "@esbuild/linux-s390x": "0.27.3", | |
| 4633 | + "@esbuild/linux-x64": "0.27.3", | |
| 4634 | + "@esbuild/netbsd-arm64": "0.27.3", | |
| 4635 | + "@esbuild/netbsd-x64": "0.27.3", | |
| 4636 | + "@esbuild/openbsd-arm64": "0.27.3", | |
| 4637 | + "@esbuild/openbsd-x64": "0.27.3", | |
| 4638 | + "@esbuild/openharmony-arm64": "0.27.3", | |
| 4639 | + "@esbuild/sunos-x64": "0.27.3", | |
| 4640 | + "@esbuild/win32-arm64": "0.27.3", | |
| 4641 | + "@esbuild/win32-ia32": "0.27.3", | |
| 4642 | + "@esbuild/win32-x64": "0.27.3" | |
| 4643 | + } | |
| 4644 | + }, | |
| 4645 | + "node_modules/escape-string-regexp": { | |
| 4646 | + "version": "4.0.0", | |
| 4647 | + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", | |
| 4648 | + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", | |
| 4649 | + "dev": true, | |
| 4650 | + "license": "MIT", | |
| 4651 | + "engines": { | |
| 4652 | + "node": ">=10" | |
| 4653 | + }, | |
| 4654 | + "funding": { | |
| 4655 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 4656 | + } | |
| 4657 | + }, | |
| 4658 | + "node_modules/eslint": { | |
| 4659 | + "version": "8.57.1", | |
| 4660 | + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", | |
| 4661 | + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", | |
| 4662 | + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", | |
| 4663 | + "dev": true, | |
| 4664 | + "license": "MIT", | |
| 4665 | + "dependencies": { | |
| 4666 | + "@eslint-community/eslint-utils": "^4.2.0", | |
| 4667 | + "@eslint-community/regexpp": "^4.6.1", | |
| 4668 | + "@eslint/eslintrc": "^2.1.4", | |
| 4669 | + "@eslint/js": "8.57.1", | |
| 4670 | + "@humanwhocodes/config-array": "^0.13.0", | |
| 4671 | + "@humanwhocodes/module-importer": "^1.0.1", | |
| 4672 | + "@nodelib/fs.walk": "^1.2.8", | |
| 4673 | + "@ungap/structured-clone": "^1.2.0", | |
| 4674 | + "ajv": "^6.12.4", | |
| 4675 | + "chalk": "^4.0.0", | |
| 4676 | + "cross-spawn": "^7.0.2", | |
| 4677 | + "debug": "^4.3.2", | |
| 4678 | + "doctrine": "^3.0.0", | |
| 4679 | + "escape-string-regexp": "^4.0.0", | |
| 4680 | + "eslint-scope": "^7.2.2", | |
| 4681 | + "eslint-visitor-keys": "^3.4.3", | |
| 4682 | + "espree": "^9.6.1", | |
| 4683 | + "esquery": "^1.4.2", | |
| 4684 | + "esutils": "^2.0.2", | |
| 4685 | + "fast-deep-equal": "^3.1.3", | |
| 4686 | + "file-entry-cache": "^6.0.1", | |
| 4687 | + "find-up": "^5.0.0", | |
| 4688 | + "glob-parent": "^6.0.2", | |
| 4689 | + "globals": "^13.19.0", | |
| 4690 | + "graphemer": "^1.4.0", | |
| 4691 | + "ignore": "^5.2.0", | |
| 4692 | + "imurmurhash": "^0.1.4", | |
| 4693 | + "is-glob": "^4.0.0", | |
| 4694 | + "is-path-inside": "^3.0.3", | |
| 4695 | + "js-yaml": "^4.1.0", | |
| 4696 | + "json-stable-stringify-without-jsonify": "^1.0.1", | |
| 4697 | + "levn": "^0.4.1", | |
| 4698 | + "lodash.merge": "^4.6.2", | |
| 4699 | + "minimatch": "^3.1.2", | |
| 4700 | + "natural-compare": "^1.4.0", | |
| 4701 | + "optionator": "^0.9.3", | |
| 4702 | + "strip-ansi": "^6.0.1", | |
| 4703 | + "text-table": "^0.2.0" | |
| 4704 | + }, | |
| 4705 | + "bin": { | |
| 4706 | + "eslint": "bin/eslint.js" | |
| 4707 | + }, | |
| 4708 | + "engines": { | |
| 4709 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 4710 | + }, | |
| 4711 | + "funding": { | |
| 4712 | + "url": "https://opencollective.com/eslint" | |
| 4713 | + } | |
| 4714 | + }, | |
| 4715 | + "node_modules/eslint-config-next": { | |
| 4716 | + "version": "14.2.35", | |
| 4717 | + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.35.tgz", | |
| 4718 | + "integrity": "sha512-BpLsv01UisH193WyT/1lpHqq5iJ/Orfz9h/NOOlAmTUq4GY349PextQ62K4XpnaM9supeiEn3TaOTeQO07gURg==", | |
| 4719 | + "dev": true, | |
| 4720 | + "license": "MIT", | |
| 4721 | + "dependencies": { | |
| 4722 | + "@next/eslint-plugin-next": "14.2.35", | |
| 4723 | + "@rushstack/eslint-patch": "^1.3.3", | |
| 4724 | + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", | |
| 4725 | + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", | |
| 4726 | + "eslint-import-resolver-node": "^0.3.6", | |
| 4727 | + "eslint-import-resolver-typescript": "^3.5.2", | |
| 4728 | + "eslint-plugin-import": "^2.28.1", | |
| 4729 | + "eslint-plugin-jsx-a11y": "^6.7.1", | |
| 4730 | + "eslint-plugin-react": "^7.33.2", | |
| 4731 | + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" | |
| 4732 | + }, | |
| 4733 | + "peerDependencies": { | |
| 4734 | + "eslint": "^7.23.0 || ^8.0.0", | |
| 4735 | + "typescript": ">=3.3.1" | |
| 4736 | + }, | |
| 4737 | + "peerDependenciesMeta": { | |
| 4738 | + "typescript": { | |
| 4739 | + "optional": true | |
| 4740 | + } | |
| 4741 | + } | |
| 4742 | + }, | |
| 4743 | + "node_modules/eslint-import-resolver-node": { | |
| 4744 | + "version": "0.3.9", | |
| 4745 | + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", | |
| 4746 | + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", | |
| 4747 | + "dev": true, | |
| 4748 | + "license": "MIT", | |
| 4749 | + "dependencies": { | |
| 4750 | + "debug": "^3.2.7", | |
| 4751 | + "is-core-module": "^2.13.0", | |
| 4752 | + "resolve": "^1.22.4" | |
| 4753 | + } | |
| 4754 | + }, | |
| 4755 | + "node_modules/eslint-import-resolver-node/node_modules/debug": { | |
| 4756 | + "version": "3.2.7", | |
| 4757 | + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", | |
| 4758 | + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", | |
| 4759 | + "dev": true, | |
| 4760 | + "license": "MIT", | |
| 4761 | + "dependencies": { | |
| 4762 | + "ms": "^2.1.1" | |
| 4763 | + } | |
| 4764 | + }, | |
| 4765 | + "node_modules/eslint-import-resolver-typescript": { | |
| 4766 | + "version": "3.10.1", | |
| 4767 | + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", | |
| 4768 | + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", | |
| 4769 | + "dev": true, | |
| 4770 | + "license": "ISC", | |
| 4771 | + "dependencies": { | |
| 4772 | + "@nolyfill/is-core-module": "1.0.39", | |
| 4773 | + "debug": "^4.4.0", | |
| 4774 | + "get-tsconfig": "^4.10.0", | |
| 4775 | + "is-bun-module": "^2.0.0", | |
| 4776 | + "stable-hash": "^0.0.5", | |
| 4777 | + "tinyglobby": "^0.2.13", | |
| 4778 | + "unrs-resolver": "^1.6.2" | |
| 4779 | + }, | |
| 4780 | + "engines": { | |
| 4781 | + "node": "^14.18.0 || >=16.0.0" | |
| 4782 | + }, | |
| 4783 | + "funding": { | |
| 4784 | + "url": "https://opencollective.com/eslint-import-resolver-typescript" | |
| 4785 | + }, | |
| 4786 | + "peerDependencies": { | |
| 4787 | + "eslint": "*", | |
| 4788 | + "eslint-plugin-import": "*", | |
| 4789 | + "eslint-plugin-import-x": "*" | |
| 4790 | + }, | |
| 4791 | + "peerDependenciesMeta": { | |
| 4792 | + "eslint-plugin-import": { | |
| 4793 | + "optional": true | |
| 4794 | + }, | |
| 4795 | + "eslint-plugin-import-x": { | |
| 4796 | + "optional": true | |
| 4797 | + } | |
| 4798 | + } | |
| 4799 | + }, | |
| 4800 | + "node_modules/eslint-module-utils": { | |
| 4801 | + "version": "2.12.1", | |
| 4802 | + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", | |
| 4803 | + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", | |
| 4804 | + "dev": true, | |
| 4805 | + "license": "MIT", | |
| 4806 | + "dependencies": { | |
| 4807 | + "debug": "^3.2.7" | |
| 4808 | + }, | |
| 4809 | + "engines": { | |
| 4810 | + "node": ">=4" | |
| 4811 | + }, | |
| 4812 | + "peerDependenciesMeta": { | |
| 4813 | + "eslint": { | |
| 4814 | + "optional": true | |
| 4815 | + } | |
| 4816 | + } | |
| 4817 | + }, | |
| 4818 | + "node_modules/eslint-module-utils/node_modules/debug": { | |
| 4819 | + "version": "3.2.7", | |
| 4820 | + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", | |
| 4821 | + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", | |
| 4822 | + "dev": true, | |
| 4823 | + "license": "MIT", | |
| 4824 | + "dependencies": { | |
| 4825 | + "ms": "^2.1.1" | |
| 4826 | + } | |
| 4827 | + }, | |
| 4828 | + "node_modules/eslint-plugin-import": { | |
| 4829 | + "version": "2.32.0", | |
| 4830 | + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", | |
| 4831 | + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", | |
| 4832 | + "dev": true, | |
| 4833 | + "license": "MIT", | |
| 4834 | + "dependencies": { | |
| 4835 | + "@rtsao/scc": "^1.1.0", | |
| 4836 | + "array-includes": "^3.1.9", | |
| 4837 | + "array.prototype.findlastindex": "^1.2.6", | |
| 4838 | + "array.prototype.flat": "^1.3.3", | |
| 4839 | + "array.prototype.flatmap": "^1.3.3", | |
| 4840 | + "debug": "^3.2.7", | |
| 4841 | + "doctrine": "^2.1.0", | |
| 4842 | + "eslint-import-resolver-node": "^0.3.9", | |
| 4843 | + "eslint-module-utils": "^2.12.1", | |
| 4844 | + "hasown": "^2.0.2", | |
| 4845 | + "is-core-module": "^2.16.1", | |
| 4846 | + "is-glob": "^4.0.3", | |
| 4847 | + "minimatch": "^3.1.2", | |
| 4848 | + "object.fromentries": "^2.0.8", | |
| 4849 | + "object.groupby": "^1.0.3", | |
| 4850 | + "object.values": "^1.2.1", | |
| 4851 | + "semver": "^6.3.1", | |
| 4852 | + "string.prototype.trimend": "^1.0.9", | |
| 4853 | + "tsconfig-paths": "^3.15.0" | |
| 4854 | + }, | |
| 4855 | + "engines": { | |
| 4856 | + "node": ">=4" | |
| 4857 | + }, | |
| 4858 | + "peerDependencies": { | |
| 4859 | + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" | |
| 4860 | + } | |
| 4861 | + }, | |
| 4862 | + "node_modules/eslint-plugin-import/node_modules/debug": { | |
| 4863 | + "version": "3.2.7", | |
| 4864 | + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", | |
| 4865 | + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", | |
| 4866 | + "dev": true, | |
| 4867 | + "license": "MIT", | |
| 4868 | + "dependencies": { | |
| 4869 | + "ms": "^2.1.1" | |
| 4870 | + } | |
| 4871 | + }, | |
| 4872 | + "node_modules/eslint-plugin-import/node_modules/doctrine": { | |
| 4873 | + "version": "2.1.0", | |
| 4874 | + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", | |
| 4875 | + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", | |
| 4876 | + "dev": true, | |
| 4877 | + "license": "Apache-2.0", | |
| 4878 | + "dependencies": { | |
| 4879 | + "esutils": "^2.0.2" | |
| 4880 | + }, | |
| 4881 | + "engines": { | |
| 4882 | + "node": ">=0.10.0" | |
| 4883 | + } | |
| 4884 | + }, | |
| 4885 | + "node_modules/eslint-plugin-import/node_modules/semver": { | |
| 4886 | + "version": "6.3.1", | |
| 4887 | + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", | |
| 4888 | + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", | |
| 4889 | + "dev": true, | |
| 4890 | + "license": "ISC", | |
| 4891 | + "bin": { | |
| 4892 | + "semver": "bin/semver.js" | |
| 4893 | + } | |
| 4894 | + }, | |
| 4895 | + "node_modules/eslint-plugin-jsx-a11y": { | |
| 4896 | + "version": "6.10.2", | |
| 4897 | + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", | |
| 4898 | + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", | |
| 4899 | + "dev": true, | |
| 4900 | + "license": "MIT", | |
| 4901 | + "dependencies": { | |
| 4902 | + "aria-query": "^5.3.2", | |
| 4903 | + "array-includes": "^3.1.8", | |
| 4904 | + "array.prototype.flatmap": "^1.3.2", | |
| 4905 | + "ast-types-flow": "^0.0.8", | |
| 4906 | + "axe-core": "^4.10.0", | |
| 4907 | + "axobject-query": "^4.1.0", | |
| 4908 | + "damerau-levenshtein": "^1.0.8", | |
| 4909 | + "emoji-regex": "^9.2.2", | |
| 4910 | + "hasown": "^2.0.2", | |
| 4911 | + "jsx-ast-utils": "^3.3.5", | |
| 4912 | + "language-tags": "^1.0.9", | |
| 4913 | + "minimatch": "^3.1.2", | |
| 4914 | + "object.fromentries": "^2.0.8", | |
| 4915 | + "safe-regex-test": "^1.0.3", | |
| 4916 | + "string.prototype.includes": "^2.0.1" | |
| 4917 | + }, | |
| 4918 | + "engines": { | |
| 4919 | + "node": ">=4.0" | |
| 4920 | + }, | |
| 4921 | + "peerDependencies": { | |
| 4922 | + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" | |
| 4923 | + } | |
| 4924 | + }, | |
| 4925 | + "node_modules/eslint-plugin-react": { | |
| 4926 | + "version": "7.37.5", | |
| 4927 | + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", | |
| 4928 | + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", | |
| 4929 | + "dev": true, | |
| 4930 | + "license": "MIT", | |
| 4931 | + "dependencies": { | |
| 4932 | + "array-includes": "^3.1.8", | |
| 4933 | + "array.prototype.findlast": "^1.2.5", | |
| 4934 | + "array.prototype.flatmap": "^1.3.3", | |
| 4935 | + "array.prototype.tosorted": "^1.1.4", | |
| 4936 | + "doctrine": "^2.1.0", | |
| 4937 | + "es-iterator-helpers": "^1.2.1", | |
| 4938 | + "estraverse": "^5.3.0", | |
| 4939 | + "hasown": "^2.0.2", | |
| 4940 | + "jsx-ast-utils": "^2.4.1 || ^3.0.0", | |
| 4941 | + "minimatch": "^3.1.2", | |
| 4942 | + "object.entries": "^1.1.9", | |
| 4943 | + "object.fromentries": "^2.0.8", | |
| 4944 | + "object.values": "^1.2.1", | |
| 4945 | + "prop-types": "^15.8.1", | |
| 4946 | + "resolve": "^2.0.0-next.5", | |
| 4947 | + "semver": "^6.3.1", | |
| 4948 | + "string.prototype.matchall": "^4.0.12", | |
| 4949 | + "string.prototype.repeat": "^1.0.0" | |
| 4950 | + }, | |
| 4951 | + "engines": { | |
| 4952 | + "node": ">=4" | |
| 4953 | + }, | |
| 4954 | + "peerDependencies": { | |
| 4955 | + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" | |
| 4956 | + } | |
| 4957 | + }, | |
| 4958 | + "node_modules/eslint-plugin-react-hooks": { | |
| 4959 | + "version": "5.0.0-canary-7118f5dd7-20230705", | |
| 4960 | + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", | |
| 4961 | + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", | |
| 4962 | + "dev": true, | |
| 4963 | + "license": "MIT", | |
| 4964 | + "engines": { | |
| 4965 | + "node": ">=10" | |
| 4966 | + }, | |
| 4967 | + "peerDependencies": { | |
| 4968 | + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" | |
| 4969 | + } | |
| 4970 | + }, | |
| 4971 | + "node_modules/eslint-plugin-react/node_modules/doctrine": { | |
| 4972 | + "version": "2.1.0", | |
| 4973 | + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", | |
| 4974 | + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", | |
| 4975 | + "dev": true, | |
| 4976 | + "license": "Apache-2.0", | |
| 4977 | + "dependencies": { | |
| 4978 | + "esutils": "^2.0.2" | |
| 4979 | + }, | |
| 4980 | + "engines": { | |
| 4981 | + "node": ">=0.10.0" | |
| 4982 | + } | |
| 4983 | + }, | |
| 4984 | + "node_modules/eslint-plugin-react/node_modules/resolve": { | |
| 4985 | + "version": "2.0.0-next.5", | |
| 4986 | + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", | |
| 4987 | + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", | |
| 4988 | + "dev": true, | |
| 4989 | + "license": "MIT", | |
| 4990 | + "dependencies": { | |
| 4991 | + "is-core-module": "^2.13.0", | |
| 4992 | + "path-parse": "^1.0.7", | |
| 4993 | + "supports-preserve-symlinks-flag": "^1.0.0" | |
| 4994 | + }, | |
| 4995 | + "bin": { | |
| 4996 | + "resolve": "bin/resolve" | |
| 4997 | + }, | |
| 4998 | + "funding": { | |
| 4999 | + "url": "https://github.com/sponsors/ljharb" | |
| 5000 | + } | |
| 5001 | + }, | |
| 5002 | + "node_modules/eslint-plugin-react/node_modules/semver": { | |
| 5003 | + "version": "6.3.1", | |
| 5004 | + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", | |
| 5005 | + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", | |
| 5006 | + "dev": true, | |
| 5007 | + "license": "ISC", | |
| 5008 | + "bin": { | |
| 5009 | + "semver": "bin/semver.js" | |
| 5010 | + } | |
| 5011 | + }, | |
| 5012 | + "node_modules/eslint-scope": { | |
| 5013 | + "version": "7.2.2", | |
| 5014 | + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", | |
| 5015 | + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", | |
| 5016 | + "dev": true, | |
| 5017 | + "license": "BSD-2-Clause", | |
| 5018 | + "dependencies": { | |
| 5019 | + "esrecurse": "^4.3.0", | |
| 5020 | + "estraverse": "^5.2.0" | |
| 5021 | + }, | |
| 5022 | + "engines": { | |
| 5023 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 5024 | + }, | |
| 5025 | + "funding": { | |
| 5026 | + "url": "https://opencollective.com/eslint" | |
| 5027 | + } | |
| 5028 | + }, | |
| 5029 | + "node_modules/eslint-visitor-keys": { | |
| 5030 | + "version": "3.4.3", | |
| 5031 | + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", | |
| 5032 | + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", | |
| 5033 | + "dev": true, | |
| 5034 | + "license": "Apache-2.0", | |
| 5035 | + "engines": { | |
| 5036 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 5037 | + }, | |
| 5038 | + "funding": { | |
| 5039 | + "url": "https://opencollective.com/eslint" | |
| 5040 | + } | |
| 5041 | + }, | |
| 5042 | + "node_modules/espree": { | |
| 5043 | + "version": "9.6.1", | |
| 5044 | + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", | |
| 5045 | + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", | |
| 5046 | + "dev": true, | |
| 5047 | + "license": "BSD-2-Clause", | |
| 5048 | + "dependencies": { | |
| 5049 | + "acorn": "^8.9.0", | |
| 5050 | + "acorn-jsx": "^5.3.2", | |
| 5051 | + "eslint-visitor-keys": "^3.4.1" | |
| 5052 | + }, | |
| 5053 | + "engines": { | |
| 5054 | + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" | |
| 5055 | + }, | |
| 5056 | + "funding": { | |
| 5057 | + "url": "https://opencollective.com/eslint" | |
| 5058 | + } | |
| 5059 | + }, | |
| 5060 | + "node_modules/esquery": { | |
| 5061 | + "version": "1.7.0", | |
| 5062 | + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", | |
| 5063 | + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", | |
| 5064 | + "dev": true, | |
| 5065 | + "license": "BSD-3-Clause", | |
| 5066 | + "dependencies": { | |
| 5067 | + "estraverse": "^5.1.0" | |
| 5068 | + }, | |
| 5069 | + "engines": { | |
| 5070 | + "node": ">=0.10" | |
| 5071 | + } | |
| 5072 | + }, | |
| 5073 | + "node_modules/esrecurse": { | |
| 5074 | + "version": "4.3.0", | |
| 5075 | + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", | |
| 5076 | + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", | |
| 5077 | + "dev": true, | |
| 5078 | + "license": "BSD-2-Clause", | |
| 5079 | + "dependencies": { | |
| 5080 | + "estraverse": "^5.2.0" | |
| 5081 | + }, | |
| 5082 | + "engines": { | |
| 5083 | + "node": ">=4.0" | |
| 5084 | + } | |
| 5085 | + }, | |
| 5086 | + "node_modules/estraverse": { | |
| 5087 | + "version": "5.3.0", | |
| 5088 | + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", | |
| 5089 | + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", | |
| 5090 | + "dev": true, | |
| 5091 | + "license": "BSD-2-Clause", | |
| 5092 | + "engines": { | |
| 5093 | + "node": ">=4.0" | |
| 5094 | + } | |
| 5095 | + }, | |
| 5096 | + "node_modules/estree-util-is-identifier-name": { | |
| 5097 | + "version": "3.0.0", | |
| 5098 | + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", | |
| 5099 | + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", | |
| 5100 | + "license": "MIT", | |
| 5101 | + "funding": { | |
| 5102 | + "type": "opencollective", | |
| 5103 | + "url": "https://opencollective.com/unified" | |
| 5104 | + } | |
| 5105 | + }, | |
| 5106 | + "node_modules/esutils": { | |
| 5107 | + "version": "2.0.3", | |
| 5108 | + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", | |
| 5109 | + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", | |
| 5110 | + "dev": true, | |
| 5111 | + "license": "BSD-2-Clause", | |
| 5112 | + "engines": { | |
| 5113 | + "node": ">=0.10.0" | |
| 5114 | + } | |
| 5115 | + }, | |
| 5116 | + "node_modules/event-target-shim": { | |
| 5117 | + "version": "5.0.1", | |
| 5118 | + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", | |
| 5119 | + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", | |
| 5120 | + "license": "MIT", | |
| 5121 | + "engines": { | |
| 5122 | + "node": ">=6" | |
| 5123 | + } | |
| 5124 | + }, | |
| 5125 | + "node_modules/events": { | |
| 5126 | + "version": "3.3.0", | |
| 5127 | + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", | |
| 5128 | + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", | |
| 5129 | + "license": "MIT", | |
| 5130 | + "engines": { | |
| 5131 | + "node": ">=0.8.x" | |
| 5132 | + } | |
| 5133 | + }, | |
| 5134 | + "node_modules/events-universal": { | |
| 5135 | + "version": "1.0.1", | |
| 5136 | + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 5137 | + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 5138 | + "license": "Apache-2.0", | |
| 5139 | + "dependencies": { | |
| 5140 | + "bare-events": "^2.7.0" | |
| 5141 | + } | |
| 5142 | + }, | |
| 5143 | + "node_modules/extend": { | |
| 5144 | + "version": "3.0.2", | |
| 5145 | + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", | |
| 5146 | + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", | |
| 5147 | + "license": "MIT" | |
| 5148 | + }, | |
| 5149 | + "node_modules/fast-deep-equal": { | |
| 5150 | + "version": "3.1.3", | |
| 5151 | + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | |
| 5152 | + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", | |
| 5153 | + "dev": true, | |
| 5154 | + "license": "MIT" | |
| 5155 | + }, | |
| 5156 | + "node_modules/fast-fifo": { | |
| 5157 | + "version": "1.3.2", | |
| 5158 | + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 5159 | + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 5160 | + "license": "MIT" | |
| 5161 | + }, | |
| 5162 | + "node_modules/fast-glob": { | |
| 5163 | + "version": "3.3.3", | |
| 5164 | + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", | |
| 5165 | + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", | |
| 5166 | + "dev": true, | |
| 5167 | + "license": "MIT", | |
| 5168 | + "dependencies": { | |
| 5169 | + "@nodelib/fs.stat": "^2.0.2", | |
| 5170 | + "@nodelib/fs.walk": "^1.2.3", | |
| 5171 | + "glob-parent": "^5.1.2", | |
| 5172 | + "merge2": "^1.3.0", | |
| 5173 | + "micromatch": "^4.0.8" | |
| 5174 | + }, | |
| 5175 | + "engines": { | |
| 5176 | + "node": ">=8.6.0" | |
| 5177 | + } | |
| 5178 | + }, | |
| 5179 | + "node_modules/fast-glob/node_modules/glob-parent": { | |
| 5180 | + "version": "5.1.2", | |
| 5181 | + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", | |
| 5182 | + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", | |
| 5183 | + "dev": true, | |
| 5184 | + "license": "ISC", | |
| 5185 | + "dependencies": { | |
| 5186 | + "is-glob": "^4.0.1" | |
| 5187 | + }, | |
| 5188 | + "engines": { | |
| 5189 | + "node": ">= 6" | |
| 5190 | + } | |
| 5191 | + }, | |
| 5192 | + "node_modules/fast-json-stable-stringify": { | |
| 5193 | + "version": "2.1.0", | |
| 5194 | + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", | |
| 5195 | + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", | |
| 5196 | + "dev": true, | |
| 5197 | + "license": "MIT" | |
| 5198 | + }, | |
| 5199 | + "node_modules/fast-levenshtein": { | |
| 5200 | + "version": "2.0.6", | |
| 5201 | + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", | |
| 5202 | + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", | |
| 5203 | + "dev": true, | |
| 5204 | + "license": "MIT" | |
| 5205 | + }, | |
| 5206 | + "node_modules/fastq": { | |
| 5207 | + "version": "1.20.1", | |
| 5208 | + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", | |
| 5209 | + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", | |
| 5210 | + "dev": true, | |
| 5211 | + "license": "ISC", | |
| 5212 | + "dependencies": { | |
| 5213 | + "reusify": "^1.0.4" | |
| 5214 | + } | |
| 5215 | + }, | |
| 5216 | + "node_modules/fault": { | |
| 5217 | + "version": "1.0.4", | |
| 5218 | + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", | |
| 5219 | + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", | |
| 5220 | + "license": "MIT", | |
| 5221 | + "dependencies": { | |
| 5222 | + "format": "^0.2.0" | |
| 5223 | + }, | |
| 5224 | + "funding": { | |
| 5225 | + "type": "github", | |
| 5226 | + "url": "https://github.com/sponsors/wooorm" | |
| 5227 | + } | |
| 5228 | + }, | |
| 5229 | + "node_modules/file-entry-cache": { | |
| 5230 | + "version": "6.0.1", | |
| 5231 | + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", | |
| 5232 | + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", | |
| 5233 | + "dev": true, | |
| 5234 | + "license": "MIT", | |
| 5235 | + "dependencies": { | |
| 5236 | + "flat-cache": "^3.0.4" | |
| 5237 | + }, | |
| 5238 | + "engines": { | |
| 5239 | + "node": "^10.12.0 || >=12.0.0" | |
| 5240 | + } | |
| 5241 | + }, | |
| 5242 | + "node_modules/file-selector": { | |
| 5243 | + "version": "2.1.2", | |
| 5244 | + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", | |
| 5245 | + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", | |
| 5246 | + "license": "MIT", | |
| 5247 | + "dependencies": { | |
| 5248 | + "tslib": "^2.7.0" | |
| 5249 | + }, | |
| 5250 | + "engines": { | |
| 5251 | + "node": ">= 12" | |
| 5252 | + } | |
| 5253 | + }, | |
| 5254 | + "node_modules/fill-range": { | |
| 5255 | + "version": "7.1.1", | |
| 5256 | + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", | |
| 5257 | + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", | |
| 5258 | + "dev": true, | |
| 5259 | + "license": "MIT", | |
| 5260 | + "dependencies": { | |
| 5261 | + "to-regex-range": "^5.0.1" | |
| 5262 | + }, | |
| 5263 | + "engines": { | |
| 5264 | + "node": ">=8" | |
| 5265 | + } | |
| 5266 | + }, | |
| 5267 | + "node_modules/find-up": { | |
| 5268 | + "version": "5.0.0", | |
| 5269 | + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", | |
| 5270 | + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", | |
| 5271 | + "dev": true, | |
| 5272 | + "license": "MIT", | |
| 5273 | + "dependencies": { | |
| 5274 | + "locate-path": "^6.0.0", | |
| 5275 | + "path-exists": "^4.0.0" | |
| 5276 | + }, | |
| 5277 | + "engines": { | |
| 5278 | + "node": ">=10" | |
| 5279 | + }, | |
| 5280 | + "funding": { | |
| 5281 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 5282 | + } | |
| 5283 | + }, | |
| 5284 | + "node_modules/flat-cache": { | |
| 5285 | + "version": "3.2.0", | |
| 5286 | + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", | |
| 5287 | + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", | |
| 5288 | + "dev": true, | |
| 5289 | + "license": "MIT", | |
| 5290 | + "dependencies": { | |
| 5291 | + "flatted": "^3.2.9", | |
| 5292 | + "keyv": "^4.5.3", | |
| 5293 | + "rimraf": "^3.0.2" | |
| 5294 | + }, | |
| 5295 | + "engines": { | |
| 5296 | + "node": "^10.12.0 || >=12.0.0" | |
| 5297 | + } | |
| 5298 | + }, | |
| 5299 | + "node_modules/flatted": { | |
| 5300 | + "version": "3.3.3", | |
| 5301 | + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", | |
| 5302 | + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", | |
| 5303 | + "dev": true, | |
| 5304 | + "license": "ISC" | |
| 5305 | + }, | |
| 5306 | + "node_modules/for-each": { | |
| 5307 | + "version": "0.3.5", | |
| 5308 | + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", | |
| 5309 | + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", | |
| 5310 | + "dev": true, | |
| 5311 | + "license": "MIT", | |
| 5312 | + "dependencies": { | |
| 5313 | + "is-callable": "^1.2.7" | |
| 5314 | + }, | |
| 5315 | + "engines": { | |
| 5316 | + "node": ">= 0.4" | |
| 5317 | + }, | |
| 5318 | + "funding": { | |
| 5319 | + "url": "https://github.com/sponsors/ljharb" | |
| 5320 | + } | |
| 5321 | + }, | |
| 5322 | + "node_modules/foreground-child": { | |
| 5323 | + "version": "3.3.1", | |
| 5324 | + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", | |
| 5325 | + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", | |
| 5326 | + "license": "ISC", | |
| 5327 | + "dependencies": { | |
| 5328 | + "cross-spawn": "^7.0.6", | |
| 5329 | + "signal-exit": "^4.0.1" | |
| 5330 | + }, | |
| 5331 | + "engines": { | |
| 5332 | + "node": ">=14" | |
| 5333 | + }, | |
| 5334 | + "funding": { | |
| 5335 | + "url": "https://github.com/sponsors/isaacs" | |
| 5336 | + } | |
| 5337 | + }, | |
| 5338 | + "node_modules/format": { | |
| 5339 | + "version": "0.2.2", | |
| 5340 | + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", | |
| 5341 | + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", | |
| 5342 | + "engines": { | |
| 5343 | + "node": ">=0.4.x" | |
| 5344 | + } | |
| 5345 | + }, | |
| 5346 | + "node_modules/framer-motion": { | |
| 5347 | + "version": "12.34.0", | |
| 5348 | + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.0.tgz", | |
| 5349 | + "integrity": "sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==", | |
| 5350 | + "license": "MIT", | |
| 5351 | + "dependencies": { | |
| 5352 | + "motion-dom": "^12.34.0", | |
| 5353 | + "motion-utils": "^12.29.2", | |
| 5354 | + "tslib": "^2.4.0" | |
| 5355 | + }, | |
| 5356 | + "peerDependencies": { | |
| 5357 | + "@emotion/is-prop-valid": "*", | |
| 5358 | + "react": "^18.0.0 || ^19.0.0", | |
| 5359 | + "react-dom": "^18.0.0 || ^19.0.0" | |
| 5360 | + }, | |
| 5361 | + "peerDependenciesMeta": { | |
| 5362 | + "@emotion/is-prop-valid": { | |
| 5363 | + "optional": true | |
| 5364 | + }, | |
| 5365 | + "react": { | |
| 5366 | + "optional": true | |
| 5367 | + }, | |
| 5368 | + "react-dom": { | |
| 5369 | + "optional": true | |
| 5370 | + } | |
| 5371 | + } | |
| 5372 | + }, | |
| 5373 | + "node_modules/fs.realpath": { | |
| 5374 | + "version": "1.0.0", | |
| 5375 | + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | |
| 5376 | + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", | |
| 5377 | + "dev": true, | |
| 5378 | + "license": "ISC" | |
| 5379 | + }, | |
| 5380 | + "node_modules/fsevents": { | |
| 5381 | + "version": "2.3.3", | |
| 5382 | + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", | |
| 5383 | + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", | |
| 5384 | + "hasInstallScript": true, | |
| 5385 | + "license": "MIT", | |
| 5386 | + "optional": true, | |
| 5387 | + "os": [ | |
| 5388 | + "darwin" | |
| 5389 | + ], | |
| 5390 | + "engines": { | |
| 5391 | + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" | |
| 5392 | + } | |
| 5393 | + }, | |
| 5394 | + "node_modules/function-bind": { | |
| 5395 | + "version": "1.1.2", | |
| 5396 | + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", | |
| 5397 | + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", | |
| 5398 | + "dev": true, | |
| 5399 | + "license": "MIT", | |
| 5400 | + "funding": { | |
| 5401 | + "url": "https://github.com/sponsors/ljharb" | |
| 5402 | + } | |
| 5403 | + }, | |
| 5404 | + "node_modules/function.prototype.name": { | |
| 5405 | + "version": "1.1.8", | |
| 5406 | + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", | |
| 5407 | + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", | |
| 5408 | + "dev": true, | |
| 5409 | + "license": "MIT", | |
| 5410 | + "dependencies": { | |
| 5411 | + "call-bind": "^1.0.8", | |
| 5412 | + "call-bound": "^1.0.3", | |
| 5413 | + "define-properties": "^1.2.1", | |
| 5414 | + "functions-have-names": "^1.2.3", | |
| 5415 | + "hasown": "^2.0.2", | |
| 5416 | + "is-callable": "^1.2.7" | |
| 5417 | + }, | |
| 5418 | + "engines": { | |
| 5419 | + "node": ">= 0.4" | |
| 5420 | + }, | |
| 5421 | + "funding": { | |
| 5422 | + "url": "https://github.com/sponsors/ljharb" | |
| 5423 | + } | |
| 5424 | + }, | |
| 5425 | + "node_modules/functions-have-names": { | |
| 5426 | + "version": "1.2.3", | |
| 5427 | + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", | |
| 5428 | + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", | |
| 5429 | + "dev": true, | |
| 5430 | + "license": "MIT", | |
| 5431 | + "funding": { | |
| 5432 | + "url": "https://github.com/sponsors/ljharb" | |
| 5433 | + } | |
| 5434 | + }, | |
| 5435 | + "node_modules/generator-function": { | |
| 5436 | + "version": "2.0.1", | |
| 5437 | + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", | |
| 5438 | + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", | |
| 5439 | + "dev": true, | |
| 5440 | + "license": "MIT", | |
| 5441 | + "engines": { | |
| 5442 | + "node": ">= 0.4" | |
| 5443 | + } | |
| 5444 | + }, | |
| 5445 | + "node_modules/get-caller-file": { | |
| 5446 | + "version": "2.0.5", | |
| 5447 | + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", | |
| 5448 | + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", | |
| 5449 | + "license": "ISC", | |
| 5450 | + "engines": { | |
| 5451 | + "node": "6.* || 8.* || >= 10.*" | |
| 5452 | + } | |
| 5453 | + }, | |
| 5454 | + "node_modules/get-intrinsic": { | |
| 5455 | + "version": "1.3.0", | |
| 5456 | + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", | |
| 5457 | + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", | |
| 5458 | + "dev": true, | |
| 5459 | + "license": "MIT", | |
| 5460 | + "dependencies": { | |
| 5461 | + "call-bind-apply-helpers": "^1.0.2", | |
| 5462 | + "es-define-property": "^1.0.1", | |
| 5463 | + "es-errors": "^1.3.0", | |
| 5464 | + "es-object-atoms": "^1.1.1", | |
| 5465 | + "function-bind": "^1.1.2", | |
| 5466 | + "get-proto": "^1.0.1", | |
| 5467 | + "gopd": "^1.2.0", | |
| 5468 | + "has-symbols": "^1.1.0", | |
| 5469 | + "hasown": "^2.0.2", | |
| 5470 | + "math-intrinsics": "^1.1.0" | |
| 5471 | + }, | |
| 5472 | + "engines": { | |
| 5473 | + "node": ">= 0.4" | |
| 5474 | + }, | |
| 5475 | + "funding": { | |
| 5476 | + "url": "https://github.com/sponsors/ljharb" | |
| 5477 | + } | |
| 5478 | + }, | |
| 5479 | + "node_modules/get-proto": { | |
| 5480 | + "version": "1.0.1", | |
| 5481 | + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", | |
| 5482 | + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", | |
| 5483 | + "dev": true, | |
| 5484 | + "license": "MIT", | |
| 5485 | + "dependencies": { | |
| 5486 | + "dunder-proto": "^1.0.1", | |
| 5487 | + "es-object-atoms": "^1.0.0" | |
| 5488 | + }, | |
| 5489 | + "engines": { | |
| 5490 | + "node": ">= 0.4" | |
| 5491 | + } | |
| 5492 | + }, | |
| 5493 | + "node_modules/get-symbol-description": { | |
| 5494 | + "version": "1.1.0", | |
| 5495 | + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", | |
| 5496 | + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", | |
| 5497 | + "dev": true, | |
| 5498 | + "license": "MIT", | |
| 5499 | + "dependencies": { | |
| 5500 | + "call-bound": "^1.0.3", | |
| 5501 | + "es-errors": "^1.3.0", | |
| 5502 | + "get-intrinsic": "^1.2.6" | |
| 5503 | + }, | |
| 5504 | + "engines": { | |
| 5505 | + "node": ">= 0.4" | |
| 5506 | + }, | |
| 5507 | + "funding": { | |
| 5508 | + "url": "https://github.com/sponsors/ljharb" | |
| 5509 | + } | |
| 5510 | + }, | |
| 5511 | + "node_modules/get-tsconfig": { | |
| 5512 | + "version": "4.13.6", | |
| 5513 | + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", | |
| 5514 | + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", | |
| 5515 | + "dev": true, | |
| 5516 | + "license": "MIT", | |
| 5517 | + "dependencies": { | |
| 5518 | + "resolve-pkg-maps": "^1.0.0" | |
| 5519 | + }, | |
| 5520 | + "funding": { | |
| 5521 | + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" | |
| 5522 | + } | |
| 5523 | + }, | |
| 5524 | + "node_modules/glob": { | |
| 5525 | + "version": "10.3.10", | |
| 5526 | + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", | |
| 5527 | + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", | |
| 5528 | + "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", | |
| 5529 | + "license": "ISC", | |
| 5530 | + "dependencies": { | |
| 5531 | + "foreground-child": "^3.1.0", | |
| 5532 | + "jackspeak": "^2.3.5", | |
| 5533 | + "minimatch": "^9.0.1", | |
| 5534 | + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", | |
| 5535 | + "path-scurry": "^1.10.1" | |
| 5536 | + }, | |
| 5537 | + "bin": { | |
| 5538 | + "glob": "dist/esm/bin.mjs" | |
| 5539 | + }, | |
| 5540 | + "engines": { | |
| 5541 | + "node": ">=16 || 14 >=14.17" | |
| 5542 | + }, | |
| 5543 | + "funding": { | |
| 5544 | + "url": "https://github.com/sponsors/isaacs" | |
| 5545 | + } | |
| 5546 | + }, | |
| 5547 | + "node_modules/glob-parent": { | |
| 5548 | + "version": "6.0.2", | |
| 5549 | + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", | |
| 5550 | + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", | |
| 5551 | + "dev": true, | |
| 5552 | + "license": "ISC", | |
| 5553 | + "dependencies": { | |
| 5554 | + "is-glob": "^4.0.3" | |
| 5555 | + }, | |
| 5556 | + "engines": { | |
| 5557 | + "node": ">=10.13.0" | |
| 5558 | + } | |
| 5559 | + }, | |
| 5560 | + "node_modules/glob/node_modules/brace-expansion": { | |
| 5561 | + "version": "2.0.2", | |
| 5562 | + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | |
| 5563 | + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | |
| 5564 | + "license": "MIT", | |
| 5565 | + "dependencies": { | |
| 5566 | + "balanced-match": "^1.0.0" | |
| 5567 | + } | |
| 5568 | + }, | |
| 5569 | + "node_modules/glob/node_modules/minimatch": { | |
| 5570 | + "version": "9.0.5", | |
| 5571 | + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", | |
| 5572 | + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", | |
| 5573 | + "license": "ISC", | |
| 5574 | + "dependencies": { | |
| 5575 | + "brace-expansion": "^2.0.1" | |
| 5576 | + }, | |
| 5577 | + "engines": { | |
| 5578 | + "node": ">=16 || 14 >=14.17" | |
| 5579 | + }, | |
| 5580 | + "funding": { | |
| 5581 | + "url": "https://github.com/sponsors/isaacs" | |
| 5582 | + } | |
| 5583 | + }, | |
| 5584 | + "node_modules/globals": { | |
| 5585 | + "version": "13.24.0", | |
| 5586 | + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", | |
| 5587 | + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", | |
| 5588 | + "dev": true, | |
| 5589 | + "license": "MIT", | |
| 5590 | + "dependencies": { | |
| 5591 | + "type-fest": "^0.20.2" | |
| 5592 | + }, | |
| 5593 | + "engines": { | |
| 5594 | + "node": ">=8" | |
| 5595 | + }, | |
| 5596 | + "funding": { | |
| 5597 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 5598 | + } | |
| 5599 | + }, | |
| 5600 | + "node_modules/globalthis": { | |
| 5601 | + "version": "1.0.4", | |
| 5602 | + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", | |
| 5603 | + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", | |
| 5604 | + "dev": true, | |
| 5605 | + "license": "MIT", | |
| 5606 | + "dependencies": { | |
| 5607 | + "define-properties": "^1.2.1", | |
| 5608 | + "gopd": "^1.0.1" | |
| 5609 | + }, | |
| 5610 | + "engines": { | |
| 5611 | + "node": ">= 0.4" | |
| 5612 | + }, | |
| 5613 | + "funding": { | |
| 5614 | + "url": "https://github.com/sponsors/ljharb" | |
| 5615 | + } | |
| 5616 | + }, | |
| 5617 | + "node_modules/goober": { | |
| 5618 | + "version": "2.1.18", | |
| 5619 | + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", | |
| 5620 | + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", | |
| 5621 | + "license": "MIT", | |
| 5622 | + "peerDependencies": { | |
| 5623 | + "csstype": "^3.0.10" | |
| 5624 | + } | |
| 5625 | + }, | |
| 5626 | + "node_modules/gopd": { | |
| 5627 | + "version": "1.2.0", | |
| 5628 | + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", | |
| 5629 | + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", | |
| 5630 | + "dev": true, | |
| 5631 | + "license": "MIT", | |
| 5632 | + "engines": { | |
| 5633 | + "node": ">= 0.4" | |
| 5634 | + }, | |
| 5635 | + "funding": { | |
| 5636 | + "url": "https://github.com/sponsors/ljharb" | |
| 5637 | + } | |
| 5638 | + }, | |
| 5639 | + "node_modules/graceful-fs": { | |
| 5640 | + "version": "4.2.11", | |
| 5641 | + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", | |
| 5642 | + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", | |
| 5643 | + "license": "ISC" | |
| 5644 | + }, | |
| 5645 | + "node_modules/graphemer": { | |
| 5646 | + "version": "1.4.0", | |
| 5647 | + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", | |
| 5648 | + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", | |
| 5649 | + "dev": true, | |
| 5650 | + "license": "MIT" | |
| 5651 | + }, | |
| 5652 | + "node_modules/has-bigints": { | |
| 5653 | + "version": "1.1.0", | |
| 5654 | + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", | |
| 5655 | + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", | |
| 5656 | + "dev": true, | |
| 5657 | + "license": "MIT", | |
| 5658 | + "engines": { | |
| 5659 | + "node": ">= 0.4" | |
| 5660 | + }, | |
| 5661 | + "funding": { | |
| 5662 | + "url": "https://github.com/sponsors/ljharb" | |
| 5663 | + } | |
| 5664 | + }, | |
| 5665 | + "node_modules/has-flag": { | |
| 5666 | + "version": "4.0.0", | |
| 5667 | + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", | |
| 5668 | + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", | |
| 5669 | + "dev": true, | |
| 5670 | + "license": "MIT", | |
| 5671 | + "engines": { | |
| 5672 | + "node": ">=8" | |
| 5673 | + } | |
| 5674 | + }, | |
| 5675 | + "node_modules/has-property-descriptors": { | |
| 5676 | + "version": "1.0.2", | |
| 5677 | + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", | |
| 5678 | + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", | |
| 5679 | + "dev": true, | |
| 5680 | + "license": "MIT", | |
| 5681 | + "dependencies": { | |
| 5682 | + "es-define-property": "^1.0.0" | |
| 5683 | + }, | |
| 5684 | + "funding": { | |
| 5685 | + "url": "https://github.com/sponsors/ljharb" | |
| 5686 | + } | |
| 5687 | + }, | |
| 5688 | + "node_modules/has-proto": { | |
| 5689 | + "version": "1.2.0", | |
| 5690 | + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", | |
| 5691 | + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", | |
| 5692 | + "dev": true, | |
| 5693 | + "license": "MIT", | |
| 5694 | + "dependencies": { | |
| 5695 | + "dunder-proto": "^1.0.0" | |
| 5696 | + }, | |
| 5697 | + "engines": { | |
| 5698 | + "node": ">= 0.4" | |
| 5699 | + }, | |
| 5700 | + "funding": { | |
| 5701 | + "url": "https://github.com/sponsors/ljharb" | |
| 5702 | + } | |
| 5703 | + }, | |
| 5704 | + "node_modules/has-symbols": { | |
| 5705 | + "version": "1.1.0", | |
| 5706 | + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", | |
| 5707 | + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", | |
| 5708 | + "dev": true, | |
| 5709 | + "license": "MIT", | |
| 5710 | + "engines": { | |
| 5711 | + "node": ">= 0.4" | |
| 5712 | + }, | |
| 5713 | + "funding": { | |
| 5714 | + "url": "https://github.com/sponsors/ljharb" | |
| 5715 | + } | |
| 5716 | + }, | |
| 5717 | + "node_modules/has-tostringtag": { | |
| 5718 | + "version": "1.0.2", | |
| 5719 | + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", | |
| 5720 | + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", | |
| 5721 | + "dev": true, | |
| 5722 | + "license": "MIT", | |
| 5723 | + "dependencies": { | |
| 5724 | + "has-symbols": "^1.0.3" | |
| 5725 | + }, | |
| 5726 | + "engines": { | |
| 5727 | + "node": ">= 0.4" | |
| 5728 | + }, | |
| 5729 | + "funding": { | |
| 5730 | + "url": "https://github.com/sponsors/ljharb" | |
| 5731 | + } | |
| 5732 | + }, | |
| 5733 | + "node_modules/hasown": { | |
| 5734 | + "version": "2.0.2", | |
| 5735 | + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", | |
| 5736 | + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", | |
| 5737 | + "dev": true, | |
| 5738 | + "license": "MIT", | |
| 5739 | + "dependencies": { | |
| 5740 | + "function-bind": "^1.1.2" | |
| 5741 | + }, | |
| 5742 | + "engines": { | |
| 5743 | + "node": ">= 0.4" | |
| 5744 | + } | |
| 5745 | + }, | |
| 5746 | + "node_modules/hast-util-parse-selector": { | |
| 5747 | + "version": "4.0.0", | |
| 5748 | + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", | |
| 5749 | + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", | |
| 5750 | + "license": "MIT", | |
| 5751 | + "dependencies": { | |
| 5752 | + "@types/hast": "^3.0.0" | |
| 5753 | + }, | |
| 5754 | + "funding": { | |
| 5755 | + "type": "opencollective", | |
| 5756 | + "url": "https://opencollective.com/unified" | |
| 5757 | + } | |
| 5758 | + }, | |
| 5759 | + "node_modules/hast-util-to-jsx-runtime": { | |
| 5760 | + "version": "2.3.6", | |
| 5761 | + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", | |
| 5762 | + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", | |
| 5763 | + "license": "MIT", | |
| 5764 | + "dependencies": { | |
| 5765 | + "@types/estree": "^1.0.0", | |
| 5766 | + "@types/hast": "^3.0.0", | |
| 5767 | + "@types/unist": "^3.0.0", | |
| 5768 | + "comma-separated-tokens": "^2.0.0", | |
| 5769 | + "devlop": "^1.0.0", | |
| 5770 | + "estree-util-is-identifier-name": "^3.0.0", | |
| 5771 | + "hast-util-whitespace": "^3.0.0", | |
| 5772 | + "mdast-util-mdx-expression": "^2.0.0", | |
| 5773 | + "mdast-util-mdx-jsx": "^3.0.0", | |
| 5774 | + "mdast-util-mdxjs-esm": "^2.0.0", | |
| 5775 | + "property-information": "^7.0.0", | |
| 5776 | + "space-separated-tokens": "^2.0.0", | |
| 5777 | + "style-to-js": "^1.0.0", | |
| 5778 | + "unist-util-position": "^5.0.0", | |
| 5779 | + "vfile-message": "^4.0.0" | |
| 5780 | + }, | |
| 5781 | + "funding": { | |
| 5782 | + "type": "opencollective", | |
| 5783 | + "url": "https://opencollective.com/unified" | |
| 5784 | + } | |
| 5785 | + }, | |
| 5786 | + "node_modules/hast-util-whitespace": { | |
| 5787 | + "version": "3.0.0", | |
| 5788 | + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", | |
| 5789 | + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", | |
| 5790 | + "license": "MIT", | |
| 5791 | + "dependencies": { | |
| 5792 | + "@types/hast": "^3.0.0" | |
| 5793 | + }, | |
| 5794 | + "funding": { | |
| 5795 | + "type": "opencollective", | |
| 5796 | + "url": "https://opencollective.com/unified" | |
| 5797 | + } | |
| 5798 | + }, | |
| 5799 | + "node_modules/hastscript": { | |
| 5800 | + "version": "9.0.1", | |
| 5801 | + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", | |
| 5802 | + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", | |
| 5803 | + "license": "MIT", | |
| 5804 | + "dependencies": { | |
| 5805 | + "@types/hast": "^3.0.0", | |
| 5806 | + "comma-separated-tokens": "^2.0.0", | |
| 5807 | + "hast-util-parse-selector": "^4.0.0", | |
| 5808 | + "property-information": "^7.0.0", | |
| 5809 | + "space-separated-tokens": "^2.0.0" | |
| 5810 | + }, | |
| 5811 | + "funding": { | |
| 5812 | + "type": "opencollective", | |
| 5813 | + "url": "https://opencollective.com/unified" | |
| 5814 | + } | |
| 5815 | + }, | |
| 5816 | + "node_modules/highlight.js": { | |
| 5817 | + "version": "10.7.3", | |
| 5818 | + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", | |
| 5819 | + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", | |
| 5820 | + "license": "BSD-3-Clause", | |
| 5821 | + "engines": { | |
| 5822 | + "node": "*" | |
| 5823 | + } | |
| 5824 | + }, | |
| 5825 | + "node_modules/highlightjs-vue": { | |
| 5826 | + "version": "1.0.0", | |
| 5827 | + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", | |
| 5828 | + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", | |
| 5829 | + "license": "CC0-1.0" | |
| 5830 | + }, | |
| 5831 | + "node_modules/html-url-attributes": { | |
| 5832 | + "version": "3.0.1", | |
| 5833 | + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", | |
| 5834 | + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", | |
| 5835 | + "license": "MIT", | |
| 5836 | + "funding": { | |
| 5837 | + "type": "opencollective", | |
| 5838 | + "url": "https://opencollective.com/unified" | |
| 5839 | + } | |
| 5840 | + }, | |
| 5841 | + "node_modules/ieee754": { | |
| 5842 | + "version": "1.2.1", | |
| 5843 | + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", | |
| 5844 | + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", | |
| 5845 | + "funding": [ | |
| 5846 | + { | |
| 5847 | + "type": "github", | |
| 5848 | + "url": "https://github.com/sponsors/feross" | |
| 5849 | + }, | |
| 5850 | + { | |
| 5851 | + "type": "patreon", | |
| 5852 | + "url": "https://www.patreon.com/feross" | |
| 5853 | + }, | |
| 5854 | + { | |
| 5855 | + "type": "consulting", | |
| 5856 | + "url": "https://feross.org/support" | |
| 5857 | + } | |
| 5858 | + ], | |
| 5859 | + "license": "BSD-3-Clause" | |
| 5860 | + }, | |
| 5861 | + "node_modules/ignore": { | |
| 5862 | + "version": "5.3.2", | |
| 5863 | + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", | |
| 5864 | + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", | |
| 5865 | + "dev": true, | |
| 5866 | + "license": "MIT", | |
| 5867 | + "engines": { | |
| 5868 | + "node": ">= 4" | |
| 5869 | + } | |
| 5870 | + }, | |
| 5871 | + "node_modules/immediate": { | |
| 5872 | + "version": "3.0.6", | |
| 5873 | + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", | |
| 5874 | + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", | |
| 5875 | + "license": "MIT" | |
| 5876 | + }, | |
| 5877 | + "node_modules/import-fresh": { | |
| 5878 | + "version": "3.3.1", | |
| 5879 | + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", | |
| 5880 | + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", | |
| 5881 | + "dev": true, | |
| 5882 | + "license": "MIT", | |
| 5883 | + "dependencies": { | |
| 5884 | + "parent-module": "^1.0.0", | |
| 5885 | + "resolve-from": "^4.0.0" | |
| 5886 | + }, | |
| 5887 | + "engines": { | |
| 5888 | + "node": ">=6" | |
| 5889 | + }, | |
| 5890 | + "funding": { | |
| 5891 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 5892 | + } | |
| 5893 | + }, | |
| 5894 | + "node_modules/imurmurhash": { | |
| 5895 | + "version": "0.1.4", | |
| 5896 | + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", | |
| 5897 | + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", | |
| 5898 | + "dev": true, | |
| 5899 | + "license": "MIT", | |
| 5900 | + "engines": { | |
| 5901 | + "node": ">=0.8.19" | |
| 5902 | + } | |
| 5903 | + }, | |
| 5904 | + "node_modules/inflight": { | |
| 5905 | + "version": "1.0.6", | |
| 5906 | + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", | |
| 5907 | + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", | |
| 5908 | + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", | |
| 5909 | + "dev": true, | |
| 5910 | + "license": "ISC", | |
| 5911 | + "dependencies": { | |
| 5912 | + "once": "^1.3.0", | |
| 5913 | + "wrappy": "1" | |
| 5914 | + } | |
| 5915 | + }, | |
| 5916 | + "node_modules/inherits": { | |
| 5917 | + "version": "2.0.4", | |
| 5918 | + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", | |
| 5919 | + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", | |
| 5920 | + "license": "ISC" | |
| 5921 | + }, | |
| 5922 | + "node_modules/inline-style-parser": { | |
| 5923 | + "version": "0.2.7", | |
| 5924 | + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", | |
| 5925 | + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", | |
| 5926 | + "license": "MIT" | |
| 5927 | + }, | |
| 5928 | + "node_modules/internal-slot": { | |
| 5929 | + "version": "1.1.0", | |
| 5930 | + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", | |
| 5931 | + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", | |
| 5932 | + "dev": true, | |
| 5933 | + "license": "MIT", | |
| 5934 | + "dependencies": { | |
| 5935 | + "es-errors": "^1.3.0", | |
| 5936 | + "hasown": "^2.0.2", | |
| 5937 | + "side-channel": "^1.1.0" | |
| 5938 | + }, | |
| 5939 | + "engines": { | |
| 5940 | + "node": ">= 0.4" | |
| 5941 | + } | |
| 5942 | + }, | |
| 5943 | + "node_modules/iron-session": { | |
| 5944 | + "version": "8.0.4", | |
| 5945 | + "resolved": "https://registry.npmjs.org/iron-session/-/iron-session-8.0.4.tgz", | |
| 5946 | + "integrity": "sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA==", | |
| 5947 | + "funding": [ | |
| 5948 | + "https://github.com/sponsors/vvo", | |
| 5949 | + "https://github.com/sponsors/brc-dd" | |
| 5950 | + ], | |
| 5951 | + "license": "MIT", | |
| 5952 | + "dependencies": { | |
| 5953 | + "cookie": "^0.7.2", | |
| 5954 | + "iron-webcrypto": "^1.2.1", | |
| 5955 | + "uncrypto": "^0.1.3" | |
| 5956 | + } | |
| 5957 | + }, | |
| 5958 | + "node_modules/iron-webcrypto": { | |
| 5959 | + "version": "1.2.1", | |
| 5960 | + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", | |
| 5961 | + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", | |
| 5962 | + "license": "MIT", | |
| 5963 | + "funding": { | |
| 5964 | + "url": "https://github.com/sponsors/brc-dd" | |
| 5965 | + } | |
| 5966 | + }, | |
| 5967 | + "node_modules/is-alphabetical": { | |
| 5968 | + "version": "2.0.1", | |
| 5969 | + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", | |
| 5970 | + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", | |
| 5971 | + "license": "MIT", | |
| 5972 | + "funding": { | |
| 5973 | + "type": "github", | |
| 5974 | + "url": "https://github.com/sponsors/wooorm" | |
| 5975 | + } | |
| 5976 | + }, | |
| 5977 | + "node_modules/is-alphanumerical": { | |
| 5978 | + "version": "2.0.1", | |
| 5979 | + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", | |
| 5980 | + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", | |
| 5981 | + "license": "MIT", | |
| 5982 | + "dependencies": { | |
| 5983 | + "is-alphabetical": "^2.0.0", | |
| 5984 | + "is-decimal": "^2.0.0" | |
| 5985 | + }, | |
| 5986 | + "funding": { | |
| 5987 | + "type": "github", | |
| 5988 | + "url": "https://github.com/sponsors/wooorm" | |
| 5989 | + } | |
| 5990 | + }, | |
| 5991 | + "node_modules/is-array-buffer": { | |
| 5992 | + "version": "3.0.5", | |
| 5993 | + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", | |
| 5994 | + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", | |
| 5995 | + "dev": true, | |
| 5996 | + "license": "MIT", | |
| 5997 | + "dependencies": { | |
| 5998 | + "call-bind": "^1.0.8", | |
| 5999 | + "call-bound": "^1.0.3", | |
| 6000 | + "get-intrinsic": "^1.2.6" | |
| 6001 | + }, | |
| 6002 | + "engines": { | |
| 6003 | + "node": ">= 0.4" | |
| 6004 | + }, | |
| 6005 | + "funding": { | |
| 6006 | + "url": "https://github.com/sponsors/ljharb" | |
| 6007 | + } | |
| 6008 | + }, | |
| 6009 | + "node_modules/is-async-function": { | |
| 6010 | + "version": "2.1.1", | |
| 6011 | + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", | |
| 6012 | + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", | |
| 6013 | + "dev": true, | |
| 6014 | + "license": "MIT", | |
| 6015 | + "dependencies": { | |
| 6016 | + "async-function": "^1.0.0", | |
| 6017 | + "call-bound": "^1.0.3", | |
| 6018 | + "get-proto": "^1.0.1", | |
| 6019 | + "has-tostringtag": "^1.0.2", | |
| 6020 | + "safe-regex-test": "^1.1.0" | |
| 6021 | + }, | |
| 6022 | + "engines": { | |
| 6023 | + "node": ">= 0.4" | |
| 6024 | + }, | |
| 6025 | + "funding": { | |
| 6026 | + "url": "https://github.com/sponsors/ljharb" | |
| 6027 | + } | |
| 6028 | + }, | |
| 6029 | + "node_modules/is-bigint": { | |
| 6030 | + "version": "1.1.0", | |
| 6031 | + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", | |
| 6032 | + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", | |
| 6033 | + "dev": true, | |
| 6034 | + "license": "MIT", | |
| 6035 | + "dependencies": { | |
| 6036 | + "has-bigints": "^1.0.2" | |
| 6037 | + }, | |
| 6038 | + "engines": { | |
| 6039 | + "node": ">= 0.4" | |
| 6040 | + }, | |
| 6041 | + "funding": { | |
| 6042 | + "url": "https://github.com/sponsors/ljharb" | |
| 6043 | + } | |
| 6044 | + }, | |
| 6045 | + "node_modules/is-binary-path": { | |
| 6046 | + "version": "2.1.0", | |
| 6047 | + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", | |
| 6048 | + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", | |
| 6049 | + "dev": true, | |
| 6050 | + "license": "MIT", | |
| 6051 | + "dependencies": { | |
| 6052 | + "binary-extensions": "^2.0.0" | |
| 6053 | + }, | |
| 6054 | + "engines": { | |
| 6055 | + "node": ">=8" | |
| 6056 | + } | |
| 6057 | + }, | |
| 6058 | + "node_modules/is-boolean-object": { | |
| 6059 | + "version": "1.2.2", | |
| 6060 | + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", | |
| 6061 | + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", | |
| 6062 | + "dev": true, | |
| 6063 | + "license": "MIT", | |
| 6064 | + "dependencies": { | |
| 6065 | + "call-bound": "^1.0.3", | |
| 6066 | + "has-tostringtag": "^1.0.2" | |
| 6067 | + }, | |
| 6068 | + "engines": { | |
| 6069 | + "node": ">= 0.4" | |
| 6070 | + }, | |
| 6071 | + "funding": { | |
| 6072 | + "url": "https://github.com/sponsors/ljharb" | |
| 6073 | + } | |
| 6074 | + }, | |
| 6075 | + "node_modules/is-bun-module": { | |
| 6076 | + "version": "2.0.0", | |
| 6077 | + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", | |
| 6078 | + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", | |
| 6079 | + "dev": true, | |
| 6080 | + "license": "MIT", | |
| 6081 | + "dependencies": { | |
| 6082 | + "semver": "^7.7.1" | |
| 6083 | + } | |
| 6084 | + }, | |
| 6085 | + "node_modules/is-callable": { | |
| 6086 | + "version": "1.2.7", | |
| 6087 | + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", | |
| 6088 | + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", | |
| 6089 | + "dev": true, | |
| 6090 | + "license": "MIT", | |
| 6091 | + "engines": { | |
| 6092 | + "node": ">= 0.4" | |
| 6093 | + }, | |
| 6094 | + "funding": { | |
| 6095 | + "url": "https://github.com/sponsors/ljharb" | |
| 6096 | + } | |
| 6097 | + }, | |
| 6098 | + "node_modules/is-core-module": { | |
| 6099 | + "version": "2.16.1", | |
| 6100 | + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", | |
| 6101 | + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", | |
| 6102 | + "dev": true, | |
| 6103 | + "license": "MIT", | |
| 6104 | + "dependencies": { | |
| 6105 | + "hasown": "^2.0.2" | |
| 6106 | + }, | |
| 6107 | + "engines": { | |
| 6108 | + "node": ">= 0.4" | |
| 6109 | + }, | |
| 6110 | + "funding": { | |
| 6111 | + "url": "https://github.com/sponsors/ljharb" | |
| 6112 | + } | |
| 6113 | + }, | |
| 6114 | + "node_modules/is-data-view": { | |
| 6115 | + "version": "1.0.2", | |
| 6116 | + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", | |
| 6117 | + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", | |
| 6118 | + "dev": true, | |
| 6119 | + "license": "MIT", | |
| 6120 | + "dependencies": { | |
| 6121 | + "call-bound": "^1.0.2", | |
| 6122 | + "get-intrinsic": "^1.2.6", | |
| 6123 | + "is-typed-array": "^1.1.13" | |
| 6124 | + }, | |
| 6125 | + "engines": { | |
| 6126 | + "node": ">= 0.4" | |
| 6127 | + }, | |
| 6128 | + "funding": { | |
| 6129 | + "url": "https://github.com/sponsors/ljharb" | |
| 6130 | + } | |
| 6131 | + }, | |
| 6132 | + "node_modules/is-date-object": { | |
| 6133 | + "version": "1.1.0", | |
| 6134 | + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", | |
| 6135 | + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", | |
| 6136 | + "dev": true, | |
| 6137 | + "license": "MIT", | |
| 6138 | + "dependencies": { | |
| 6139 | + "call-bound": "^1.0.2", | |
| 6140 | + "has-tostringtag": "^1.0.2" | |
| 6141 | + }, | |
| 6142 | + "engines": { | |
| 6143 | + "node": ">= 0.4" | |
| 6144 | + }, | |
| 6145 | + "funding": { | |
| 6146 | + "url": "https://github.com/sponsors/ljharb" | |
| 6147 | + } | |
| 6148 | + }, | |
| 6149 | + "node_modules/is-decimal": { | |
| 6150 | + "version": "2.0.1", | |
| 6151 | + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", | |
| 6152 | + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", | |
| 6153 | + "license": "MIT", | |
| 6154 | + "funding": { | |
| 6155 | + "type": "github", | |
| 6156 | + "url": "https://github.com/sponsors/wooorm" | |
| 6157 | + } | |
| 6158 | + }, | |
| 6159 | + "node_modules/is-extglob": { | |
| 6160 | + "version": "2.1.1", | |
| 6161 | + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", | |
| 6162 | + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", | |
| 6163 | + "dev": true, | |
| 6164 | + "license": "MIT", | |
| 6165 | + "engines": { | |
| 6166 | + "node": ">=0.10.0" | |
| 6167 | + } | |
| 6168 | + }, | |
| 6169 | + "node_modules/is-finalizationregistry": { | |
| 6170 | + "version": "1.1.1", | |
| 6171 | + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", | |
| 6172 | + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", | |
| 6173 | + "dev": true, | |
| 6174 | + "license": "MIT", | |
| 6175 | + "dependencies": { | |
| 6176 | + "call-bound": "^1.0.3" | |
| 6177 | + }, | |
| 6178 | + "engines": { | |
| 6179 | + "node": ">= 0.4" | |
| 6180 | + }, | |
| 6181 | + "funding": { | |
| 6182 | + "url": "https://github.com/sponsors/ljharb" | |
| 6183 | + } | |
| 6184 | + }, | |
| 6185 | + "node_modules/is-fullwidth-code-point": { | |
| 6186 | + "version": "3.0.0", | |
| 6187 | + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", | |
| 6188 | + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", | |
| 6189 | + "license": "MIT", | |
| 6190 | + "engines": { | |
| 6191 | + "node": ">=8" | |
| 6192 | + } | |
| 6193 | + }, | |
| 6194 | + "node_modules/is-generator-function": { | |
| 6195 | + "version": "1.1.2", | |
| 6196 | + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", | |
| 6197 | + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", | |
| 6198 | + "dev": true, | |
| 6199 | + "license": "MIT", | |
| 6200 | + "dependencies": { | |
| 6201 | + "call-bound": "^1.0.4", | |
| 6202 | + "generator-function": "^2.0.0", | |
| 6203 | + "get-proto": "^1.0.1", | |
| 6204 | + "has-tostringtag": "^1.0.2", | |
| 6205 | + "safe-regex-test": "^1.1.0" | |
| 6206 | + }, | |
| 6207 | + "engines": { | |
| 6208 | + "node": ">= 0.4" | |
| 6209 | + }, | |
| 6210 | + "funding": { | |
| 6211 | + "url": "https://github.com/sponsors/ljharb" | |
| 6212 | + } | |
| 6213 | + }, | |
| 6214 | + "node_modules/is-glob": { | |
| 6215 | + "version": "4.0.3", | |
| 6216 | + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", | |
| 6217 | + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", | |
| 6218 | + "dev": true, | |
| 6219 | + "license": "MIT", | |
| 6220 | + "dependencies": { | |
| 6221 | + "is-extglob": "^2.1.1" | |
| 6222 | + }, | |
| 6223 | + "engines": { | |
| 6224 | + "node": ">=0.10.0" | |
| 6225 | + } | |
| 6226 | + }, | |
| 6227 | + "node_modules/is-hexadecimal": { | |
| 6228 | + "version": "2.0.1", | |
| 6229 | + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", | |
| 6230 | + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", | |
| 6231 | + "license": "MIT", | |
| 6232 | + "funding": { | |
| 6233 | + "type": "github", | |
| 6234 | + "url": "https://github.com/sponsors/wooorm" | |
| 6235 | + } | |
| 6236 | + }, | |
| 6237 | + "node_modules/is-map": { | |
| 6238 | + "version": "2.0.3", | |
| 6239 | + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", | |
| 6240 | + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", | |
| 6241 | + "dev": true, | |
| 6242 | + "license": "MIT", | |
| 6243 | + "engines": { | |
| 6244 | + "node": ">= 0.4" | |
| 6245 | + }, | |
| 6246 | + "funding": { | |
| 6247 | + "url": "https://github.com/sponsors/ljharb" | |
| 6248 | + } | |
| 6249 | + }, | |
| 6250 | + "node_modules/is-negative-zero": { | |
| 6251 | + "version": "2.0.3", | |
| 6252 | + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", | |
| 6253 | + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", | |
| 6254 | + "dev": true, | |
| 6255 | + "license": "MIT", | |
| 6256 | + "engines": { | |
| 6257 | + "node": ">= 0.4" | |
| 6258 | + }, | |
| 6259 | + "funding": { | |
| 6260 | + "url": "https://github.com/sponsors/ljharb" | |
| 6261 | + } | |
| 6262 | + }, | |
| 6263 | + "node_modules/is-number": { | |
| 6264 | + "version": "7.0.0", | |
| 6265 | + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", | |
| 6266 | + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", | |
| 6267 | + "dev": true, | |
| 6268 | + "license": "MIT", | |
| 6269 | + "engines": { | |
| 6270 | + "node": ">=0.12.0" | |
| 6271 | + } | |
| 6272 | + }, | |
| 6273 | + "node_modules/is-number-object": { | |
| 6274 | + "version": "1.1.1", | |
| 6275 | + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", | |
| 6276 | + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", | |
| 6277 | + "dev": true, | |
| 6278 | + "license": "MIT", | |
| 6279 | + "dependencies": { | |
| 6280 | + "call-bound": "^1.0.3", | |
| 6281 | + "has-tostringtag": "^1.0.2" | |
| 6282 | + }, | |
| 6283 | + "engines": { | |
| 6284 | + "node": ">= 0.4" | |
| 6285 | + }, | |
| 6286 | + "funding": { | |
| 6287 | + "url": "https://github.com/sponsors/ljharb" | |
| 6288 | + } | |
| 6289 | + }, | |
| 6290 | + "node_modules/is-path-inside": { | |
| 6291 | + "version": "3.0.3", | |
| 6292 | + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", | |
| 6293 | + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", | |
| 6294 | + "dev": true, | |
| 6295 | + "license": "MIT", | |
| 6296 | + "engines": { | |
| 6297 | + "node": ">=8" | |
| 6298 | + } | |
| 6299 | + }, | |
| 6300 | + "node_modules/is-plain-obj": { | |
| 6301 | + "version": "4.1.0", | |
| 6302 | + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", | |
| 6303 | + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", | |
| 6304 | + "license": "MIT", | |
| 6305 | + "engines": { | |
| 6306 | + "node": ">=12" | |
| 6307 | + }, | |
| 6308 | + "funding": { | |
| 6309 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 6310 | + } | |
| 6311 | + }, | |
| 6312 | + "node_modules/is-regex": { | |
| 6313 | + "version": "1.2.1", | |
| 6314 | + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", | |
| 6315 | + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", | |
| 6316 | + "dev": true, | |
| 6317 | + "license": "MIT", | |
| 6318 | + "dependencies": { | |
| 6319 | + "call-bound": "^1.0.2", | |
| 6320 | + "gopd": "^1.2.0", | |
| 6321 | + "has-tostringtag": "^1.0.2", | |
| 6322 | + "hasown": "^2.0.2" | |
| 6323 | + }, | |
| 6324 | + "engines": { | |
| 6325 | + "node": ">= 0.4" | |
| 6326 | + }, | |
| 6327 | + "funding": { | |
| 6328 | + "url": "https://github.com/sponsors/ljharb" | |
| 6329 | + } | |
| 6330 | + }, | |
| 6331 | + "node_modules/is-set": { | |
| 6332 | + "version": "2.0.3", | |
| 6333 | + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", | |
| 6334 | + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", | |
| 6335 | + "dev": true, | |
| 6336 | + "license": "MIT", | |
| 6337 | + "engines": { | |
| 6338 | + "node": ">= 0.4" | |
| 6339 | + }, | |
| 6340 | + "funding": { | |
| 6341 | + "url": "https://github.com/sponsors/ljharb" | |
| 6342 | + } | |
| 6343 | + }, | |
| 6344 | + "node_modules/is-shared-array-buffer": { | |
| 6345 | + "version": "1.0.4", | |
| 6346 | + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", | |
| 6347 | + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", | |
| 6348 | + "dev": true, | |
| 6349 | + "license": "MIT", | |
| 6350 | + "dependencies": { | |
| 6351 | + "call-bound": "^1.0.3" | |
| 6352 | + }, | |
| 6353 | + "engines": { | |
| 6354 | + "node": ">= 0.4" | |
| 6355 | + }, | |
| 6356 | + "funding": { | |
| 6357 | + "url": "https://github.com/sponsors/ljharb" | |
| 6358 | + } | |
| 6359 | + }, | |
| 6360 | + "node_modules/is-stream": { | |
| 6361 | + "version": "2.0.1", | |
| 6362 | + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", | |
| 6363 | + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", | |
| 6364 | + "license": "MIT", | |
| 6365 | + "engines": { | |
| 6366 | + "node": ">=8" | |
| 6367 | + }, | |
| 6368 | + "funding": { | |
| 6369 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 6370 | + } | |
| 6371 | + }, | |
| 6372 | + "node_modules/is-string": { | |
| 6373 | + "version": "1.1.1", | |
| 6374 | + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", | |
| 6375 | + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", | |
| 6376 | + "dev": true, | |
| 6377 | + "license": "MIT", | |
| 6378 | + "dependencies": { | |
| 6379 | + "call-bound": "^1.0.3", | |
| 6380 | + "has-tostringtag": "^1.0.2" | |
| 6381 | + }, | |
| 6382 | + "engines": { | |
| 6383 | + "node": ">= 0.4" | |
| 6384 | + }, | |
| 6385 | + "funding": { | |
| 6386 | + "url": "https://github.com/sponsors/ljharb" | |
| 6387 | + } | |
| 6388 | + }, | |
| 6389 | + "node_modules/is-symbol": { | |
| 6390 | + "version": "1.1.1", | |
| 6391 | + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", | |
| 6392 | + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", | |
| 6393 | + "dev": true, | |
| 6394 | + "license": "MIT", | |
| 6395 | + "dependencies": { | |
| 6396 | + "call-bound": "^1.0.2", | |
| 6397 | + "has-symbols": "^1.1.0", | |
| 6398 | + "safe-regex-test": "^1.1.0" | |
| 6399 | + }, | |
| 6400 | + "engines": { | |
| 6401 | + "node": ">= 0.4" | |
| 6402 | + }, | |
| 6403 | + "funding": { | |
| 6404 | + "url": "https://github.com/sponsors/ljharb" | |
| 6405 | + } | |
| 6406 | + }, | |
| 6407 | + "node_modules/is-typed-array": { | |
| 6408 | + "version": "1.1.15", | |
| 6409 | + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", | |
| 6410 | + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", | |
| 6411 | + "dev": true, | |
| 6412 | + "license": "MIT", | |
| 6413 | + "dependencies": { | |
| 6414 | + "which-typed-array": "^1.1.16" | |
| 6415 | + }, | |
| 6416 | + "engines": { | |
| 6417 | + "node": ">= 0.4" | |
| 6418 | + }, | |
| 6419 | + "funding": { | |
| 6420 | + "url": "https://github.com/sponsors/ljharb" | |
| 6421 | + } | |
| 6422 | + }, | |
| 6423 | + "node_modules/is-weakmap": { | |
| 6424 | + "version": "2.0.2", | |
| 6425 | + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", | |
| 6426 | + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", | |
| 6427 | + "dev": true, | |
| 6428 | + "license": "MIT", | |
| 6429 | + "engines": { | |
| 6430 | + "node": ">= 0.4" | |
| 6431 | + }, | |
| 6432 | + "funding": { | |
| 6433 | + "url": "https://github.com/sponsors/ljharb" | |
| 6434 | + } | |
| 6435 | + }, | |
| 6436 | + "node_modules/is-weakref": { | |
| 6437 | + "version": "1.1.1", | |
| 6438 | + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", | |
| 6439 | + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", | |
| 6440 | + "dev": true, | |
| 6441 | + "license": "MIT", | |
| 6442 | + "dependencies": { | |
| 6443 | + "call-bound": "^1.0.3" | |
| 6444 | + }, | |
| 6445 | + "engines": { | |
| 6446 | + "node": ">= 0.4" | |
| 6447 | + }, | |
| 6448 | + "funding": { | |
| 6449 | + "url": "https://github.com/sponsors/ljharb" | |
| 6450 | + } | |
| 6451 | + }, | |
| 6452 | + "node_modules/is-weakset": { | |
| 6453 | + "version": "2.0.4", | |
| 6454 | + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", | |
| 6455 | + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", | |
| 6456 | + "dev": true, | |
| 6457 | + "license": "MIT", | |
| 6458 | + "dependencies": { | |
| 6459 | + "call-bound": "^1.0.3", | |
| 6460 | + "get-intrinsic": "^1.2.6" | |
| 6461 | + }, | |
| 6462 | + "engines": { | |
| 6463 | + "node": ">= 0.4" | |
| 6464 | + }, | |
| 6465 | + "funding": { | |
| 6466 | + "url": "https://github.com/sponsors/ljharb" | |
| 6467 | + } | |
| 6468 | + }, | |
| 6469 | + "node_modules/isarray": { | |
| 6470 | + "version": "2.0.5", | |
| 6471 | + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", | |
| 6472 | + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", | |
| 6473 | + "dev": true, | |
| 6474 | + "license": "MIT" | |
| 6475 | + }, | |
| 6476 | + "node_modules/isexe": { | |
| 6477 | + "version": "2.0.0", | |
| 6478 | + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | |
| 6479 | + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", | |
| 6480 | + "license": "ISC" | |
| 6481 | + }, | |
| 6482 | + "node_modules/iterator.prototype": { | |
| 6483 | + "version": "1.1.5", | |
| 6484 | + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", | |
| 6485 | + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", | |
| 6486 | + "dev": true, | |
| 6487 | + "license": "MIT", | |
| 6488 | + "dependencies": { | |
| 6489 | + "define-data-property": "^1.1.4", | |
| 6490 | + "es-object-atoms": "^1.0.0", | |
| 6491 | + "get-intrinsic": "^1.2.6", | |
| 6492 | + "get-proto": "^1.0.0", | |
| 6493 | + "has-symbols": "^1.1.0", | |
| 6494 | + "set-function-name": "^2.0.2" | |
| 6495 | + }, | |
| 6496 | + "engines": { | |
| 6497 | + "node": ">= 0.4" | |
| 6498 | + } | |
| 6499 | + }, | |
| 6500 | + "node_modules/jackspeak": { | |
| 6501 | + "version": "2.3.6", | |
| 6502 | + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", | |
| 6503 | + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", | |
| 6504 | + "license": "BlueOak-1.0.0", | |
| 6505 | + "dependencies": { | |
| 6506 | + "@isaacs/cliui": "^8.0.2" | |
| 6507 | + }, | |
| 6508 | + "engines": { | |
| 6509 | + "node": ">=14" | |
| 6510 | + }, | |
| 6511 | + "funding": { | |
| 6512 | + "url": "https://github.com/sponsors/isaacs" | |
| 6513 | + }, | |
| 6514 | + "optionalDependencies": { | |
| 6515 | + "@pkgjs/parseargs": "^0.11.0" | |
| 6516 | + } | |
| 6517 | + }, | |
| 6518 | + "node_modules/jiti": { | |
| 6519 | + "version": "1.21.7", | |
| 6520 | + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", | |
| 6521 | + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", | |
| 6522 | + "dev": true, | |
| 6523 | + "license": "MIT", | |
| 6524 | + "bin": { | |
| 6525 | + "jiti": "bin/jiti.js" | |
| 6526 | + } | |
| 6527 | + }, | |
| 6528 | + "node_modules/js-tokens": { | |
| 6529 | + "version": "4.0.0", | |
| 6530 | + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", | |
| 6531 | + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", | |
| 6532 | + "license": "MIT" | |
| 6533 | + }, | |
| 6534 | + "node_modules/js-yaml": { | |
| 6535 | + "version": "4.1.1", | |
| 6536 | + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", | |
| 6537 | + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", | |
| 6538 | + "dev": true, | |
| 6539 | + "license": "MIT", | |
| 6540 | + "dependencies": { | |
| 6541 | + "argparse": "^2.0.1" | |
| 6542 | + }, | |
| 6543 | + "bin": { | |
| 6544 | + "js-yaml": "bin/js-yaml.js" | |
| 6545 | + } | |
| 6546 | + }, | |
| 6547 | + "node_modules/json-buffer": { | |
| 6548 | + "version": "3.0.1", | |
| 6549 | + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", | |
| 6550 | + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", | |
| 6551 | + "dev": true, | |
| 6552 | + "license": "MIT" | |
| 6553 | + }, | |
| 6554 | + "node_modules/json-schema-to-ts": { | |
| 6555 | + "version": "3.1.1", | |
| 6556 | + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", | |
| 6557 | + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", | |
| 6558 | + "license": "MIT", | |
| 6559 | + "dependencies": { | |
| 6560 | + "@babel/runtime": "^7.18.3", | |
| 6561 | + "ts-algebra": "^2.0.0" | |
| 6562 | + }, | |
| 6563 | + "engines": { | |
| 6564 | + "node": ">=16" | |
| 6565 | + } | |
| 6566 | + }, | |
| 6567 | + "node_modules/json-schema-traverse": { | |
| 6568 | + "version": "0.4.1", | |
| 6569 | + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", | |
| 6570 | + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", | |
| 6571 | + "dev": true, | |
| 6572 | + "license": "MIT" | |
| 6573 | + }, | |
| 6574 | + "node_modules/json-stable-stringify-without-jsonify": { | |
| 6575 | + "version": "1.0.1", | |
| 6576 | + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", | |
| 6577 | + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", | |
| 6578 | + "dev": true, | |
| 6579 | + "license": "MIT" | |
| 6580 | + }, | |
| 6581 | + "node_modules/json5": { | |
| 6582 | + "version": "1.0.2", | |
| 6583 | + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", | |
| 6584 | + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", | |
| 6585 | + "dev": true, | |
| 6586 | + "license": "MIT", | |
| 6587 | + "dependencies": { | |
| 6588 | + "minimist": "^1.2.0" | |
| 6589 | + }, | |
| 6590 | + "bin": { | |
| 6591 | + "json5": "lib/cli.js" | |
| 6592 | + } | |
| 6593 | + }, | |
| 6594 | + "node_modules/jsx-ast-utils": { | |
| 6595 | + "version": "3.3.5", | |
| 6596 | + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", | |
| 6597 | + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", | |
| 6598 | + "dev": true, | |
| 6599 | + "license": "MIT", | |
| 6600 | + "dependencies": { | |
| 6601 | + "array-includes": "^3.1.6", | |
| 6602 | + "array.prototype.flat": "^1.3.1", | |
| 6603 | + "object.assign": "^4.1.4", | |
| 6604 | + "object.values": "^1.1.6" | |
| 6605 | + }, | |
| 6606 | + "engines": { | |
| 6607 | + "node": ">=4.0" | |
| 6608 | + } | |
| 6609 | + }, | |
| 6610 | + "node_modules/jszip": { | |
| 6611 | + "version": "3.10.1", | |
| 6612 | + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", | |
| 6613 | + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", | |
| 6614 | + "license": "(MIT OR GPL-3.0-or-later)", | |
| 6615 | + "dependencies": { | |
| 6616 | + "lie": "~3.3.0", | |
| 6617 | + "pako": "~1.0.2", | |
| 6618 | + "readable-stream": "~2.3.6", | |
| 6619 | + "setimmediate": "^1.0.5" | |
| 6620 | + } | |
| 6621 | + }, | |
| 6622 | + "node_modules/keyv": { | |
| 6623 | + "version": "4.5.4", | |
| 6624 | + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", | |
| 6625 | + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", | |
| 6626 | + "dev": true, | |
| 6627 | + "license": "MIT", | |
| 6628 | + "dependencies": { | |
| 6629 | + "json-buffer": "3.0.1" | |
| 6630 | + } | |
| 6631 | + }, | |
| 6632 | + "node_modules/language-subtag-registry": { | |
| 6633 | + "version": "0.3.23", | |
| 6634 | + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", | |
| 6635 | + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", | |
| 6636 | + "dev": true, | |
| 6637 | + "license": "CC0-1.0" | |
| 6638 | + }, | |
| 6639 | + "node_modules/language-tags": { | |
| 6640 | + "version": "1.0.9", | |
| 6641 | + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", | |
| 6642 | + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", | |
| 6643 | + "dev": true, | |
| 6644 | + "license": "MIT", | |
| 6645 | + "dependencies": { | |
| 6646 | + "language-subtag-registry": "^0.3.20" | |
| 6647 | + }, | |
| 6648 | + "engines": { | |
| 6649 | + "node": ">=0.10" | |
| 6650 | + } | |
| 6651 | + }, | |
| 6652 | + "node_modules/lazystream": { | |
| 6653 | + "version": "1.0.1", | |
| 6654 | + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", | |
| 6655 | + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", | |
| 6656 | + "license": "MIT", | |
| 6657 | + "dependencies": { | |
| 6658 | + "readable-stream": "^2.0.5" | |
| 6659 | + }, | |
| 6660 | + "engines": { | |
| 6661 | + "node": ">= 0.6.3" | |
| 6662 | + } | |
| 6663 | + }, | |
| 6664 | + "node_modules/levn": { | |
| 6665 | + "version": "0.4.1", | |
| 6666 | + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", | |
| 6667 | + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", | |
| 6668 | + "dev": true, | |
| 6669 | + "license": "MIT", | |
| 6670 | + "dependencies": { | |
| 6671 | + "prelude-ls": "^1.2.1", | |
| 6672 | + "type-check": "~0.4.0" | |
| 6673 | + }, | |
| 6674 | + "engines": { | |
| 6675 | + "node": ">= 0.8.0" | |
| 6676 | + } | |
| 6677 | + }, | |
| 6678 | + "node_modules/lie": { | |
| 6679 | + "version": "3.3.0", | |
| 6680 | + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", | |
| 6681 | + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", | |
| 6682 | + "license": "MIT", | |
| 6683 | + "dependencies": { | |
| 6684 | + "immediate": "~3.0.5" | |
| 6685 | + } | |
| 6686 | + }, | |
| 6687 | + "node_modules/lilconfig": { | |
| 6688 | + "version": "3.1.3", | |
| 6689 | + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", | |
| 6690 | + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", | |
| 6691 | + "dev": true, | |
| 6692 | + "license": "MIT", | |
| 6693 | + "engines": { | |
| 6694 | + "node": ">=14" | |
| 6695 | + }, | |
| 6696 | + "funding": { | |
| 6697 | + "url": "https://github.com/sponsors/antonk52" | |
| 6698 | + } | |
| 6699 | + }, | |
| 6700 | + "node_modules/lines-and-columns": { | |
| 6701 | + "version": "1.2.4", | |
| 6702 | + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", | |
| 6703 | + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", | |
| 6704 | + "dev": true, | |
| 6705 | + "license": "MIT" | |
| 6706 | + }, | |
| 6707 | + "node_modules/locate-path": { | |
| 6708 | + "version": "6.0.0", | |
| 6709 | + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", | |
| 6710 | + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", | |
| 6711 | + "dev": true, | |
| 6712 | + "license": "MIT", | |
| 6713 | + "dependencies": { | |
| 6714 | + "p-locate": "^5.0.0" | |
| 6715 | + }, | |
| 6716 | + "engines": { | |
| 6717 | + "node": ">=10" | |
| 6718 | + }, | |
| 6719 | + "funding": { | |
| 6720 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 6721 | + } | |
| 6722 | + }, | |
| 6723 | + "node_modules/lodash": { | |
| 6724 | + "version": "4.17.23", | |
| 6725 | + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", | |
| 6726 | + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", | |
| 6727 | + "license": "MIT" | |
| 6728 | + }, | |
| 6729 | + "node_modules/lodash.merge": { | |
| 6730 | + "version": "4.6.2", | |
| 6731 | + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", | |
| 6732 | + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", | |
| 6733 | + "dev": true, | |
| 6734 | + "license": "MIT" | |
| 6735 | + }, | |
| 6736 | + "node_modules/longest-streak": { | |
| 6737 | + "version": "3.1.0", | |
| 6738 | + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", | |
| 6739 | + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", | |
| 6740 | + "license": "MIT", | |
| 6741 | + "funding": { | |
| 6742 | + "type": "github", | |
| 6743 | + "url": "https://github.com/sponsors/wooorm" | |
| 6744 | + } | |
| 6745 | + }, | |
| 6746 | + "node_modules/loose-envify": { | |
| 6747 | + "version": "1.4.0", | |
| 6748 | + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", | |
| 6749 | + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", | |
| 6750 | + "license": "MIT", | |
| 6751 | + "dependencies": { | |
| 6752 | + "js-tokens": "^3.0.0 || ^4.0.0" | |
| 6753 | + }, | |
| 6754 | + "bin": { | |
| 6755 | + "loose-envify": "cli.js" | |
| 6756 | + } | |
| 6757 | + }, | |
| 6758 | + "node_modules/lowlight": { | |
| 6759 | + "version": "1.20.0", | |
| 6760 | + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", | |
| 6761 | + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", | |
| 6762 | + "license": "MIT", | |
| 6763 | + "dependencies": { | |
| 6764 | + "fault": "^1.0.0", | |
| 6765 | + "highlight.js": "~10.7.0" | |
| 6766 | + }, | |
| 6767 | + "funding": { | |
| 6768 | + "type": "github", | |
| 6769 | + "url": "https://github.com/sponsors/wooorm" | |
| 6770 | + } | |
| 6771 | + }, | |
| 6772 | + "node_modules/lru-cache": { | |
| 6773 | + "version": "10.4.3", | |
| 6774 | + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", | |
| 6775 | + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", | |
| 6776 | + "license": "ISC" | |
| 6777 | + }, | |
| 6778 | + "node_modules/lucide-react": { | |
| 6779 | + "version": "0.564.0", | |
| 6780 | + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.564.0.tgz", | |
| 6781 | + "integrity": "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg==", | |
| 6782 | + "license": "ISC", | |
| 6783 | + "peerDependencies": { | |
| 6784 | + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" | |
| 6785 | + } | |
| 6786 | + }, | |
| 6787 | + "node_modules/math-intrinsics": { | |
| 6788 | + "version": "1.1.0", | |
| 6789 | + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", | |
| 6790 | + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", | |
| 6791 | + "dev": true, | |
| 6792 | + "license": "MIT", | |
| 6793 | + "engines": { | |
| 6794 | + "node": ">= 0.4" | |
| 6795 | + } | |
| 6796 | + }, | |
| 6797 | + "node_modules/mdast-util-from-markdown": { | |
| 6798 | + "version": "2.0.2", | |
| 6799 | + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", | |
| 6800 | + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", | |
| 6801 | + "license": "MIT", | |
| 6802 | + "dependencies": { | |
| 6803 | + "@types/mdast": "^4.0.0", | |
| 6804 | + "@types/unist": "^3.0.0", | |
| 6805 | + "decode-named-character-reference": "^1.0.0", | |
| 6806 | + "devlop": "^1.0.0", | |
| 6807 | + "mdast-util-to-string": "^4.0.0", | |
| 6808 | + "micromark": "^4.0.0", | |
| 6809 | + "micromark-util-decode-numeric-character-reference": "^2.0.0", | |
| 6810 | + "micromark-util-decode-string": "^2.0.0", | |
| 6811 | + "micromark-util-normalize-identifier": "^2.0.0", | |
| 6812 | + "micromark-util-symbol": "^2.0.0", | |
| 6813 | + "micromark-util-types": "^2.0.0", | |
| 6814 | + "unist-util-stringify-position": "^4.0.0" | |
| 6815 | + }, | |
| 6816 | + "funding": { | |
| 6817 | + "type": "opencollective", | |
| 6818 | + "url": "https://opencollective.com/unified" | |
| 6819 | + } | |
| 6820 | + }, | |
| 6821 | + "node_modules/mdast-util-mdx-expression": { | |
| 6822 | + "version": "2.0.1", | |
| 6823 | + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", | |
| 6824 | + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", | |
| 6825 | + "license": "MIT", | |
| 6826 | + "dependencies": { | |
| 6827 | + "@types/estree-jsx": "^1.0.0", | |
| 6828 | + "@types/hast": "^3.0.0", | |
| 6829 | + "@types/mdast": "^4.0.0", | |
| 6830 | + "devlop": "^1.0.0", | |
| 6831 | + "mdast-util-from-markdown": "^2.0.0", | |
| 6832 | + "mdast-util-to-markdown": "^2.0.0" | |
| 6833 | + }, | |
| 6834 | + "funding": { | |
| 6835 | + "type": "opencollective", | |
| 6836 | + "url": "https://opencollective.com/unified" | |
| 6837 | + } | |
| 6838 | + }, | |
| 6839 | + "node_modules/mdast-util-mdx-jsx": { | |
| 6840 | + "version": "3.2.0", | |
| 6841 | + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", | |
| 6842 | + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", | |
| 6843 | + "license": "MIT", | |
| 6844 | + "dependencies": { | |
| 6845 | + "@types/estree-jsx": "^1.0.0", | |
| 6846 | + "@types/hast": "^3.0.0", | |
| 6847 | + "@types/mdast": "^4.0.0", | |
| 6848 | + "@types/unist": "^3.0.0", | |
| 6849 | + "ccount": "^2.0.0", | |
| 6850 | + "devlop": "^1.1.0", | |
| 6851 | + "mdast-util-from-markdown": "^2.0.0", | |
| 6852 | + "mdast-util-to-markdown": "^2.0.0", | |
| 6853 | + "parse-entities": "^4.0.0", | |
| 6854 | + "stringify-entities": "^4.0.0", | |
| 6855 | + "unist-util-stringify-position": "^4.0.0", | |
| 6856 | + "vfile-message": "^4.0.0" | |
| 6857 | + }, | |
| 6858 | + "funding": { | |
| 6859 | + "type": "opencollective", | |
| 6860 | + "url": "https://opencollective.com/unified" | |
| 6861 | + } | |
| 6862 | + }, | |
| 6863 | + "node_modules/mdast-util-mdxjs-esm": { | |
| 6864 | + "version": "2.0.1", | |
| 6865 | + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", | |
| 6866 | + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", | |
| 6867 | + "license": "MIT", | |
| 6868 | + "dependencies": { | |
| 6869 | + "@types/estree-jsx": "^1.0.0", | |
| 6870 | + "@types/hast": "^3.0.0", | |
| 6871 | + "@types/mdast": "^4.0.0", | |
| 6872 | + "devlop": "^1.0.0", | |
| 6873 | + "mdast-util-from-markdown": "^2.0.0", | |
| 6874 | + "mdast-util-to-markdown": "^2.0.0" | |
| 6875 | + }, | |
| 6876 | + "funding": { | |
| 6877 | + "type": "opencollective", | |
| 6878 | + "url": "https://opencollective.com/unified" | |
| 6879 | + } | |
| 6880 | + }, | |
| 6881 | + "node_modules/mdast-util-phrasing": { | |
| 6882 | + "version": "4.1.0", | |
| 6883 | + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", | |
| 6884 | + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", | |
| 6885 | + "license": "MIT", | |
| 6886 | + "dependencies": { | |
| 6887 | + "@types/mdast": "^4.0.0", | |
| 6888 | + "unist-util-is": "^6.0.0" | |
| 6889 | + }, | |
| 6890 | + "funding": { | |
| 6891 | + "type": "opencollective", | |
| 6892 | + "url": "https://opencollective.com/unified" | |
| 6893 | + } | |
| 6894 | + }, | |
| 6895 | + "node_modules/mdast-util-to-hast": { | |
| 6896 | + "version": "13.2.1", | |
| 6897 | + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", | |
| 6898 | + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", | |
| 6899 | + "license": "MIT", | |
| 6900 | + "dependencies": { | |
| 6901 | + "@types/hast": "^3.0.0", | |
| 6902 | + "@types/mdast": "^4.0.0", | |
| 6903 | + "@ungap/structured-clone": "^1.0.0", | |
| 6904 | + "devlop": "^1.0.0", | |
| 6905 | + "micromark-util-sanitize-uri": "^2.0.0", | |
| 6906 | + "trim-lines": "^3.0.0", | |
| 6907 | + "unist-util-position": "^5.0.0", | |
| 6908 | + "unist-util-visit": "^5.0.0", | |
| 6909 | + "vfile": "^6.0.0" | |
| 6910 | + }, | |
| 6911 | + "funding": { | |
| 6912 | + "type": "opencollective", | |
| 6913 | + "url": "https://opencollective.com/unified" | |
| 6914 | + } | |
| 6915 | + }, | |
| 6916 | + "node_modules/mdast-util-to-markdown": { | |
| 6917 | + "version": "2.1.2", | |
| 6918 | + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", | |
| 6919 | + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", | |
| 6920 | + "license": "MIT", | |
| 6921 | + "dependencies": { | |
| 6922 | + "@types/mdast": "^4.0.0", | |
| 6923 | + "@types/unist": "^3.0.0", | |
| 6924 | + "longest-streak": "^3.0.0", | |
| 6925 | + "mdast-util-phrasing": "^4.0.0", | |
| 6926 | + "mdast-util-to-string": "^4.0.0", | |
| 6927 | + "micromark-util-classify-character": "^2.0.0", | |
| 6928 | + "micromark-util-decode-string": "^2.0.0", | |
| 6929 | + "unist-util-visit": "^5.0.0", | |
| 6930 | + "zwitch": "^2.0.0" | |
| 6931 | + }, | |
| 6932 | + "funding": { | |
| 6933 | + "type": "opencollective", | |
| 6934 | + "url": "https://opencollective.com/unified" | |
| 6935 | + } | |
| 6936 | + }, | |
| 6937 | + "node_modules/mdast-util-to-string": { | |
| 6938 | + "version": "4.0.0", | |
| 6939 | + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", | |
| 6940 | + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", | |
| 6941 | + "license": "MIT", | |
| 6942 | + "dependencies": { | |
| 6943 | + "@types/mdast": "^4.0.0" | |
| 6944 | + }, | |
| 6945 | + "funding": { | |
| 6946 | + "type": "opencollective", | |
| 6947 | + "url": "https://opencollective.com/unified" | |
| 6948 | + } | |
| 6949 | + }, | |
| 6950 | + "node_modules/merge2": { | |
| 6951 | + "version": "1.4.1", | |
| 6952 | + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", | |
| 6953 | + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", | |
| 6954 | + "dev": true, | |
| 6955 | + "license": "MIT", | |
| 6956 | + "engines": { | |
| 6957 | + "node": ">= 8" | |
| 6958 | + } | |
| 6959 | + }, | |
| 6960 | + "node_modules/micromark": { | |
| 6961 | + "version": "4.0.2", | |
| 6962 | + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", | |
| 6963 | + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", | |
| 6964 | + "funding": [ | |
| 6965 | + { | |
| 6966 | + "type": "GitHub Sponsors", | |
| 6967 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 6968 | + }, | |
| 6969 | + { | |
| 6970 | + "type": "OpenCollective", | |
| 6971 | + "url": "https://opencollective.com/unified" | |
| 6972 | + } | |
| 6973 | + ], | |
| 6974 | + "license": "MIT", | |
| 6975 | + "dependencies": { | |
| 6976 | + "@types/debug": "^4.0.0", | |
| 6977 | + "debug": "^4.0.0", | |
| 6978 | + "decode-named-character-reference": "^1.0.0", | |
| 6979 | + "devlop": "^1.0.0", | |
| 6980 | + "micromark-core-commonmark": "^2.0.0", | |
| 6981 | + "micromark-factory-space": "^2.0.0", | |
| 6982 | + "micromark-util-character": "^2.0.0", | |
| 6983 | + "micromark-util-chunked": "^2.0.0", | |
| 6984 | + "micromark-util-combine-extensions": "^2.0.0", | |
| 6985 | + "micromark-util-decode-numeric-character-reference": "^2.0.0", | |
| 6986 | + "micromark-util-encode": "^2.0.0", | |
| 6987 | + "micromark-util-normalize-identifier": "^2.0.0", | |
| 6988 | + "micromark-util-resolve-all": "^2.0.0", | |
| 6989 | + "micromark-util-sanitize-uri": "^2.0.0", | |
| 6990 | + "micromark-util-subtokenize": "^2.0.0", | |
| 6991 | + "micromark-util-symbol": "^2.0.0", | |
| 6992 | + "micromark-util-types": "^2.0.0" | |
| 6993 | + } | |
| 6994 | + }, | |
| 6995 | + "node_modules/micromark-core-commonmark": { | |
| 6996 | + "version": "2.0.3", | |
| 6997 | + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", | |
| 6998 | + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", | |
| 6999 | + "funding": [ | |
| 7000 | + { | |
| 7001 | + "type": "GitHub Sponsors", | |
| 7002 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7003 | + }, | |
| 7004 | + { | |
| 7005 | + "type": "OpenCollective", | |
| 7006 | + "url": "https://opencollective.com/unified" | |
| 7007 | + } | |
| 7008 | + ], | |
| 7009 | + "license": "MIT", | |
| 7010 | + "dependencies": { | |
| 7011 | + "decode-named-character-reference": "^1.0.0", | |
| 7012 | + "devlop": "^1.0.0", | |
| 7013 | + "micromark-factory-destination": "^2.0.0", | |
| 7014 | + "micromark-factory-label": "^2.0.0", | |
| 7015 | + "micromark-factory-space": "^2.0.0", | |
| 7016 | + "micromark-factory-title": "^2.0.0", | |
| 7017 | + "micromark-factory-whitespace": "^2.0.0", | |
| 7018 | + "micromark-util-character": "^2.0.0", | |
| 7019 | + "micromark-util-chunked": "^2.0.0", | |
| 7020 | + "micromark-util-classify-character": "^2.0.0", | |
| 7021 | + "micromark-util-html-tag-name": "^2.0.0", | |
| 7022 | + "micromark-util-normalize-identifier": "^2.0.0", | |
| 7023 | + "micromark-util-resolve-all": "^2.0.0", | |
| 7024 | + "micromark-util-subtokenize": "^2.0.0", | |
| 7025 | + "micromark-util-symbol": "^2.0.0", | |
| 7026 | + "micromark-util-types": "^2.0.0" | |
| 7027 | + } | |
| 7028 | + }, | |
| 7029 | + "node_modules/micromark-factory-destination": { | |
| 7030 | + "version": "2.0.1", | |
| 7031 | + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", | |
| 7032 | + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", | |
| 7033 | + "funding": [ | |
| 7034 | + { | |
| 7035 | + "type": "GitHub Sponsors", | |
| 7036 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7037 | + }, | |
| 7038 | + { | |
| 7039 | + "type": "OpenCollective", | |
| 7040 | + "url": "https://opencollective.com/unified" | |
| 7041 | + } | |
| 7042 | + ], | |
| 7043 | + "license": "MIT", | |
| 7044 | + "dependencies": { | |
| 7045 | + "micromark-util-character": "^2.0.0", | |
| 7046 | + "micromark-util-symbol": "^2.0.0", | |
| 7047 | + "micromark-util-types": "^2.0.0" | |
| 7048 | + } | |
| 7049 | + }, | |
| 7050 | + "node_modules/micromark-factory-label": { | |
| 7051 | + "version": "2.0.1", | |
| 7052 | + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", | |
| 7053 | + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", | |
| 7054 | + "funding": [ | |
| 7055 | + { | |
| 7056 | + "type": "GitHub Sponsors", | |
| 7057 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7058 | + }, | |
| 7059 | + { | |
| 7060 | + "type": "OpenCollective", | |
| 7061 | + "url": "https://opencollective.com/unified" | |
| 7062 | + } | |
| 7063 | + ], | |
| 7064 | + "license": "MIT", | |
| 7065 | + "dependencies": { | |
| 7066 | + "devlop": "^1.0.0", | |
| 7067 | + "micromark-util-character": "^2.0.0", | |
| 7068 | + "micromark-util-symbol": "^2.0.0", | |
| 7069 | + "micromark-util-types": "^2.0.0" | |
| 7070 | + } | |
| 7071 | + }, | |
| 7072 | + "node_modules/micromark-factory-space": { | |
| 7073 | + "version": "2.0.1", | |
| 7074 | + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", | |
| 7075 | + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", | |
| 7076 | + "funding": [ | |
| 7077 | + { | |
| 7078 | + "type": "GitHub Sponsors", | |
| 7079 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7080 | + }, | |
| 7081 | + { | |
| 7082 | + "type": "OpenCollective", | |
| 7083 | + "url": "https://opencollective.com/unified" | |
| 7084 | + } | |
| 7085 | + ], | |
| 7086 | + "license": "MIT", | |
| 7087 | + "dependencies": { | |
| 7088 | + "micromark-util-character": "^2.0.0", | |
| 7089 | + "micromark-util-types": "^2.0.0" | |
| 7090 | + } | |
| 7091 | + }, | |
| 7092 | + "node_modules/micromark-factory-title": { | |
| 7093 | + "version": "2.0.1", | |
| 7094 | + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", | |
| 7095 | + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", | |
| 7096 | + "funding": [ | |
| 7097 | + { | |
| 7098 | + "type": "GitHub Sponsors", | |
| 7099 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7100 | + }, | |
| 7101 | + { | |
| 7102 | + "type": "OpenCollective", | |
| 7103 | + "url": "https://opencollective.com/unified" | |
| 7104 | + } | |
| 7105 | + ], | |
| 7106 | + "license": "MIT", | |
| 7107 | + "dependencies": { | |
| 7108 | + "micromark-factory-space": "^2.0.0", | |
| 7109 | + "micromark-util-character": "^2.0.0", | |
| 7110 | + "micromark-util-symbol": "^2.0.0", | |
| 7111 | + "micromark-util-types": "^2.0.0" | |
| 7112 | + } | |
| 7113 | + }, | |
| 7114 | + "node_modules/micromark-factory-whitespace": { | |
| 7115 | + "version": "2.0.1", | |
| 7116 | + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", | |
| 7117 | + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", | |
| 7118 | + "funding": [ | |
| 7119 | + { | |
| 7120 | + "type": "GitHub Sponsors", | |
| 7121 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7122 | + }, | |
| 7123 | + { | |
| 7124 | + "type": "OpenCollective", | |
| 7125 | + "url": "https://opencollective.com/unified" | |
| 7126 | + } | |
| 7127 | + ], | |
| 7128 | + "license": "MIT", | |
| 7129 | + "dependencies": { | |
| 7130 | + "micromark-factory-space": "^2.0.0", | |
| 7131 | + "micromark-util-character": "^2.0.0", | |
| 7132 | + "micromark-util-symbol": "^2.0.0", | |
| 7133 | + "micromark-util-types": "^2.0.0" | |
| 7134 | + } | |
| 7135 | + }, | |
| 7136 | + "node_modules/micromark-util-character": { | |
| 7137 | + "version": "2.1.1", | |
| 7138 | + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", | |
| 7139 | + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", | |
| 7140 | + "funding": [ | |
| 7141 | + { | |
| 7142 | + "type": "GitHub Sponsors", | |
| 7143 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7144 | + }, | |
| 7145 | + { | |
| 7146 | + "type": "OpenCollective", | |
| 7147 | + "url": "https://opencollective.com/unified" | |
| 7148 | + } | |
| 7149 | + ], | |
| 7150 | + "license": "MIT", | |
| 7151 | + "dependencies": { | |
| 7152 | + "micromark-util-symbol": "^2.0.0", | |
| 7153 | + "micromark-util-types": "^2.0.0" | |
| 7154 | + } | |
| 7155 | + }, | |
| 7156 | + "node_modules/micromark-util-chunked": { | |
| 7157 | + "version": "2.0.1", | |
| 7158 | + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", | |
| 7159 | + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", | |
| 7160 | + "funding": [ | |
| 7161 | + { | |
| 7162 | + "type": "GitHub Sponsors", | |
| 7163 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7164 | + }, | |
| 7165 | + { | |
| 7166 | + "type": "OpenCollective", | |
| 7167 | + "url": "https://opencollective.com/unified" | |
| 7168 | + } | |
| 7169 | + ], | |
| 7170 | + "license": "MIT", | |
| 7171 | + "dependencies": { | |
| 7172 | + "micromark-util-symbol": "^2.0.0" | |
| 7173 | + } | |
| 7174 | + }, | |
| 7175 | + "node_modules/micromark-util-classify-character": { | |
| 7176 | + "version": "2.0.1", | |
| 7177 | + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", | |
| 7178 | + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", | |
| 7179 | + "funding": [ | |
| 7180 | + { | |
| 7181 | + "type": "GitHub Sponsors", | |
| 7182 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7183 | + }, | |
| 7184 | + { | |
| 7185 | + "type": "OpenCollective", | |
| 7186 | + "url": "https://opencollective.com/unified" | |
| 7187 | + } | |
| 7188 | + ], | |
| 7189 | + "license": "MIT", | |
| 7190 | + "dependencies": { | |
| 7191 | + "micromark-util-character": "^2.0.0", | |
| 7192 | + "micromark-util-symbol": "^2.0.0", | |
| 7193 | + "micromark-util-types": "^2.0.0" | |
| 7194 | + } | |
| 7195 | + }, | |
| 7196 | + "node_modules/micromark-util-combine-extensions": { | |
| 7197 | + "version": "2.0.1", | |
| 7198 | + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", | |
| 7199 | + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", | |
| 7200 | + "funding": [ | |
| 7201 | + { | |
| 7202 | + "type": "GitHub Sponsors", | |
| 7203 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7204 | + }, | |
| 7205 | + { | |
| 7206 | + "type": "OpenCollective", | |
| 7207 | + "url": "https://opencollective.com/unified" | |
| 7208 | + } | |
| 7209 | + ], | |
| 7210 | + "license": "MIT", | |
| 7211 | + "dependencies": { | |
| 7212 | + "micromark-util-chunked": "^2.0.0", | |
| 7213 | + "micromark-util-types": "^2.0.0" | |
| 7214 | + } | |
| 7215 | + }, | |
| 7216 | + "node_modules/micromark-util-decode-numeric-character-reference": { | |
| 7217 | + "version": "2.0.2", | |
| 7218 | + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", | |
| 7219 | + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", | |
| 7220 | + "funding": [ | |
| 7221 | + { | |
| 7222 | + "type": "GitHub Sponsors", | |
| 7223 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7224 | + }, | |
| 7225 | + { | |
| 7226 | + "type": "OpenCollective", | |
| 7227 | + "url": "https://opencollective.com/unified" | |
| 7228 | + } | |
| 7229 | + ], | |
| 7230 | + "license": "MIT", | |
| 7231 | + "dependencies": { | |
| 7232 | + "micromark-util-symbol": "^2.0.0" | |
| 7233 | + } | |
| 7234 | + }, | |
| 7235 | + "node_modules/micromark-util-decode-string": { | |
| 7236 | + "version": "2.0.1", | |
| 7237 | + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", | |
| 7238 | + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", | |
| 7239 | + "funding": [ | |
| 7240 | + { | |
| 7241 | + "type": "GitHub Sponsors", | |
| 7242 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7243 | + }, | |
| 7244 | + { | |
| 7245 | + "type": "OpenCollective", | |
| 7246 | + "url": "https://opencollective.com/unified" | |
| 7247 | + } | |
| 7248 | + ], | |
| 7249 | + "license": "MIT", | |
| 7250 | + "dependencies": { | |
| 7251 | + "decode-named-character-reference": "^1.0.0", | |
| 7252 | + "micromark-util-character": "^2.0.0", | |
| 7253 | + "micromark-util-decode-numeric-character-reference": "^2.0.0", | |
| 7254 | + "micromark-util-symbol": "^2.0.0" | |
| 7255 | + } | |
| 7256 | + }, | |
| 7257 | + "node_modules/micromark-util-encode": { | |
| 7258 | + "version": "2.0.1", | |
| 7259 | + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", | |
| 7260 | + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", | |
| 7261 | + "funding": [ | |
| 7262 | + { | |
| 7263 | + "type": "GitHub Sponsors", | |
| 7264 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7265 | + }, | |
| 7266 | + { | |
| 7267 | + "type": "OpenCollective", | |
| 7268 | + "url": "https://opencollective.com/unified" | |
| 7269 | + } | |
| 7270 | + ], | |
| 7271 | + "license": "MIT" | |
| 7272 | + }, | |
| 7273 | + "node_modules/micromark-util-html-tag-name": { | |
| 7274 | + "version": "2.0.1", | |
| 7275 | + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", | |
| 7276 | + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", | |
| 7277 | + "funding": [ | |
| 7278 | + { | |
| 7279 | + "type": "GitHub Sponsors", | |
| 7280 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7281 | + }, | |
| 7282 | + { | |
| 7283 | + "type": "OpenCollective", | |
| 7284 | + "url": "https://opencollective.com/unified" | |
| 7285 | + } | |
| 7286 | + ], | |
| 7287 | + "license": "MIT" | |
| 7288 | + }, | |
| 7289 | + "node_modules/micromark-util-normalize-identifier": { | |
| 7290 | + "version": "2.0.1", | |
| 7291 | + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", | |
| 7292 | + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", | |
| 7293 | + "funding": [ | |
| 7294 | + { | |
| 7295 | + "type": "GitHub Sponsors", | |
| 7296 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7297 | + }, | |
| 7298 | + { | |
| 7299 | + "type": "OpenCollective", | |
| 7300 | + "url": "https://opencollective.com/unified" | |
| 7301 | + } | |
| 7302 | + ], | |
| 7303 | + "license": "MIT", | |
| 7304 | + "dependencies": { | |
| 7305 | + "micromark-util-symbol": "^2.0.0" | |
| 7306 | + } | |
| 7307 | + }, | |
| 7308 | + "node_modules/micromark-util-resolve-all": { | |
| 7309 | + "version": "2.0.1", | |
| 7310 | + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", | |
| 7311 | + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", | |
| 7312 | + "funding": [ | |
| 7313 | + { | |
| 7314 | + "type": "GitHub Sponsors", | |
| 7315 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7316 | + }, | |
| 7317 | + { | |
| 7318 | + "type": "OpenCollective", | |
| 7319 | + "url": "https://opencollective.com/unified" | |
| 7320 | + } | |
| 7321 | + ], | |
| 7322 | + "license": "MIT", | |
| 7323 | + "dependencies": { | |
| 7324 | + "micromark-util-types": "^2.0.0" | |
| 7325 | + } | |
| 7326 | + }, | |
| 7327 | + "node_modules/micromark-util-sanitize-uri": { | |
| 7328 | + "version": "2.0.1", | |
| 7329 | + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", | |
| 7330 | + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", | |
| 7331 | + "funding": [ | |
| 7332 | + { | |
| 7333 | + "type": "GitHub Sponsors", | |
| 7334 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7335 | + }, | |
| 7336 | + { | |
| 7337 | + "type": "OpenCollective", | |
| 7338 | + "url": "https://opencollective.com/unified" | |
| 7339 | + } | |
| 7340 | + ], | |
| 7341 | + "license": "MIT", | |
| 7342 | + "dependencies": { | |
| 7343 | + "micromark-util-character": "^2.0.0", | |
| 7344 | + "micromark-util-encode": "^2.0.0", | |
| 7345 | + "micromark-util-symbol": "^2.0.0" | |
| 7346 | + } | |
| 7347 | + }, | |
| 7348 | + "node_modules/micromark-util-subtokenize": { | |
| 7349 | + "version": "2.1.0", | |
| 7350 | + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", | |
| 7351 | + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", | |
| 7352 | + "funding": [ | |
| 7353 | + { | |
| 7354 | + "type": "GitHub Sponsors", | |
| 7355 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7356 | + }, | |
| 7357 | + { | |
| 7358 | + "type": "OpenCollective", | |
| 7359 | + "url": "https://opencollective.com/unified" | |
| 7360 | + } | |
| 7361 | + ], | |
| 7362 | + "license": "MIT", | |
| 7363 | + "dependencies": { | |
| 7364 | + "devlop": "^1.0.0", | |
| 7365 | + "micromark-util-chunked": "^2.0.0", | |
| 7366 | + "micromark-util-symbol": "^2.0.0", | |
| 7367 | + "micromark-util-types": "^2.0.0" | |
| 7368 | + } | |
| 7369 | + }, | |
| 7370 | + "node_modules/micromark-util-symbol": { | |
| 7371 | + "version": "2.0.1", | |
| 7372 | + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", | |
| 7373 | + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", | |
| 7374 | + "funding": [ | |
| 7375 | + { | |
| 7376 | + "type": "GitHub Sponsors", | |
| 7377 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7378 | + }, | |
| 7379 | + { | |
| 7380 | + "type": "OpenCollective", | |
| 7381 | + "url": "https://opencollective.com/unified" | |
| 7382 | + } | |
| 7383 | + ], | |
| 7384 | + "license": "MIT" | |
| 7385 | + }, | |
| 7386 | + "node_modules/micromark-util-types": { | |
| 7387 | + "version": "2.0.2", | |
| 7388 | + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", | |
| 7389 | + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", | |
| 7390 | + "funding": [ | |
| 7391 | + { | |
| 7392 | + "type": "GitHub Sponsors", | |
| 7393 | + "url": "https://github.com/sponsors/unifiedjs" | |
| 7394 | + }, | |
| 7395 | + { | |
| 7396 | + "type": "OpenCollective", | |
| 7397 | + "url": "https://opencollective.com/unified" | |
| 7398 | + } | |
| 7399 | + ], | |
| 7400 | + "license": "MIT" | |
| 7401 | + }, | |
| 7402 | + "node_modules/micromatch": { | |
| 7403 | + "version": "4.0.8", | |
| 7404 | + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", | |
| 7405 | + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", | |
| 7406 | + "dev": true, | |
| 7407 | + "license": "MIT", | |
| 7408 | + "dependencies": { | |
| 7409 | + "braces": "^3.0.3", | |
| 7410 | + "picomatch": "^2.3.1" | |
| 7411 | + }, | |
| 7412 | + "engines": { | |
| 7413 | + "node": ">=8.6" | |
| 7414 | + } | |
| 7415 | + }, | |
| 7416 | + "node_modules/mime-db": { | |
| 7417 | + "version": "1.54.0", | |
| 7418 | + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", | |
| 7419 | + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", | |
| 7420 | + "license": "MIT", | |
| 7421 | + "engines": { | |
| 7422 | + "node": ">= 0.6" | |
| 7423 | + } | |
| 7424 | + }, | |
| 7425 | + "node_modules/mime-types": { | |
| 7426 | + "version": "3.0.2", | |
| 7427 | + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", | |
| 7428 | + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", | |
| 7429 | + "license": "MIT", | |
| 7430 | + "dependencies": { | |
| 7431 | + "mime-db": "^1.54.0" | |
| 7432 | + }, | |
| 7433 | + "engines": { | |
| 7434 | + "node": ">=18" | |
| 7435 | + }, | |
| 7436 | + "funding": { | |
| 7437 | + "type": "opencollective", | |
| 7438 | + "url": "https://opencollective.com/express" | |
| 7439 | + } | |
| 7440 | + }, | |
| 7441 | + "node_modules/minimatch": { | |
| 7442 | + "version": "3.1.2", | |
| 7443 | + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", | |
| 7444 | + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", | |
| 7445 | + "dev": true, | |
| 7446 | + "license": "ISC", | |
| 7447 | + "dependencies": { | |
| 7448 | + "brace-expansion": "^1.1.7" | |
| 7449 | + }, | |
| 7450 | + "engines": { | |
| 7451 | + "node": "*" | |
| 7452 | + } | |
| 7453 | + }, | |
| 7454 | + "node_modules/minimist": { | |
| 7455 | + "version": "1.2.8", | |
| 7456 | + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", | |
| 7457 | + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", | |
| 7458 | + "dev": true, | |
| 7459 | + "license": "MIT", | |
| 7460 | + "funding": { | |
| 7461 | + "url": "https://github.com/sponsors/ljharb" | |
| 7462 | + } | |
| 7463 | + }, | |
| 7464 | + "node_modules/minipass": { | |
| 7465 | + "version": "7.1.2", | |
| 7466 | + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", | |
| 7467 | + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", | |
| 7468 | + "license": "ISC", | |
| 7469 | + "engines": { | |
| 7470 | + "node": ">=16 || 14 >=14.17" | |
| 7471 | + } | |
| 7472 | + }, | |
| 7473 | + "node_modules/motion-dom": { | |
| 7474 | + "version": "12.34.0", | |
| 7475 | + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.0.tgz", | |
| 7476 | + "integrity": "sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==", | |
| 7477 | + "license": "MIT", | |
| 7478 | + "dependencies": { | |
| 7479 | + "motion-utils": "^12.29.2" | |
| 7480 | + } | |
| 7481 | + }, | |
| 7482 | + "node_modules/motion-utils": { | |
| 7483 | + "version": "12.29.2", | |
| 7484 | + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", | |
| 7485 | + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", | |
| 7486 | + "license": "MIT" | |
| 7487 | + }, | |
| 7488 | + "node_modules/ms": { | |
| 7489 | + "version": "2.1.3", | |
| 7490 | + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", | |
| 7491 | + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", | |
| 7492 | + "license": "MIT" | |
| 7493 | + }, | |
| 7494 | + "node_modules/mz": { | |
| 7495 | + "version": "2.7.0", | |
| 7496 | + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", | |
| 7497 | + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", | |
| 7498 | + "dev": true, | |
| 7499 | + "license": "MIT", | |
| 7500 | + "dependencies": { | |
| 7501 | + "any-promise": "^1.0.0", | |
| 7502 | + "object-assign": "^4.0.1", | |
| 7503 | + "thenify-all": "^1.0.0" | |
| 7504 | + } | |
| 7505 | + }, | |
| 7506 | + "node_modules/nanoid": { | |
| 7507 | + "version": "3.3.11", | |
| 7508 | + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", | |
| 7509 | + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", | |
| 7510 | + "funding": [ | |
| 7511 | + { | |
| 7512 | + "type": "github", | |
| 7513 | + "url": "https://github.com/sponsors/ai" | |
| 7514 | + } | |
| 7515 | + ], | |
| 7516 | + "license": "MIT", | |
| 7517 | + "bin": { | |
| 7518 | + "nanoid": "bin/nanoid.cjs" | |
| 7519 | + }, | |
| 7520 | + "engines": { | |
| 7521 | + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" | |
| 7522 | + } | |
| 7523 | + }, | |
| 7524 | + "node_modules/napi-postinstall": { | |
| 7525 | + "version": "0.3.4", | |
| 7526 | + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", | |
| 7527 | + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", | |
| 7528 | + "dev": true, | |
| 7529 | + "license": "MIT", | |
| 7530 | + "bin": { | |
| 7531 | + "napi-postinstall": "lib/cli.js" | |
| 7532 | + }, | |
| 7533 | + "engines": { | |
| 7534 | + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" | |
| 7535 | + }, | |
| 7536 | + "funding": { | |
| 7537 | + "url": "https://opencollective.com/napi-postinstall" | |
| 7538 | + } | |
| 7539 | + }, | |
| 7540 | + "node_modules/natural-compare": { | |
| 7541 | + "version": "1.4.0", | |
| 7542 | + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", | |
| 7543 | + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", | |
| 7544 | + "dev": true, | |
| 7545 | + "license": "MIT" | |
| 7546 | + }, | |
| 7547 | + "node_modules/next": { | |
| 7548 | + "version": "14.2.35", | |
| 7549 | + "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", | |
| 7550 | + "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", | |
| 7551 | + "license": "MIT", | |
| 7552 | + "dependencies": { | |
| 7553 | + "@next/env": "14.2.35", | |
| 7554 | + "@swc/helpers": "0.5.5", | |
| 7555 | + "busboy": "1.6.0", | |
| 7556 | + "caniuse-lite": "^1.0.30001579", | |
| 7557 | + "graceful-fs": "^4.2.11", | |
| 7558 | + "postcss": "8.4.31", | |
| 7559 | + "styled-jsx": "5.1.1" | |
| 7560 | + }, | |
| 7561 | + "bin": { | |
| 7562 | + "next": "dist/bin/next" | |
| 7563 | + }, | |
| 7564 | + "engines": { | |
| 7565 | + "node": ">=18.17.0" | |
| 7566 | + }, | |
| 7567 | + "optionalDependencies": { | |
| 7568 | + "@next/swc-darwin-arm64": "14.2.33", | |
| 7569 | + "@next/swc-darwin-x64": "14.2.33", | |
| 7570 | + "@next/swc-linux-arm64-gnu": "14.2.33", | |
| 7571 | + "@next/swc-linux-arm64-musl": "14.2.33", | |
| 7572 | + "@next/swc-linux-x64-gnu": "14.2.33", | |
| 7573 | + "@next/swc-linux-x64-musl": "14.2.33", | |
| 7574 | + "@next/swc-win32-arm64-msvc": "14.2.33", | |
| 7575 | + "@next/swc-win32-ia32-msvc": "14.2.33", | |
| 7576 | + "@next/swc-win32-x64-msvc": "14.2.33" | |
| 7577 | + }, | |
| 7578 | + "peerDependencies": { | |
| 7579 | + "@opentelemetry/api": "^1.1.0", | |
| 7580 | + "@playwright/test": "^1.41.2", | |
| 7581 | + "react": "^18.2.0", | |
| 7582 | + "react-dom": "^18.2.0", | |
| 7583 | + "sass": "^1.3.0" | |
| 7584 | + }, | |
| 7585 | + "peerDependenciesMeta": { | |
| 7586 | + "@opentelemetry/api": { | |
| 7587 | + "optional": true | |
| 7588 | + }, | |
| 7589 | + "@playwright/test": { | |
| 7590 | + "optional": true | |
| 7591 | + }, | |
| 7592 | + "sass": { | |
| 7593 | + "optional": true | |
| 7594 | + } | |
| 7595 | + } | |
| 7596 | + }, | |
| 7597 | + "node_modules/next/node_modules/postcss": { | |
| 7598 | + "version": "8.4.31", | |
| 7599 | + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", | |
| 7600 | + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", | |
| 7601 | + "funding": [ | |
| 7602 | + { | |
| 7603 | + "type": "opencollective", | |
| 7604 | + "url": "https://opencollective.com/postcss/" | |
| 7605 | + }, | |
| 7606 | + { | |
| 7607 | + "type": "tidelift", | |
| 7608 | + "url": "https://tidelift.com/funding/github/npm/postcss" | |
| 7609 | + }, | |
| 7610 | + { | |
| 7611 | + "type": "github", | |
| 7612 | + "url": "https://github.com/sponsors/ai" | |
| 7613 | + } | |
| 7614 | + ], | |
| 7615 | + "license": "MIT", | |
| 7616 | + "dependencies": { | |
| 7617 | + "nanoid": "^3.3.6", | |
| 7618 | + "picocolors": "^1.0.0", | |
| 7619 | + "source-map-js": "^1.0.2" | |
| 7620 | + }, | |
| 7621 | + "engines": { | |
| 7622 | + "node": "^10 || ^12 || >=14" | |
| 7623 | + } | |
| 7624 | + }, | |
| 7625 | + "node_modules/node-readable-to-web-readable-stream": { | |
| 7626 | + "version": "0.4.2", | |
| 7627 | + "resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz", | |
| 7628 | + "integrity": "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==", | |
| 7629 | + "license": "MIT", | |
| 7630 | + "optional": true | |
| 7631 | + }, | |
| 7632 | + "node_modules/normalize-path": { | |
| 7633 | + "version": "3.0.0", | |
| 7634 | + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | |
| 7635 | + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", | |
| 7636 | + "license": "MIT", | |
| 7637 | + "engines": { | |
| 7638 | + "node": ">=0.10.0" | |
| 7639 | + } | |
| 7640 | + }, | |
| 7641 | + "node_modules/object-assign": { | |
| 7642 | + "version": "4.1.1", | |
| 7643 | + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", | |
| 7644 | + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", | |
| 7645 | + "license": "MIT", | |
| 7646 | + "engines": { | |
| 7647 | + "node": ">=0.10.0" | |
| 7648 | + } | |
| 7649 | + }, | |
| 7650 | + "node_modules/object-hash": { | |
| 7651 | + "version": "3.0.0", | |
| 7652 | + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", | |
| 7653 | + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", | |
| 7654 | + "dev": true, | |
| 7655 | + "license": "MIT", | |
| 7656 | + "engines": { | |
| 7657 | + "node": ">= 6" | |
| 7658 | + } | |
| 7659 | + }, | |
| 7660 | + "node_modules/object-inspect": { | |
| 7661 | + "version": "1.13.4", | |
| 7662 | + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", | |
| 7663 | + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", | |
| 7664 | + "dev": true, | |
| 7665 | + "license": "MIT", | |
| 7666 | + "engines": { | |
| 7667 | + "node": ">= 0.4" | |
| 7668 | + }, | |
| 7669 | + "funding": { | |
| 7670 | + "url": "https://github.com/sponsors/ljharb" | |
| 7671 | + } | |
| 7672 | + }, | |
| 7673 | + "node_modules/object-keys": { | |
| 7674 | + "version": "1.1.1", | |
| 7675 | + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", | |
| 7676 | + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", | |
| 7677 | + "dev": true, | |
| 7678 | + "license": "MIT", | |
| 7679 | + "engines": { | |
| 7680 | + "node": ">= 0.4" | |
| 7681 | + } | |
| 7682 | + }, | |
| 7683 | + "node_modules/object.assign": { | |
| 7684 | + "version": "4.1.7", | |
| 7685 | + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", | |
| 7686 | + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", | |
| 7687 | + "dev": true, | |
| 7688 | + "license": "MIT", | |
| 7689 | + "dependencies": { | |
| 7690 | + "call-bind": "^1.0.8", | |
| 7691 | + "call-bound": "^1.0.3", | |
| 7692 | + "define-properties": "^1.2.1", | |
| 7693 | + "es-object-atoms": "^1.0.0", | |
| 7694 | + "has-symbols": "^1.1.0", | |
| 7695 | + "object-keys": "^1.1.1" | |
| 7696 | + }, | |
| 7697 | + "engines": { | |
| 7698 | + "node": ">= 0.4" | |
| 7699 | + }, | |
| 7700 | + "funding": { | |
| 7701 | + "url": "https://github.com/sponsors/ljharb" | |
| 7702 | + } | |
| 7703 | + }, | |
| 7704 | + "node_modules/object.entries": { | |
| 7705 | + "version": "1.1.9", | |
| 7706 | + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", | |
| 7707 | + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", | |
| 7708 | + "dev": true, | |
| 7709 | + "license": "MIT", | |
| 7710 | + "dependencies": { | |
| 7711 | + "call-bind": "^1.0.8", | |
| 7712 | + "call-bound": "^1.0.4", | |
| 7713 | + "define-properties": "^1.2.1", | |
| 7714 | + "es-object-atoms": "^1.1.1" | |
| 7715 | + }, | |
| 7716 | + "engines": { | |
| 7717 | + "node": ">= 0.4" | |
| 7718 | + } | |
| 7719 | + }, | |
| 7720 | + "node_modules/object.fromentries": { | |
| 7721 | + "version": "2.0.8", | |
| 7722 | + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", | |
| 7723 | + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", | |
| 7724 | + "dev": true, | |
| 7725 | + "license": "MIT", | |
| 7726 | + "dependencies": { | |
| 7727 | + "call-bind": "^1.0.7", | |
| 7728 | + "define-properties": "^1.2.1", | |
| 7729 | + "es-abstract": "^1.23.2", | |
| 7730 | + "es-object-atoms": "^1.0.0" | |
| 7731 | + }, | |
| 7732 | + "engines": { | |
| 7733 | + "node": ">= 0.4" | |
| 7734 | + }, | |
| 7735 | + "funding": { | |
| 7736 | + "url": "https://github.com/sponsors/ljharb" | |
| 7737 | + } | |
| 7738 | + }, | |
| 7739 | + "node_modules/object.groupby": { | |
| 7740 | + "version": "1.0.3", | |
| 7741 | + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", | |
| 7742 | + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", | |
| 7743 | + "dev": true, | |
| 7744 | + "license": "MIT", | |
| 7745 | + "dependencies": { | |
| 7746 | + "call-bind": "^1.0.7", | |
| 7747 | + "define-properties": "^1.2.1", | |
| 7748 | + "es-abstract": "^1.23.2" | |
| 7749 | + }, | |
| 7750 | + "engines": { | |
| 7751 | + "node": ">= 0.4" | |
| 7752 | + } | |
| 7753 | + }, | |
| 7754 | + "node_modules/object.values": { | |
| 7755 | + "version": "1.2.1", | |
| 7756 | + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", | |
| 7757 | + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", | |
| 7758 | + "dev": true, | |
| 7759 | + "license": "MIT", | |
| 7760 | + "dependencies": { | |
| 7761 | + "call-bind": "^1.0.8", | |
| 7762 | + "call-bound": "^1.0.3", | |
| 7763 | + "define-properties": "^1.2.1", | |
| 7764 | + "es-object-atoms": "^1.0.0" | |
| 7765 | + }, | |
| 7766 | + "engines": { | |
| 7767 | + "node": ">= 0.4" | |
| 7768 | + }, | |
| 7769 | + "funding": { | |
| 7770 | + "url": "https://github.com/sponsors/ljharb" | |
| 7771 | + } | |
| 7772 | + }, | |
| 7773 | + "node_modules/once": { | |
| 7774 | + "version": "1.4.0", | |
| 7775 | + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | |
| 7776 | + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", | |
| 7777 | + "dev": true, | |
| 7778 | + "license": "ISC", | |
| 7779 | + "dependencies": { | |
| 7780 | + "wrappy": "1" | |
| 7781 | + } | |
| 7782 | + }, | |
| 7783 | + "node_modules/optionator": { | |
| 7784 | + "version": "0.9.4", | |
| 7785 | + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", | |
| 7786 | + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", | |
| 7787 | + "dev": true, | |
| 7788 | + "license": "MIT", | |
| 7789 | + "dependencies": { | |
| 7790 | + "deep-is": "^0.1.3", | |
| 7791 | + "fast-levenshtein": "^2.0.6", | |
| 7792 | + "levn": "^0.4.1", | |
| 7793 | + "prelude-ls": "^1.2.1", | |
| 7794 | + "type-check": "^0.4.0", | |
| 7795 | + "word-wrap": "^1.2.5" | |
| 7796 | + }, | |
| 7797 | + "engines": { | |
| 7798 | + "node": ">= 0.8.0" | |
| 7799 | + } | |
| 7800 | + }, | |
| 7801 | + "node_modules/otplib": { | |
| 7802 | + "version": "13.3.0", | |
| 7803 | + "resolved": "https://registry.npmjs.org/otplib/-/otplib-13.3.0.tgz", | |
| 7804 | + "integrity": "sha512-VYMKyyDG8yt2q+z58sz54/EIyTh7+tyMrjeemR44iVh5+dkKtIs57irTqxjH+IkAL1uMmG1JIFhG5CxTpqdU5g==", | |
| 7805 | + "license": "MIT", | |
| 7806 | + "dependencies": { | |
| 7807 | + "@otplib/core": "13.3.0", | |
| 7808 | + "@otplib/hotp": "13.3.0", | |
| 7809 | + "@otplib/plugin-base32-scure": "13.3.0", | |
| 7810 | + "@otplib/plugin-crypto-noble": "13.3.0", | |
| 7811 | + "@otplib/totp": "13.3.0", | |
| 7812 | + "@otplib/uri": "13.3.0" | |
| 7813 | + } | |
| 7814 | + }, | |
| 7815 | + "node_modules/own-keys": { | |
| 7816 | + "version": "1.0.1", | |
| 7817 | + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", | |
| 7818 | + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", | |
| 7819 | + "dev": true, | |
| 7820 | + "license": "MIT", | |
| 7821 | + "dependencies": { | |
| 7822 | + "get-intrinsic": "^1.2.6", | |
| 7823 | + "object-keys": "^1.1.1", | |
| 7824 | + "safe-push-apply": "^1.0.0" | |
| 7825 | + }, | |
| 7826 | + "engines": { | |
| 7827 | + "node": ">= 0.4" | |
| 7828 | + }, | |
| 7829 | + "funding": { | |
| 7830 | + "url": "https://github.com/sponsors/ljharb" | |
| 7831 | + } | |
| 7832 | + }, | |
| 7833 | + "node_modules/p-limit": { | |
| 7834 | + "version": "3.1.0", | |
| 7835 | + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", | |
| 7836 | + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", | |
| 7837 | + "dev": true, | |
| 7838 | + "license": "MIT", | |
| 7839 | + "dependencies": { | |
| 7840 | + "yocto-queue": "^0.1.0" | |
| 7841 | + }, | |
| 7842 | + "engines": { | |
| 7843 | + "node": ">=10" | |
| 7844 | + }, | |
| 7845 | + "funding": { | |
| 7846 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 7847 | + } | |
| 7848 | + }, | |
| 7849 | + "node_modules/p-locate": { | |
| 7850 | + "version": "5.0.0", | |
| 7851 | + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", | |
| 7852 | + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", | |
| 7853 | + "dev": true, | |
| 7854 | + "license": "MIT", | |
| 7855 | + "dependencies": { | |
| 7856 | + "p-limit": "^3.0.2" | |
| 7857 | + }, | |
| 7858 | + "engines": { | |
| 7859 | + "node": ">=10" | |
| 7860 | + }, | |
| 7861 | + "funding": { | |
| 7862 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 7863 | + } | |
| 7864 | + }, | |
| 7865 | + "node_modules/p-try": { | |
| 7866 | + "version": "2.2.0", | |
| 7867 | + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", | |
| 7868 | + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", | |
| 7869 | + "license": "MIT", | |
| 7870 | + "engines": { | |
| 7871 | + "node": ">=6" | |
| 7872 | + } | |
| 7873 | + }, | |
| 7874 | + "node_modules/pako": { | |
| 7875 | + "version": "1.0.11", | |
| 7876 | + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", | |
| 7877 | + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", | |
| 7878 | + "license": "(MIT AND Zlib)" | |
| 7879 | + }, | |
| 7880 | + "node_modules/papaparse": { | |
| 7881 | + "version": "5.5.3", | |
| 7882 | + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", | |
| 7883 | + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", | |
| 7884 | + "license": "MIT" | |
| 7885 | + }, | |
| 7886 | + "node_modules/parent-module": { | |
| 7887 | + "version": "1.0.1", | |
| 7888 | + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", | |
| 7889 | + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", | |
| 7890 | + "dev": true, | |
| 7891 | + "license": "MIT", | |
| 7892 | + "dependencies": { | |
| 7893 | + "callsites": "^3.0.0" | |
| 7894 | + }, | |
| 7895 | + "engines": { | |
| 7896 | + "node": ">=6" | |
| 7897 | + } | |
| 7898 | + }, | |
| 7899 | + "node_modules/parse-entities": { | |
| 7900 | + "version": "4.0.2", | |
| 7901 | + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", | |
| 7902 | + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", | |
| 7903 | + "license": "MIT", | |
| 7904 | + "dependencies": { | |
| 7905 | + "@types/unist": "^2.0.0", | |
| 7906 | + "character-entities-legacy": "^3.0.0", | |
| 7907 | + "character-reference-invalid": "^2.0.0", | |
| 7908 | + "decode-named-character-reference": "^1.0.0", | |
| 7909 | + "is-alphanumerical": "^2.0.0", | |
| 7910 | + "is-decimal": "^2.0.0", | |
| 7911 | + "is-hexadecimal": "^2.0.0" | |
| 7912 | + }, | |
| 7913 | + "funding": { | |
| 7914 | + "type": "github", | |
| 7915 | + "url": "https://github.com/sponsors/wooorm" | |
| 7916 | + } | |
| 7917 | + }, | |
| 7918 | + "node_modules/parse-entities/node_modules/@types/unist": { | |
| 7919 | + "version": "2.0.11", | |
| 7920 | + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", | |
| 7921 | + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", | |
| 7922 | + "license": "MIT" | |
| 7923 | + }, | |
| 7924 | + "node_modules/path-exists": { | |
| 7925 | + "version": "4.0.0", | |
| 7926 | + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", | |
| 7927 | + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", | |
| 7928 | + "license": "MIT", | |
| 7929 | + "engines": { | |
| 7930 | + "node": ">=8" | |
| 7931 | + } | |
| 7932 | + }, | |
| 7933 | + "node_modules/path-is-absolute": { | |
| 7934 | + "version": "1.0.1", | |
| 7935 | + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | |
| 7936 | + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", | |
| 7937 | + "dev": true, | |
| 7938 | + "license": "MIT", | |
| 7939 | + "engines": { | |
| 7940 | + "node": ">=0.10.0" | |
| 7941 | + } | |
| 7942 | + }, | |
| 7943 | + "node_modules/path-key": { | |
| 7944 | + "version": "3.1.1", | |
| 7945 | + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", | |
| 7946 | + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", | |
| 7947 | + "license": "MIT", | |
| 7948 | + "engines": { | |
| 7949 | + "node": ">=8" | |
| 7950 | + } | |
| 7951 | + }, | |
| 7952 | + "node_modules/path-parse": { | |
| 7953 | + "version": "1.0.7", | |
| 7954 | + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", | |
| 7955 | + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", | |
| 7956 | + "dev": true, | |
| 7957 | + "license": "MIT" | |
| 7958 | + }, | |
| 7959 | + "node_modules/path-scurry": { | |
| 7960 | + "version": "1.11.1", | |
| 7961 | + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", | |
| 7962 | + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", | |
| 7963 | + "license": "BlueOak-1.0.0", | |
| 7964 | + "dependencies": { | |
| 7965 | + "lru-cache": "^10.2.0", | |
| 7966 | + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" | |
| 7967 | + }, | |
| 7968 | + "engines": { | |
| 7969 | + "node": ">=16 || 14 >=14.18" | |
| 7970 | + }, | |
| 7971 | + "funding": { | |
| 7972 | + "url": "https://github.com/sponsors/isaacs" | |
| 7973 | + } | |
| 7974 | + }, | |
| 7975 | + "node_modules/pdf-lib": { | |
| 7976 | + "version": "1.17.1", | |
| 7977 | + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", | |
| 7978 | + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", | |
| 7979 | + "license": "MIT", | |
| 7980 | + "dependencies": { | |
| 7981 | + "@pdf-lib/standard-fonts": "^1.0.0", | |
| 7982 | + "@pdf-lib/upng": "^1.0.1", | |
| 7983 | + "pako": "^1.0.11", | |
| 7984 | + "tslib": "^1.11.1" | |
| 7985 | + } | |
| 7986 | + }, | |
| 7987 | + "node_modules/pdf-lib/node_modules/tslib": { | |
| 7988 | + "version": "1.14.1", | |
| 7989 | + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", | |
| 7990 | + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", | |
| 7991 | + "license": "0BSD" | |
| 7992 | + }, | |
| 7993 | + "node_modules/pdfjs-dist": { | |
| 7994 | + "version": "5.4.624", | |
| 7995 | + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.624.tgz", | |
| 7996 | + "integrity": "sha512-sm6TxKTtWv1Oh6n3C6J6a8odejb5uO4A4zo/2dgkHuC0iu8ZMAXOezEODkVaoVp8nX1Xzr+0WxFJJmUr45hQzg==", | |
| 7997 | + "license": "Apache-2.0", | |
| 7998 | + "engines": { | |
| 7999 | + "node": ">=20.16.0 || >=22.3.0" | |
| 8000 | + }, | |
| 8001 | + "optionalDependencies": { | |
| 8002 | + "@napi-rs/canvas": "^0.1.88", | |
| 8003 | + "node-readable-to-web-readable-stream": "^0.4.2" | |
| 8004 | + } | |
| 8005 | + }, | |
| 8006 | + "node_modules/picocolors": { | |
| 8007 | + "version": "1.1.1", | |
| 8008 | + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", | |
| 8009 | + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", | |
| 8010 | + "license": "ISC" | |
| 8011 | + }, | |
| 8012 | + "node_modules/picomatch": { | |
| 8013 | + "version": "2.3.1", | |
| 8014 | + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", | |
| 8015 | + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", | |
| 8016 | + "dev": true, | |
| 8017 | + "license": "MIT", | |
| 8018 | + "engines": { | |
| 8019 | + "node": ">=8.6" | |
| 8020 | + }, | |
| 8021 | + "funding": { | |
| 8022 | + "url": "https://github.com/sponsors/jonschlinkert" | |
| 8023 | + } | |
| 8024 | + }, | |
| 8025 | + "node_modules/pify": { | |
| 8026 | + "version": "2.3.0", | |
| 8027 | + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", | |
| 8028 | + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", | |
| 8029 | + "dev": true, | |
| 8030 | + "license": "MIT", | |
| 8031 | + "engines": { | |
| 8032 | + "node": ">=0.10.0" | |
| 8033 | + } | |
| 8034 | + }, | |
| 8035 | + "node_modules/pirates": { | |
| 8036 | + "version": "4.0.7", | |
| 8037 | + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", | |
| 8038 | + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", | |
| 8039 | + "dev": true, | |
| 8040 | + "license": "MIT", | |
| 8041 | + "engines": { | |
| 8042 | + "node": ">= 6" | |
| 8043 | + } | |
| 8044 | + }, | |
| 8045 | + "node_modules/pngjs": { | |
| 8046 | + "version": "5.0.0", | |
| 8047 | + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", | |
| 8048 | + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", | |
| 8049 | + "license": "MIT", | |
| 8050 | + "engines": { | |
| 8051 | + "node": ">=10.13.0" | |
| 8052 | + } | |
| 8053 | + }, | |
| 8054 | + "node_modules/possible-typed-array-names": { | |
| 8055 | + "version": "1.1.0", | |
| 8056 | + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", | |
| 8057 | + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", | |
| 8058 | + "dev": true, | |
| 8059 | + "license": "MIT", | |
| 8060 | + "engines": { | |
| 8061 | + "node": ">= 0.4" | |
| 8062 | + } | |
| 8063 | + }, | |
| 8064 | + "node_modules/postcss": { | |
| 8065 | + "version": "8.5.6", | |
| 8066 | + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", | |
| 8067 | + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", | |
| 8068 | + "dev": true, | |
| 8069 | + "funding": [ | |
| 8070 | + { | |
| 8071 | + "type": "opencollective", | |
| 8072 | + "url": "https://opencollective.com/postcss/" | |
| 8073 | + }, | |
| 8074 | + { | |
| 8075 | + "type": "tidelift", | |
| 8076 | + "url": "https://tidelift.com/funding/github/npm/postcss" | |
| 8077 | + }, | |
| 8078 | + { | |
| 8079 | + "type": "github", | |
| 8080 | + "url": "https://github.com/sponsors/ai" | |
| 8081 | + } | |
| 8082 | + ], | |
| 8083 | + "license": "MIT", | |
| 8084 | + "dependencies": { | |
| 8085 | + "nanoid": "^3.3.11", | |
| 8086 | + "picocolors": "^1.1.1", | |
| 8087 | + "source-map-js": "^1.2.1" | |
| 8088 | + }, | |
| 8089 | + "engines": { | |
| 8090 | + "node": "^10 || ^12 || >=14" | |
| 8091 | + } | |
| 8092 | + }, | |
| 8093 | + "node_modules/postcss-import": { | |
| 8094 | + "version": "15.1.0", | |
| 8095 | + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", | |
| 8096 | + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", | |
| 8097 | + "dev": true, | |
| 8098 | + "license": "MIT", | |
| 8099 | + "dependencies": { | |
| 8100 | + "postcss-value-parser": "^4.0.0", | |
| 8101 | + "read-cache": "^1.0.0", | |
| 8102 | + "resolve": "^1.1.7" | |
| 8103 | + }, | |
| 8104 | + "engines": { | |
| 8105 | + "node": ">=14.0.0" | |
| 8106 | + }, | |
| 8107 | + "peerDependencies": { | |
| 8108 | + "postcss": "^8.0.0" | |
| 8109 | + } | |
| 8110 | + }, | |
| 8111 | + "node_modules/postcss-js": { | |
| 8112 | + "version": "4.1.0", | |
| 8113 | + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", | |
| 8114 | + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", | |
| 8115 | + "dev": true, | |
| 8116 | + "funding": [ | |
| 8117 | + { | |
| 8118 | + "type": "opencollective", | |
| 8119 | + "url": "https://opencollective.com/postcss/" | |
| 8120 | + }, | |
| 8121 | + { | |
| 8122 | + "type": "github", | |
| 8123 | + "url": "https://github.com/sponsors/ai" | |
| 8124 | + } | |
| 8125 | + ], | |
| 8126 | + "license": "MIT", | |
| 8127 | + "dependencies": { | |
| 8128 | + "camelcase-css": "^2.0.1" | |
| 8129 | + }, | |
| 8130 | + "engines": { | |
| 8131 | + "node": "^12 || ^14 || >= 16" | |
| 8132 | + }, | |
| 8133 | + "peerDependencies": { | |
| 8134 | + "postcss": "^8.4.21" | |
| 8135 | + } | |
| 8136 | + }, | |
| 8137 | + "node_modules/postcss-load-config": { | |
| 8138 | + "version": "6.0.1", | |
| 8139 | + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", | |
| 8140 | + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", | |
| 8141 | + "dev": true, | |
| 8142 | + "funding": [ | |
| 8143 | + { | |
| 8144 | + "type": "opencollective", | |
| 8145 | + "url": "https://opencollective.com/postcss/" | |
| 8146 | + }, | |
| 8147 | + { | |
| 8148 | + "type": "github", | |
| 8149 | + "url": "https://github.com/sponsors/ai" | |
| 8150 | + } | |
| 8151 | + ], | |
| 8152 | + "license": "MIT", | |
| 8153 | + "dependencies": { | |
| 8154 | + "lilconfig": "^3.1.1" | |
| 8155 | + }, | |
| 8156 | + "engines": { | |
| 8157 | + "node": ">= 18" | |
| 8158 | + }, | |
| 8159 | + "peerDependencies": { | |
| 8160 | + "jiti": ">=1.21.0", | |
| 8161 | + "postcss": ">=8.0.9", | |
| 8162 | + "tsx": "^4.8.1", | |
| 8163 | + "yaml": "^2.4.2" | |
| 8164 | + }, | |
| 8165 | + "peerDependenciesMeta": { | |
| 8166 | + "jiti": { | |
| 8167 | + "optional": true | |
| 8168 | + }, | |
| 8169 | + "postcss": { | |
| 8170 | + "optional": true | |
| 8171 | + }, | |
| 8172 | + "tsx": { | |
| 8173 | + "optional": true | |
| 8174 | + }, | |
| 8175 | + "yaml": { | |
| 8176 | + "optional": true | |
| 8177 | + } | |
| 8178 | + } | |
| 8179 | + }, | |
| 8180 | + "node_modules/postcss-nested": { | |
| 8181 | + "version": "6.2.0", | |
| 8182 | + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", | |
| 8183 | + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", | |
| 8184 | + "dev": true, | |
| 8185 | + "funding": [ | |
| 8186 | + { | |
| 8187 | + "type": "opencollective", | |
| 8188 | + "url": "https://opencollective.com/postcss/" | |
| 8189 | + }, | |
| 8190 | + { | |
| 8191 | + "type": "github", | |
| 8192 | + "url": "https://github.com/sponsors/ai" | |
| 8193 | + } | |
| 8194 | + ], | |
| 8195 | + "license": "MIT", | |
| 8196 | + "dependencies": { | |
| 8197 | + "postcss-selector-parser": "^6.1.1" | |
| 8198 | + }, | |
| 8199 | + "engines": { | |
| 8200 | + "node": ">=12.0" | |
| 8201 | + }, | |
| 8202 | + "peerDependencies": { | |
| 8203 | + "postcss": "^8.2.14" | |
| 8204 | + } | |
| 8205 | + }, | |
| 8206 | + "node_modules/postcss-selector-parser": { | |
| 8207 | + "version": "6.1.2", | |
| 8208 | + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", | |
| 8209 | + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", | |
| 8210 | + "dev": true, | |
| 8211 | + "license": "MIT", | |
| 8212 | + "dependencies": { | |
| 8213 | + "cssesc": "^3.0.0", | |
| 8214 | + "util-deprecate": "^1.0.2" | |
| 8215 | + }, | |
| 8216 | + "engines": { | |
| 8217 | + "node": ">=4" | |
| 8218 | + } | |
| 8219 | + }, | |
| 8220 | + "node_modules/postcss-value-parser": { | |
| 8221 | + "version": "4.2.0", | |
| 8222 | + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", | |
| 8223 | + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", | |
| 8224 | + "dev": true, | |
| 8225 | + "license": "MIT" | |
| 8226 | + }, | |
| 8227 | + "node_modules/prelude-ls": { | |
| 8228 | + "version": "1.2.1", | |
| 8229 | + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", | |
| 8230 | + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", | |
| 8231 | + "dev": true, | |
| 8232 | + "license": "MIT", | |
| 8233 | + "engines": { | |
| 8234 | + "node": ">= 0.8.0" | |
| 8235 | + } | |
| 8236 | + }, | |
| 8237 | + "node_modules/prisma": { | |
| 8238 | + "version": "5.22.0", | |
| 8239 | + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", | |
| 8240 | + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", | |
| 8241 | + "hasInstallScript": true, | |
| 8242 | + "license": "Apache-2.0", | |
| 8243 | + "dependencies": { | |
| 8244 | + "@prisma/engines": "5.22.0" | |
| 8245 | + }, | |
| 8246 | + "bin": { | |
| 8247 | + "prisma": "build/index.js" | |
| 8248 | + }, | |
| 8249 | + "engines": { | |
| 8250 | + "node": ">=16.13" | |
| 8251 | + }, | |
| 8252 | + "optionalDependencies": { | |
| 8253 | + "fsevents": "2.3.3" | |
| 8254 | + } | |
| 8255 | + }, | |
| 8256 | + "node_modules/prismjs": { | |
| 8257 | + "version": "1.30.0", | |
| 8258 | + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", | |
| 8259 | + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", | |
| 8260 | + "license": "MIT", | |
| 8261 | + "engines": { | |
| 8262 | + "node": ">=6" | |
| 8263 | + } | |
| 8264 | + }, | |
| 8265 | + "node_modules/process": { | |
| 8266 | + "version": "0.11.10", | |
| 8267 | + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", | |
| 8268 | + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", | |
| 8269 | + "license": "MIT", | |
| 8270 | + "engines": { | |
| 8271 | + "node": ">= 0.6.0" | |
| 8272 | + } | |
| 8273 | + }, | |
| 8274 | + "node_modules/process-nextick-args": { | |
| 8275 | + "version": "2.0.1", | |
| 8276 | + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", | |
| 8277 | + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", | |
| 8278 | + "license": "MIT" | |
| 8279 | + }, | |
| 8280 | + "node_modules/prop-types": { | |
| 8281 | + "version": "15.8.1", | |
| 8282 | + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | |
| 8283 | + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", | |
| 8284 | + "license": "MIT", | |
| 8285 | + "dependencies": { | |
| 8286 | + "loose-envify": "^1.4.0", | |
| 8287 | + "object-assign": "^4.1.1", | |
| 8288 | + "react-is": "^16.13.1" | |
| 8289 | + } | |
| 8290 | + }, | |
| 8291 | + "node_modules/property-information": { | |
| 8292 | + "version": "7.1.0", | |
| 8293 | + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", | |
| 8294 | + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", | |
| 8295 | + "license": "MIT", | |
| 8296 | + "funding": { | |
| 8297 | + "type": "github", | |
| 8298 | + "url": "https://github.com/sponsors/wooorm" | |
| 8299 | + } | |
| 8300 | + }, | |
| 8301 | + "node_modules/punycode": { | |
| 8302 | + "version": "2.3.1", | |
| 8303 | + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", | |
| 8304 | + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", | |
| 8305 | + "dev": true, | |
| 8306 | + "license": "MIT", | |
| 8307 | + "engines": { | |
| 8308 | + "node": ">=6" | |
| 8309 | + } | |
| 8310 | + }, | |
| 8311 | + "node_modules/qrcode": { | |
| 8312 | + "version": "1.5.4", | |
| 8313 | + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", | |
| 8314 | + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", | |
| 8315 | + "license": "MIT", | |
| 8316 | + "dependencies": { | |
| 8317 | + "dijkstrajs": "^1.0.1", | |
| 8318 | + "pngjs": "^5.0.0", | |
| 8319 | + "yargs": "^15.3.1" | |
| 8320 | + }, | |
| 8321 | + "bin": { | |
| 8322 | + "qrcode": "bin/qrcode" | |
| 8323 | + }, | |
| 8324 | + "engines": { | |
| 8325 | + "node": ">=10.13.0" | |
| 8326 | + } | |
| 8327 | + }, | |
| 8328 | + "node_modules/queue-microtask": { | |
| 8329 | + "version": "1.2.3", | |
| 8330 | + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", | |
| 8331 | + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", | |
| 8332 | + "dev": true, | |
| 8333 | + "funding": [ | |
| 8334 | + { | |
| 8335 | + "type": "github", | |
| 8336 | + "url": "https://github.com/sponsors/feross" | |
| 8337 | + }, | |
| 8338 | + { | |
| 8339 | + "type": "patreon", | |
| 8340 | + "url": "https://www.patreon.com/feross" | |
| 8341 | + }, | |
| 8342 | + { | |
| 8343 | + "type": "consulting", | |
| 8344 | + "url": "https://feross.org/support" | |
| 8345 | + } | |
| 8346 | + ], | |
| 8347 | + "license": "MIT" | |
| 8348 | + }, | |
| 8349 | + "node_modules/rate-limiter-flexible": { | |
| 8350 | + "version": "9.1.1", | |
| 8351 | + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-9.1.1.tgz", | |
| 8352 | + "integrity": "sha512-imxFjzPCmvDLMe7d2tsgiSQvs5EI2fI9SNymmslAfOqznZhsZ+PqbIjIYKpuSbd3pKovR1aMG47qfCLIO/adVg==", | |
| 8353 | + "license": "ISC" | |
| 8354 | + }, | |
| 8355 | + "node_modules/react": { | |
| 8356 | + "version": "18.3.1", | |
| 8357 | + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", | |
| 8358 | + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", | |
| 8359 | + "license": "MIT", | |
| 8360 | + "dependencies": { | |
| 8361 | + "loose-envify": "^1.1.0" | |
| 8362 | + }, | |
| 8363 | + "engines": { | |
| 8364 | + "node": ">=0.10.0" | |
| 8365 | + } | |
| 8366 | + }, | |
| 8367 | + "node_modules/react-dom": { | |
| 8368 | + "version": "18.3.1", | |
| 8369 | + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", | |
| 8370 | + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", | |
| 8371 | + "license": "MIT", | |
| 8372 | + "dependencies": { | |
| 8373 | + "loose-envify": "^1.1.0", | |
| 8374 | + "scheduler": "^0.23.2" | |
| 8375 | + }, | |
| 8376 | + "peerDependencies": { | |
| 8377 | + "react": "^18.3.1" | |
| 8378 | + } | |
| 8379 | + }, | |
| 8380 | + "node_modules/react-dropzone": { | |
| 8381 | + "version": "15.0.0", | |
| 8382 | + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-15.0.0.tgz", | |
| 8383 | + "integrity": "sha512-lGjYV/EoqEjEWPnmiSvH4v5IoIAwQM2W4Z1C0Q/Pw2xD0eVzKPS359BQTUMum+1fa0kH2nrKjuavmTPOGhpLPg==", | |
| 8384 | + "license": "MIT", | |
| 8385 | + "dependencies": { | |
| 8386 | + "attr-accept": "^2.2.4", | |
| 8387 | + "file-selector": "^2.1.0", | |
| 8388 | + "prop-types": "^15.8.1" | |
| 8389 | + }, | |
| 8390 | + "engines": { | |
| 8391 | + "node": ">= 10.13" | |
| 8392 | + }, | |
| 8393 | + "peerDependencies": { | |
| 8394 | + "react": ">= 16.8 || 18.0.0" | |
| 8395 | + } | |
| 8396 | + }, | |
| 8397 | + "node_modules/react-hot-toast": { | |
| 8398 | + "version": "2.6.0", | |
| 8399 | + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", | |
| 8400 | + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", | |
| 8401 | + "license": "MIT", | |
| 8402 | + "dependencies": { | |
| 8403 | + "csstype": "^3.1.3", | |
| 8404 | + "goober": "^2.1.16" | |
| 8405 | + }, | |
| 8406 | + "engines": { | |
| 8407 | + "node": ">=10" | |
| 8408 | + }, | |
| 8409 | + "peerDependencies": { | |
| 8410 | + "react": ">=16", | |
| 8411 | + "react-dom": ">=16" | |
| 8412 | + } | |
| 8413 | + }, | |
| 8414 | + "node_modules/react-is": { | |
| 8415 | + "version": "16.13.1", | |
| 8416 | + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", | |
| 8417 | + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", | |
| 8418 | + "license": "MIT" | |
| 8419 | + }, | |
| 8420 | + "node_modules/react-markdown": { | |
| 8421 | + "version": "10.1.0", | |
| 8422 | + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", | |
| 8423 | + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", | |
| 8424 | + "license": "MIT", | |
| 8425 | + "dependencies": { | |
| 8426 | + "@types/hast": "^3.0.0", | |
| 8427 | + "@types/mdast": "^4.0.0", | |
| 8428 | + "devlop": "^1.0.0", | |
| 8429 | + "hast-util-to-jsx-runtime": "^2.0.0", | |
| 8430 | + "html-url-attributes": "^3.0.0", | |
| 8431 | + "mdast-util-to-hast": "^13.0.0", | |
| 8432 | + "remark-parse": "^11.0.0", | |
| 8433 | + "remark-rehype": "^11.0.0", | |
| 8434 | + "unified": "^11.0.0", | |
| 8435 | + "unist-util-visit": "^5.0.0", | |
| 8436 | + "vfile": "^6.0.0" | |
| 8437 | + }, | |
| 8438 | + "funding": { | |
| 8439 | + "type": "opencollective", | |
| 8440 | + "url": "https://opencollective.com/unified" | |
| 8441 | + }, | |
| 8442 | + "peerDependencies": { | |
| 8443 | + "@types/react": ">=18", | |
| 8444 | + "react": ">=18" | |
| 8445 | + } | |
| 8446 | + }, | |
| 8447 | + "node_modules/react-syntax-highlighter": { | |
| 8448 | + "version": "16.1.0", | |
| 8449 | + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz", | |
| 8450 | + "integrity": "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==", | |
| 8451 | + "license": "MIT", | |
| 8452 | + "dependencies": { | |
| 8453 | + "@babel/runtime": "^7.28.4", | |
| 8454 | + "highlight.js": "^10.4.1", | |
| 8455 | + "highlightjs-vue": "^1.0.0", | |
| 8456 | + "lowlight": "^1.17.0", | |
| 8457 | + "prismjs": "^1.30.0", | |
| 8458 | + "refractor": "^5.0.0" | |
| 8459 | + }, | |
| 8460 | + "engines": { | |
| 8461 | + "node": ">= 16.20.2" | |
| 8462 | + }, | |
| 8463 | + "peerDependencies": { | |
| 8464 | + "react": ">= 0.14.0" | |
| 8465 | + } | |
| 8466 | + }, | |
| 8467 | + "node_modules/read-cache": { | |
| 8468 | + "version": "1.0.0", | |
| 8469 | + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", | |
| 8470 | + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", | |
| 8471 | + "dev": true, | |
| 8472 | + "license": "MIT", | |
| 8473 | + "dependencies": { | |
| 8474 | + "pify": "^2.3.0" | |
| 8475 | + } | |
| 8476 | + }, | |
| 8477 | + "node_modules/readable-stream": { | |
| 8478 | + "version": "2.3.8", | |
| 8479 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", | |
| 8480 | + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", | |
| 8481 | + "license": "MIT", | |
| 8482 | + "dependencies": { | |
| 8483 | + "core-util-is": "~1.0.0", | |
| 8484 | + "inherits": "~2.0.3", | |
| 8485 | + "isarray": "~1.0.0", | |
| 8486 | + "process-nextick-args": "~2.0.0", | |
| 8487 | + "safe-buffer": "~5.1.1", | |
| 8488 | + "string_decoder": "~1.1.1", | |
| 8489 | + "util-deprecate": "~1.0.1" | |
| 8490 | + } | |
| 8491 | + }, | |
| 8492 | + "node_modules/readable-stream/node_modules/isarray": { | |
| 8493 | + "version": "1.0.0", | |
| 8494 | + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", | |
| 8495 | + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", | |
| 8496 | + "license": "MIT" | |
| 8497 | + }, | |
| 8498 | + "node_modules/readdir-glob": { | |
| 8499 | + "version": "1.1.3", | |
| 8500 | + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", | |
| 8501 | + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", | |
| 8502 | + "license": "Apache-2.0", | |
| 8503 | + "dependencies": { | |
| 8504 | + "minimatch": "^5.1.0" | |
| 8505 | + } | |
| 8506 | + }, | |
| 8507 | + "node_modules/readdir-glob/node_modules/brace-expansion": { | |
| 8508 | + "version": "2.0.2", | |
| 8509 | + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", | |
| 8510 | + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", | |
| 8511 | + "license": "MIT", | |
| 8512 | + "dependencies": { | |
| 8513 | + "balanced-match": "^1.0.0" | |
| 8514 | + } | |
| 8515 | + }, | |
| 8516 | + "node_modules/readdir-glob/node_modules/minimatch": { | |
| 8517 | + "version": "5.1.6", | |
| 8518 | + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", | |
| 8519 | + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", | |
| 8520 | + "license": "ISC", | |
| 8521 | + "dependencies": { | |
| 8522 | + "brace-expansion": "^2.0.1" | |
| 8523 | + }, | |
| 8524 | + "engines": { | |
| 8525 | + "node": ">=10" | |
| 8526 | + } | |
| 8527 | + }, | |
| 8528 | + "node_modules/readdirp": { | |
| 8529 | + "version": "3.6.0", | |
| 8530 | + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", | |
| 8531 | + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", | |
| 8532 | + "dev": true, | |
| 8533 | + "license": "MIT", | |
| 8534 | + "dependencies": { | |
| 8535 | + "picomatch": "^2.2.1" | |
| 8536 | + }, | |
| 8537 | + "engines": { | |
| 8538 | + "node": ">=8.10.0" | |
| 8539 | + } | |
| 8540 | + }, | |
| 8541 | + "node_modules/reflect.getprototypeof": { | |
| 8542 | + "version": "1.0.10", | |
| 8543 | + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", | |
| 8544 | + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", | |
| 8545 | + "dev": true, | |
| 8546 | + "license": "MIT", | |
| 8547 | + "dependencies": { | |
| 8548 | + "call-bind": "^1.0.8", | |
| 8549 | + "define-properties": "^1.2.1", | |
| 8550 | + "es-abstract": "^1.23.9", | |
| 8551 | + "es-errors": "^1.3.0", | |
| 8552 | + "es-object-atoms": "^1.0.0", | |
| 8553 | + "get-intrinsic": "^1.2.7", | |
| 8554 | + "get-proto": "^1.0.1", | |
| 8555 | + "which-builtin-type": "^1.2.1" | |
| 8556 | + }, | |
| 8557 | + "engines": { | |
| 8558 | + "node": ">= 0.4" | |
| 8559 | + }, | |
| 8560 | + "funding": { | |
| 8561 | + "url": "https://github.com/sponsors/ljharb" | |
| 8562 | + } | |
| 8563 | + }, | |
| 8564 | + "node_modules/refractor": { | |
| 8565 | + "version": "5.0.0", | |
| 8566 | + "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", | |
| 8567 | + "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", | |
| 8568 | + "license": "MIT", | |
| 8569 | + "dependencies": { | |
| 8570 | + "@types/hast": "^3.0.0", | |
| 8571 | + "@types/prismjs": "^1.0.0", | |
| 8572 | + "hastscript": "^9.0.0", | |
| 8573 | + "parse-entities": "^4.0.0" | |
| 8574 | + }, | |
| 8575 | + "funding": { | |
| 8576 | + "type": "github", | |
| 8577 | + "url": "https://github.com/sponsors/wooorm" | |
| 8578 | + } | |
| 8579 | + }, | |
| 8580 | + "node_modules/regexp.prototype.flags": { | |
| 8581 | + "version": "1.5.4", | |
| 8582 | + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", | |
| 8583 | + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", | |
| 8584 | + "dev": true, | |
| 8585 | + "license": "MIT", | |
| 8586 | + "dependencies": { | |
| 8587 | + "call-bind": "^1.0.8", | |
| 8588 | + "define-properties": "^1.2.1", | |
| 8589 | + "es-errors": "^1.3.0", | |
| 8590 | + "get-proto": "^1.0.1", | |
| 8591 | + "gopd": "^1.2.0", | |
| 8592 | + "set-function-name": "^2.0.2" | |
| 8593 | + }, | |
| 8594 | + "engines": { | |
| 8595 | + "node": ">= 0.4" | |
| 8596 | + }, | |
| 8597 | + "funding": { | |
| 8598 | + "url": "https://github.com/sponsors/ljharb" | |
| 8599 | + } | |
| 8600 | + }, | |
| 8601 | + "node_modules/remark-parse": { | |
| 8602 | + "version": "11.0.0", | |
| 8603 | + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", | |
| 8604 | + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", | |
| 8605 | + "license": "MIT", | |
| 8606 | + "dependencies": { | |
| 8607 | + "@types/mdast": "^4.0.0", | |
| 8608 | + "mdast-util-from-markdown": "^2.0.0", | |
| 8609 | + "micromark-util-types": "^2.0.0", | |
| 8610 | + "unified": "^11.0.0" | |
| 8611 | + }, | |
| 8612 | + "funding": { | |
| 8613 | + "type": "opencollective", | |
| 8614 | + "url": "https://opencollective.com/unified" | |
| 8615 | + } | |
| 8616 | + }, | |
| 8617 | + "node_modules/remark-rehype": { | |
| 8618 | + "version": "11.1.2", | |
| 8619 | + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", | |
| 8620 | + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", | |
| 8621 | + "license": "MIT", | |
| 8622 | + "dependencies": { | |
| 8623 | + "@types/hast": "^3.0.0", | |
| 8624 | + "@types/mdast": "^4.0.0", | |
| 8625 | + "mdast-util-to-hast": "^13.0.0", | |
| 8626 | + "unified": "^11.0.0", | |
| 8627 | + "vfile": "^6.0.0" | |
| 8628 | + }, | |
| 8629 | + "funding": { | |
| 8630 | + "type": "opencollective", | |
| 8631 | + "url": "https://opencollective.com/unified" | |
| 8632 | + } | |
| 8633 | + }, | |
| 8634 | + "node_modules/require-directory": { | |
| 8635 | + "version": "2.1.1", | |
| 8636 | + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", | |
| 8637 | + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", | |
| 8638 | + "license": "MIT", | |
| 8639 | + "engines": { | |
| 8640 | + "node": ">=0.10.0" | |
| 8641 | + } | |
| 8642 | + }, | |
| 8643 | + "node_modules/require-main-filename": { | |
| 8644 | + "version": "2.0.0", | |
| 8645 | + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", | |
| 8646 | + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", | |
| 8647 | + "license": "ISC" | |
| 8648 | + }, | |
| 8649 | + "node_modules/resolve": { | |
| 8650 | + "version": "1.22.11", | |
| 8651 | + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", | |
| 8652 | + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", | |
| 8653 | + "dev": true, | |
| 8654 | + "license": "MIT", | |
| 8655 | + "dependencies": { | |
| 8656 | + "is-core-module": "^2.16.1", | |
| 8657 | + "path-parse": "^1.0.7", | |
| 8658 | + "supports-preserve-symlinks-flag": "^1.0.0" | |
| 8659 | + }, | |
| 8660 | + "bin": { | |
| 8661 | + "resolve": "bin/resolve" | |
| 8662 | + }, | |
| 8663 | + "engines": { | |
| 8664 | + "node": ">= 0.4" | |
| 8665 | + }, | |
| 8666 | + "funding": { | |
| 8667 | + "url": "https://github.com/sponsors/ljharb" | |
| 8668 | + } | |
| 8669 | + }, | |
| 8670 | + "node_modules/resolve-from": { | |
| 8671 | + "version": "4.0.0", | |
| 8672 | + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", | |
| 8673 | + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", | |
| 8674 | + "dev": true, | |
| 8675 | + "license": "MIT", | |
| 8676 | + "engines": { | |
| 8677 | + "node": ">=4" | |
| 8678 | + } | |
| 8679 | + }, | |
| 8680 | + "node_modules/resolve-pkg-maps": { | |
| 8681 | + "version": "1.0.0", | |
| 8682 | + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", | |
| 8683 | + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", | |
| 8684 | + "dev": true, | |
| 8685 | + "license": "MIT", | |
| 8686 | + "funding": { | |
| 8687 | + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" | |
| 8688 | + } | |
| 8689 | + }, | |
| 8690 | + "node_modules/reusify": { | |
| 8691 | + "version": "1.1.0", | |
| 8692 | + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", | |
| 8693 | + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", | |
| 8694 | + "dev": true, | |
| 8695 | + "license": "MIT", | |
| 8696 | + "engines": { | |
| 8697 | + "iojs": ">=1.0.0", | |
| 8698 | + "node": ">=0.10.0" | |
| 8699 | + } | |
| 8700 | + }, | |
| 8701 | + "node_modules/rimraf": { | |
| 8702 | + "version": "3.0.2", | |
| 8703 | + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", | |
| 8704 | + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", | |
| 8705 | + "deprecated": "Rimraf versions prior to v4 are no longer supported", | |
| 8706 | + "dev": true, | |
| 8707 | + "license": "ISC", | |
| 8708 | + "dependencies": { | |
| 8709 | + "glob": "^7.1.3" | |
| 8710 | + }, | |
| 8711 | + "bin": { | |
| 8712 | + "rimraf": "bin.js" | |
| 8713 | + }, | |
| 8714 | + "funding": { | |
| 8715 | + "url": "https://github.com/sponsors/isaacs" | |
| 8716 | + } | |
| 8717 | + }, | |
| 8718 | + "node_modules/rimraf/node_modules/glob": { | |
| 8719 | + "version": "7.2.3", | |
| 8720 | + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", | |
| 8721 | + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", | |
| 8722 | + "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", | |
| 8723 | + "dev": true, | |
| 8724 | + "license": "ISC", | |
| 8725 | + "dependencies": { | |
| 8726 | + "fs.realpath": "^1.0.0", | |
| 8727 | + "inflight": "^1.0.4", | |
| 8728 | + "inherits": "2", | |
| 8729 | + "minimatch": "^3.1.1", | |
| 8730 | + "once": "^1.3.0", | |
| 8731 | + "path-is-absolute": "^1.0.0" | |
| 8732 | + }, | |
| 8733 | + "engines": { | |
| 8734 | + "node": "*" | |
| 8735 | + }, | |
| 8736 | + "funding": { | |
| 8737 | + "url": "https://github.com/sponsors/isaacs" | |
| 8738 | + } | |
| 8739 | + }, | |
| 8740 | + "node_modules/run-parallel": { | |
| 8741 | + "version": "1.2.0", | |
| 8742 | + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", | |
| 8743 | + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", | |
| 8744 | + "dev": true, | |
| 8745 | + "funding": [ | |
| 8746 | + { | |
| 8747 | + "type": "github", | |
| 8748 | + "url": "https://github.com/sponsors/feross" | |
| 8749 | + }, | |
| 8750 | + { | |
| 8751 | + "type": "patreon", | |
| 8752 | + "url": "https://www.patreon.com/feross" | |
| 8753 | + }, | |
| 8754 | + { | |
| 8755 | + "type": "consulting", | |
| 8756 | + "url": "https://feross.org/support" | |
| 8757 | + } | |
| 8758 | + ], | |
| 8759 | + "license": "MIT", | |
| 8760 | + "dependencies": { | |
| 8761 | + "queue-microtask": "^1.2.2" | |
| 8762 | + } | |
| 8763 | + }, | |
| 8764 | + "node_modules/safe-array-concat": { | |
| 8765 | + "version": "1.1.3", | |
| 8766 | + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", | |
| 8767 | + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", | |
| 8768 | + "dev": true, | |
| 8769 | + "license": "MIT", | |
| 8770 | + "dependencies": { | |
| 8771 | + "call-bind": "^1.0.8", | |
| 8772 | + "call-bound": "^1.0.2", | |
| 8773 | + "get-intrinsic": "^1.2.6", | |
| 8774 | + "has-symbols": "^1.1.0", | |
| 8775 | + "isarray": "^2.0.5" | |
| 8776 | + }, | |
| 8777 | + "engines": { | |
| 8778 | + "node": ">=0.4" | |
| 8779 | + }, | |
| 8780 | + "funding": { | |
| 8781 | + "url": "https://github.com/sponsors/ljharb" | |
| 8782 | + } | |
| 8783 | + }, | |
| 8784 | + "node_modules/safe-buffer": { | |
| 8785 | + "version": "5.1.2", | |
| 8786 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | |
| 8787 | + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", | |
| 8788 | + "license": "MIT" | |
| 8789 | + }, | |
| 8790 | + "node_modules/safe-push-apply": { | |
| 8791 | + "version": "1.0.0", | |
| 8792 | + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", | |
| 8793 | + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", | |
| 8794 | + "dev": true, | |
| 8795 | + "license": "MIT", | |
| 8796 | + "dependencies": { | |
| 8797 | + "es-errors": "^1.3.0", | |
| 8798 | + "isarray": "^2.0.5" | |
| 8799 | + }, | |
| 8800 | + "engines": { | |
| 8801 | + "node": ">= 0.4" | |
| 8802 | + }, | |
| 8803 | + "funding": { | |
| 8804 | + "url": "https://github.com/sponsors/ljharb" | |
| 8805 | + } | |
| 8806 | + }, | |
| 8807 | + "node_modules/safe-regex-test": { | |
| 8808 | + "version": "1.1.0", | |
| 8809 | + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", | |
| 8810 | + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", | |
| 8811 | + "dev": true, | |
| 8812 | + "license": "MIT", | |
| 8813 | + "dependencies": { | |
| 8814 | + "call-bound": "^1.0.2", | |
| 8815 | + "es-errors": "^1.3.0", | |
| 8816 | + "is-regex": "^1.2.1" | |
| 8817 | + }, | |
| 8818 | + "engines": { | |
| 8819 | + "node": ">= 0.4" | |
| 8820 | + }, | |
| 8821 | + "funding": { | |
| 8822 | + "url": "https://github.com/sponsors/ljharb" | |
| 8823 | + } | |
| 8824 | + }, | |
| 8825 | + "node_modules/scheduler": { | |
| 8826 | + "version": "0.23.2", | |
| 8827 | + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", | |
| 8828 | + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", | |
| 8829 | + "license": "MIT", | |
| 8830 | + "dependencies": { | |
| 8831 | + "loose-envify": "^1.1.0" | |
| 8832 | + } | |
| 8833 | + }, | |
| 8834 | + "node_modules/semver": { | |
| 8835 | + "version": "7.7.4", | |
| 8836 | + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", | |
| 8837 | + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", | |
| 8838 | + "license": "ISC", | |
| 8839 | + "bin": { | |
| 8840 | + "semver": "bin/semver.js" | |
| 8841 | + }, | |
| 8842 | + "engines": { | |
| 8843 | + "node": ">=10" | |
| 8844 | + } | |
| 8845 | + }, | |
| 8846 | + "node_modules/set-blocking": { | |
| 8847 | + "version": "2.0.0", | |
| 8848 | + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", | |
| 8849 | + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", | |
| 8850 | + "license": "ISC" | |
| 8851 | + }, | |
| 8852 | + "node_modules/set-function-length": { | |
| 8853 | + "version": "1.2.2", | |
| 8854 | + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", | |
| 8855 | + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", | |
| 8856 | + "dev": true, | |
| 8857 | + "license": "MIT", | |
| 8858 | + "dependencies": { | |
| 8859 | + "define-data-property": "^1.1.4", | |
| 8860 | + "es-errors": "^1.3.0", | |
| 8861 | + "function-bind": "^1.1.2", | |
| 8862 | + "get-intrinsic": "^1.2.4", | |
| 8863 | + "gopd": "^1.0.1", | |
| 8864 | + "has-property-descriptors": "^1.0.2" | |
| 8865 | + }, | |
| 8866 | + "engines": { | |
| 8867 | + "node": ">= 0.4" | |
| 8868 | + } | |
| 8869 | + }, | |
| 8870 | + "node_modules/set-function-name": { | |
| 8871 | + "version": "2.0.2", | |
| 8872 | + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", | |
| 8873 | + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", | |
| 8874 | + "dev": true, | |
| 8875 | + "license": "MIT", | |
| 8876 | + "dependencies": { | |
| 8877 | + "define-data-property": "^1.1.4", | |
| 8878 | + "es-errors": "^1.3.0", | |
| 8879 | + "functions-have-names": "^1.2.3", | |
| 8880 | + "has-property-descriptors": "^1.0.2" | |
| 8881 | + }, | |
| 8882 | + "engines": { | |
| 8883 | + "node": ">= 0.4" | |
| 8884 | + } | |
| 8885 | + }, | |
| 8886 | + "node_modules/set-proto": { | |
| 8887 | + "version": "1.0.0", | |
| 8888 | + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", | |
| 8889 | + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", | |
| 8890 | + "dev": true, | |
| 8891 | + "license": "MIT", | |
| 8892 | + "dependencies": { | |
| 8893 | + "dunder-proto": "^1.0.1", | |
| 8894 | + "es-errors": "^1.3.0", | |
| 8895 | + "es-object-atoms": "^1.0.0" | |
| 8896 | + }, | |
| 8897 | + "engines": { | |
| 8898 | + "node": ">= 0.4" | |
| 8899 | + } | |
| 8900 | + }, | |
| 8901 | + "node_modules/setimmediate": { | |
| 8902 | + "version": "1.0.5", | |
| 8903 | + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", | |
| 8904 | + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", | |
| 8905 | + "license": "MIT" | |
| 8906 | + }, | |
| 8907 | + "node_modules/sharp": { | |
| 8908 | + "version": "0.34.5", | |
| 8909 | + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", | |
| 8910 | + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", | |
| 8911 | + "hasInstallScript": true, | |
| 8912 | + "license": "Apache-2.0", | |
| 8913 | + "dependencies": { | |
| 8914 | + "@img/colour": "^1.0.0", | |
| 8915 | + "detect-libc": "^2.1.2", | |
| 8916 | + "semver": "^7.7.3" | |
| 8917 | + }, | |
| 8918 | + "engines": { | |
| 8919 | + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" | |
| 8920 | + }, | |
| 8921 | + "funding": { | |
| 8922 | + "url": "https://opencollective.com/libvips" | |
| 8923 | + }, | |
| 8924 | + "optionalDependencies": { | |
| 8925 | + "@img/sharp-darwin-arm64": "0.34.5", | |
| 8926 | + "@img/sharp-darwin-x64": "0.34.5", | |
| 8927 | + "@img/sharp-libvips-darwin-arm64": "1.2.4", | |
| 8928 | + "@img/sharp-libvips-darwin-x64": "1.2.4", | |
| 8929 | + "@img/sharp-libvips-linux-arm": "1.2.4", | |
| 8930 | + "@img/sharp-libvips-linux-arm64": "1.2.4", | |
| 8931 | + "@img/sharp-libvips-linux-ppc64": "1.2.4", | |
| 8932 | + "@img/sharp-libvips-linux-riscv64": "1.2.4", | |
| 8933 | + "@img/sharp-libvips-linux-s390x": "1.2.4", | |
| 8934 | + "@img/sharp-libvips-linux-x64": "1.2.4", | |
| 8935 | + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", | |
| 8936 | + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", | |
| 8937 | + "@img/sharp-linux-arm": "0.34.5", | |
| 8938 | + "@img/sharp-linux-arm64": "0.34.5", | |
| 8939 | + "@img/sharp-linux-ppc64": "0.34.5", | |
| 8940 | + "@img/sharp-linux-riscv64": "0.34.5", | |
| 8941 | + "@img/sharp-linux-s390x": "0.34.5", | |
| 8942 | + "@img/sharp-linux-x64": "0.34.5", | |
| 8943 | + "@img/sharp-linuxmusl-arm64": "0.34.5", | |
| 8944 | + "@img/sharp-linuxmusl-x64": "0.34.5", | |
| 8945 | + "@img/sharp-wasm32": "0.34.5", | |
| 8946 | + "@img/sharp-win32-arm64": "0.34.5", | |
| 8947 | + "@img/sharp-win32-ia32": "0.34.5", | |
| 8948 | + "@img/sharp-win32-x64": "0.34.5" | |
| 8949 | + } | |
| 8950 | + }, | |
| 8951 | + "node_modules/shebang-command": { | |
| 8952 | + "version": "2.0.0", | |
| 8953 | + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", | |
| 8954 | + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", | |
| 8955 | + "license": "MIT", | |
| 8956 | + "dependencies": { | |
| 8957 | + "shebang-regex": "^3.0.0" | |
| 8958 | + }, | |
| 8959 | + "engines": { | |
| 8960 | + "node": ">=8" | |
| 8961 | + } | |
| 8962 | + }, | |
| 8963 | + "node_modules/shebang-regex": { | |
| 8964 | + "version": "3.0.0", | |
| 8965 | + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", | |
| 8966 | + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", | |
| 8967 | + "license": "MIT", | |
| 8968 | + "engines": { | |
| 8969 | + "node": ">=8" | |
| 8970 | + } | |
| 8971 | + }, | |
| 8972 | + "node_modules/side-channel": { | |
| 8973 | + "version": "1.1.0", | |
| 8974 | + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", | |
| 8975 | + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", | |
| 8976 | + "dev": true, | |
| 8977 | + "license": "MIT", | |
| 8978 | + "dependencies": { | |
| 8979 | + "es-errors": "^1.3.0", | |
| 8980 | + "object-inspect": "^1.13.3", | |
| 8981 | + "side-channel-list": "^1.0.0", | |
| 8982 | + "side-channel-map": "^1.0.1", | |
| 8983 | + "side-channel-weakmap": "^1.0.2" | |
| 8984 | + }, | |
| 8985 | + "engines": { | |
| 8986 | + "node": ">= 0.4" | |
| 8987 | + }, | |
| 8988 | + "funding": { | |
| 8989 | + "url": "https://github.com/sponsors/ljharb" | |
| 8990 | + } | |
| 8991 | + }, | |
| 8992 | + "node_modules/side-channel-list": { | |
| 8993 | + "version": "1.0.0", | |
| 8994 | + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", | |
| 8995 | + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", | |
| 8996 | + "dev": true, | |
| 8997 | + "license": "MIT", | |
| 8998 | + "dependencies": { | |
| 8999 | + "es-errors": "^1.3.0", | |
| 9000 | + "object-inspect": "^1.13.3" | |
| 9001 | + }, | |
| 9002 | + "engines": { | |
| 9003 | + "node": ">= 0.4" | |
| 9004 | + }, | |
| 9005 | + "funding": { | |
| 9006 | + "url": "https://github.com/sponsors/ljharb" | |
| 9007 | + } | |
| 9008 | + }, | |
| 9009 | + "node_modules/side-channel-map": { | |
| 9010 | + "version": "1.0.1", | |
| 9011 | + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", | |
| 9012 | + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", | |
| 9013 | + "dev": true, | |
| 9014 | + "license": "MIT", | |
| 9015 | + "dependencies": { | |
| 9016 | + "call-bound": "^1.0.2", | |
| 9017 | + "es-errors": "^1.3.0", | |
| 9018 | + "get-intrinsic": "^1.2.5", | |
| 9019 | + "object-inspect": "^1.13.3" | |
| 9020 | + }, | |
| 9021 | + "engines": { | |
| 9022 | + "node": ">= 0.4" | |
| 9023 | + }, | |
| 9024 | + "funding": { | |
| 9025 | + "url": "https://github.com/sponsors/ljharb" | |
| 9026 | + } | |
| 9027 | + }, | |
| 9028 | + "node_modules/side-channel-weakmap": { | |
| 9029 | + "version": "1.0.2", | |
| 9030 | + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", | |
| 9031 | + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", | |
| 9032 | + "dev": true, | |
| 9033 | + "license": "MIT", | |
| 9034 | + "dependencies": { | |
| 9035 | + "call-bound": "^1.0.2", | |
| 9036 | + "es-errors": "^1.3.0", | |
| 9037 | + "get-intrinsic": "^1.2.5", | |
| 9038 | + "object-inspect": "^1.13.3", | |
| 9039 | + "side-channel-map": "^1.0.1" | |
| 9040 | + }, | |
| 9041 | + "engines": { | |
| 9042 | + "node": ">= 0.4" | |
| 9043 | + }, | |
| 9044 | + "funding": { | |
| 9045 | + "url": "https://github.com/sponsors/ljharb" | |
| 9046 | + } | |
| 9047 | + }, | |
| 9048 | + "node_modules/signal-exit": { | |
| 9049 | + "version": "4.1.0", | |
| 9050 | + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", | |
| 9051 | + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", | |
| 9052 | + "license": "ISC", | |
| 9053 | + "engines": { | |
| 9054 | + "node": ">=14" | |
| 9055 | + }, | |
| 9056 | + "funding": { | |
| 9057 | + "url": "https://github.com/sponsors/isaacs" | |
| 9058 | + } | |
| 9059 | + }, | |
| 9060 | + "node_modules/source-map-js": { | |
| 9061 | + "version": "1.2.1", | |
| 9062 | + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", | |
| 9063 | + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", | |
| 9064 | + "license": "BSD-3-Clause", | |
| 9065 | + "engines": { | |
| 9066 | + "node": ">=0.10.0" | |
| 9067 | + } | |
| 9068 | + }, | |
| 9069 | + "node_modules/space-separated-tokens": { | |
| 9070 | + "version": "2.0.2", | |
| 9071 | + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", | |
| 9072 | + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", | |
| 9073 | + "license": "MIT", | |
| 9074 | + "funding": { | |
| 9075 | + "type": "github", | |
| 9076 | + "url": "https://github.com/sponsors/wooorm" | |
| 9077 | + } | |
| 9078 | + }, | |
| 9079 | + "node_modules/stable-hash": { | |
| 9080 | + "version": "0.0.5", | |
| 9081 | + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", | |
| 9082 | + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", | |
| 9083 | + "dev": true, | |
| 9084 | + "license": "MIT" | |
| 9085 | + }, | |
| 9086 | + "node_modules/stop-iteration-iterator": { | |
| 9087 | + "version": "1.1.0", | |
| 9088 | + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", | |
| 9089 | + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", | |
| 9090 | + "dev": true, | |
| 9091 | + "license": "MIT", | |
| 9092 | + "dependencies": { | |
| 9093 | + "es-errors": "^1.3.0", | |
| 9094 | + "internal-slot": "^1.1.0" | |
| 9095 | + }, | |
| 9096 | + "engines": { | |
| 9097 | + "node": ">= 0.4" | |
| 9098 | + } | |
| 9099 | + }, | |
| 9100 | + "node_modules/streamsearch": { | |
| 9101 | + "version": "1.1.0", | |
| 9102 | + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", | |
| 9103 | + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", | |
| 9104 | + "engines": { | |
| 9105 | + "node": ">=10.0.0" | |
| 9106 | + } | |
| 9107 | + }, | |
| 9108 | + "node_modules/streamx": { | |
| 9109 | + "version": "2.23.0", | |
| 9110 | + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", | |
| 9111 | + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", | |
| 9112 | + "license": "MIT", | |
| 9113 | + "dependencies": { | |
| 9114 | + "events-universal": "^1.0.0", | |
| 9115 | + "fast-fifo": "^1.3.2", | |
| 9116 | + "text-decoder": "^1.1.0" | |
| 9117 | + } | |
| 9118 | + }, | |
| 9119 | + "node_modules/string_decoder": { | |
| 9120 | + "version": "1.1.1", | |
| 9121 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | |
| 9122 | + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | |
| 9123 | + "license": "MIT", | |
| 9124 | + "dependencies": { | |
| 9125 | + "safe-buffer": "~5.1.0" | |
| 9126 | + } | |
| 9127 | + }, | |
| 9128 | + "node_modules/string-width": { | |
| 9129 | + "version": "5.1.2", | |
| 9130 | + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", | |
| 9131 | + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", | |
| 9132 | + "license": "MIT", | |
| 9133 | + "dependencies": { | |
| 9134 | + "eastasianwidth": "^0.2.0", | |
| 9135 | + "emoji-regex": "^9.2.2", | |
| 9136 | + "strip-ansi": "^7.0.1" | |
| 9137 | + }, | |
| 9138 | + "engines": { | |
| 9139 | + "node": ">=12" | |
| 9140 | + }, | |
| 9141 | + "funding": { | |
| 9142 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 9143 | + } | |
| 9144 | + }, | |
| 9145 | + "node_modules/string-width-cjs": { | |
| 9146 | + "name": "string-width", | |
| 9147 | + "version": "4.2.3", | |
| 9148 | + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | |
| 9149 | + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | |
| 9150 | + "license": "MIT", | |
| 9151 | + "dependencies": { | |
| 9152 | + "emoji-regex": "^8.0.0", | |
| 9153 | + "is-fullwidth-code-point": "^3.0.0", | |
| 9154 | + "strip-ansi": "^6.0.1" | |
| 9155 | + }, | |
| 9156 | + "engines": { | |
| 9157 | + "node": ">=8" | |
| 9158 | + } | |
| 9159 | + }, | |
| 9160 | + "node_modules/string-width-cjs/node_modules/emoji-regex": { | |
| 9161 | + "version": "8.0.0", | |
| 9162 | + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | |
| 9163 | + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", | |
| 9164 | + "license": "MIT" | |
| 9165 | + }, | |
| 9166 | + "node_modules/string-width/node_modules/ansi-regex": { | |
| 9167 | + "version": "6.2.2", | |
| 9168 | + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", | |
| 9169 | + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", | |
| 9170 | + "license": "MIT", | |
| 9171 | + "engines": { | |
| 9172 | + "node": ">=12" | |
| 9173 | + }, | |
| 9174 | + "funding": { | |
| 9175 | + "url": "https://github.com/chalk/ansi-regex?sponsor=1" | |
| 9176 | + } | |
| 9177 | + }, | |
| 9178 | + "node_modules/string-width/node_modules/strip-ansi": { | |
| 9179 | + "version": "7.1.2", | |
| 9180 | + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", | |
| 9181 | + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", | |
| 9182 | + "license": "MIT", | |
| 9183 | + "dependencies": { | |
| 9184 | + "ansi-regex": "^6.0.1" | |
| 9185 | + }, | |
| 9186 | + "engines": { | |
| 9187 | + "node": ">=12" | |
| 9188 | + }, | |
| 9189 | + "funding": { | |
| 9190 | + "url": "https://github.com/chalk/strip-ansi?sponsor=1" | |
| 9191 | + } | |
| 9192 | + }, | |
| 9193 | + "node_modules/string.prototype.includes": { | |
| 9194 | + "version": "2.0.1", | |
| 9195 | + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", | |
| 9196 | + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", | |
| 9197 | + "dev": true, | |
| 9198 | + "license": "MIT", | |
| 9199 | + "dependencies": { | |
| 9200 | + "call-bind": "^1.0.7", | |
| 9201 | + "define-properties": "^1.2.1", | |
| 9202 | + "es-abstract": "^1.23.3" | |
| 9203 | + }, | |
| 9204 | + "engines": { | |
| 9205 | + "node": ">= 0.4" | |
| 9206 | + } | |
| 9207 | + }, | |
| 9208 | + "node_modules/string.prototype.matchall": { | |
| 9209 | + "version": "4.0.12", | |
| 9210 | + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", | |
| 9211 | + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", | |
| 9212 | + "dev": true, | |
| 9213 | + "license": "MIT", | |
| 9214 | + "dependencies": { | |
| 9215 | + "call-bind": "^1.0.8", | |
| 9216 | + "call-bound": "^1.0.3", | |
| 9217 | + "define-properties": "^1.2.1", | |
| 9218 | + "es-abstract": "^1.23.6", | |
| 9219 | + "es-errors": "^1.3.0", | |
| 9220 | + "es-object-atoms": "^1.0.0", | |
| 9221 | + "get-intrinsic": "^1.2.6", | |
| 9222 | + "gopd": "^1.2.0", | |
| 9223 | + "has-symbols": "^1.1.0", | |
| 9224 | + "internal-slot": "^1.1.0", | |
| 9225 | + "regexp.prototype.flags": "^1.5.3", | |
| 9226 | + "set-function-name": "^2.0.2", | |
| 9227 | + "side-channel": "^1.1.0" | |
| 9228 | + }, | |
| 9229 | + "engines": { | |
| 9230 | + "node": ">= 0.4" | |
| 9231 | + }, | |
| 9232 | + "funding": { | |
| 9233 | + "url": "https://github.com/sponsors/ljharb" | |
| 9234 | + } | |
| 9235 | + }, | |
| 9236 | + "node_modules/string.prototype.repeat": { | |
| 9237 | + "version": "1.0.0", | |
| 9238 | + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", | |
| 9239 | + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", | |
| 9240 | + "dev": true, | |
| 9241 | + "license": "MIT", | |
| 9242 | + "dependencies": { | |
| 9243 | + "define-properties": "^1.1.3", | |
| 9244 | + "es-abstract": "^1.17.5" | |
| 9245 | + } | |
| 9246 | + }, | |
| 9247 | + "node_modules/string.prototype.trim": { | |
| 9248 | + "version": "1.2.10", | |
| 9249 | + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", | |
| 9250 | + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", | |
| 9251 | + "dev": true, | |
| 9252 | + "license": "MIT", | |
| 9253 | + "dependencies": { | |
| 9254 | + "call-bind": "^1.0.8", | |
| 9255 | + "call-bound": "^1.0.2", | |
| 9256 | + "define-data-property": "^1.1.4", | |
| 9257 | + "define-properties": "^1.2.1", | |
| 9258 | + "es-abstract": "^1.23.5", | |
| 9259 | + "es-object-atoms": "^1.0.0", | |
| 9260 | + "has-property-descriptors": "^1.0.2" | |
| 9261 | + }, | |
| 9262 | + "engines": { | |
| 9263 | + "node": ">= 0.4" | |
| 9264 | + }, | |
| 9265 | + "funding": { | |
| 9266 | + "url": "https://github.com/sponsors/ljharb" | |
| 9267 | + } | |
| 9268 | + }, | |
| 9269 | + "node_modules/string.prototype.trimend": { | |
| 9270 | + "version": "1.0.9", | |
| 9271 | + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", | |
| 9272 | + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", | |
| 9273 | + "dev": true, | |
| 9274 | + "license": "MIT", | |
| 9275 | + "dependencies": { | |
| 9276 | + "call-bind": "^1.0.8", | |
| 9277 | + "call-bound": "^1.0.2", | |
| 9278 | + "define-properties": "^1.2.1", | |
| 9279 | + "es-object-atoms": "^1.0.0" | |
| 9280 | + }, | |
| 9281 | + "engines": { | |
| 9282 | + "node": ">= 0.4" | |
| 9283 | + }, | |
| 9284 | + "funding": { | |
| 9285 | + "url": "https://github.com/sponsors/ljharb" | |
| 9286 | + } | |
| 9287 | + }, | |
| 9288 | + "node_modules/string.prototype.trimstart": { | |
| 9289 | + "version": "1.0.8", | |
| 9290 | + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", | |
| 9291 | + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", | |
| 9292 | + "dev": true, | |
| 9293 | + "license": "MIT", | |
| 9294 | + "dependencies": { | |
| 9295 | + "call-bind": "^1.0.7", | |
| 9296 | + "define-properties": "^1.2.1", | |
| 9297 | + "es-object-atoms": "^1.0.0" | |
| 9298 | + }, | |
| 9299 | + "engines": { | |
| 9300 | + "node": ">= 0.4" | |
| 9301 | + }, | |
| 9302 | + "funding": { | |
| 9303 | + "url": "https://github.com/sponsors/ljharb" | |
| 9304 | + } | |
| 9305 | + }, | |
| 9306 | + "node_modules/stringify-entities": { | |
| 9307 | + "version": "4.0.4", | |
| 9308 | + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", | |
| 9309 | + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", | |
| 9310 | + "license": "MIT", | |
| 9311 | + "dependencies": { | |
| 9312 | + "character-entities-html4": "^2.0.0", | |
| 9313 | + "character-entities-legacy": "^3.0.0" | |
| 9314 | + }, | |
| 9315 | + "funding": { | |
| 9316 | + "type": "github", | |
| 9317 | + "url": "https://github.com/sponsors/wooorm" | |
| 9318 | + } | |
| 9319 | + }, | |
| 9320 | + "node_modules/strip-ansi": { | |
| 9321 | + "version": "6.0.1", | |
| 9322 | + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | |
| 9323 | + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | |
| 9324 | + "license": "MIT", | |
| 9325 | + "dependencies": { | |
| 9326 | + "ansi-regex": "^5.0.1" | |
| 9327 | + }, | |
| 9328 | + "engines": { | |
| 9329 | + "node": ">=8" | |
| 9330 | + } | |
| 9331 | + }, | |
| 9332 | + "node_modules/strip-ansi-cjs": { | |
| 9333 | + "name": "strip-ansi", | |
| 9334 | + "version": "6.0.1", | |
| 9335 | + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | |
| 9336 | + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | |
| 9337 | + "license": "MIT", | |
| 9338 | + "dependencies": { | |
| 9339 | + "ansi-regex": "^5.0.1" | |
| 9340 | + }, | |
| 9341 | + "engines": { | |
| 9342 | + "node": ">=8" | |
| 9343 | + } | |
| 9344 | + }, | |
| 9345 | + "node_modules/strip-bom": { | |
| 9346 | + "version": "3.0.0", | |
| 9347 | + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", | |
| 9348 | + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", | |
| 9349 | + "dev": true, | |
| 9350 | + "license": "MIT", | |
| 9351 | + "engines": { | |
| 9352 | + "node": ">=4" | |
| 9353 | + } | |
| 9354 | + }, | |
| 9355 | + "node_modules/strip-json-comments": { | |
| 9356 | + "version": "3.1.1", | |
| 9357 | + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", | |
| 9358 | + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", | |
| 9359 | + "dev": true, | |
| 9360 | + "license": "MIT", | |
| 9361 | + "engines": { | |
| 9362 | + "node": ">=8" | |
| 9363 | + }, | |
| 9364 | + "funding": { | |
| 9365 | + "url": "https://github.com/sponsors/sindresorhus" | |
| 9366 | + } | |
| 9367 | + }, | |
| 9368 | + "node_modules/style-mod": { | |
| 9369 | + "version": "4.1.3", | |
| 9370 | + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", | |
| 9371 | + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", | |
| 9372 | + "license": "MIT" | |
| 9373 | + }, | |
| 9374 | + "node_modules/style-to-js": { | |
| 9375 | + "version": "1.1.21", | |
| 9376 | + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", | |
| 9377 | + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", | |
| 9378 | + "license": "MIT", | |
| 9379 | + "dependencies": { | |
| 9380 | + "style-to-object": "1.0.14" | |
| 9381 | + } | |
| 9382 | + }, | |
| 9383 | + "node_modules/style-to-object": { | |
| 9384 | + "version": "1.0.14", | |
| 9385 | + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", | |
| 9386 | + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", | |
| 9387 | + "license": "MIT", | |
| 9388 | + "dependencies": { | |
| 9389 | + "inline-style-parser": "0.2.7" | |
| 9390 | + } | |
| 9391 | + }, | |
| 9392 | + "node_modules/styled-jsx": { | |
| 9393 | + "version": "5.1.1", | |
| 9394 | + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", | |
| 9395 | + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", | |
| 9396 | + "license": "MIT", | |
| 9397 | + "dependencies": { | |
| 9398 | + "client-only": "0.0.1" | |
| 9399 | + }, | |
| 9400 | + "engines": { | |
| 9401 | + "node": ">= 12.0.0" | |
| 9402 | + }, | |
| 9403 | + "peerDependencies": { | |
| 9404 | + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" | |
| 9405 | + }, | |
| 9406 | + "peerDependenciesMeta": { | |
| 9407 | + "@babel/core": { | |
| 9408 | + "optional": true | |
| 9409 | + }, | |
| 9410 | + "babel-plugin-macros": { | |
| 9411 | + "optional": true | |
| 9412 | + } | |
| 9413 | + } | |
| 9414 | + }, | |
| 9415 | + "node_modules/sucrase": { | |
| 9416 | + "version": "3.35.1", | |
| 9417 | + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", | |
| 9418 | + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", | |
| 9419 | + "dev": true, | |
| 9420 | + "license": "MIT", | |
| 9421 | + "dependencies": { | |
| 9422 | + "@jridgewell/gen-mapping": "^0.3.2", | |
| 9423 | + "commander": "^4.0.0", | |
| 9424 | + "lines-and-columns": "^1.1.6", | |
| 9425 | + "mz": "^2.7.0", | |
| 9426 | + "pirates": "^4.0.1", | |
| 9427 | + "tinyglobby": "^0.2.11", | |
| 9428 | + "ts-interface-checker": "^0.1.9" | |
| 9429 | + }, | |
| 9430 | + "bin": { | |
| 9431 | + "sucrase": "bin/sucrase", | |
| 9432 | + "sucrase-node": "bin/sucrase-node" | |
| 9433 | + }, | |
| 9434 | + "engines": { | |
| 9435 | + "node": ">=16 || 14 >=14.17" | |
| 9436 | + } | |
| 9437 | + }, | |
| 9438 | + "node_modules/supports-color": { | |
| 9439 | + "version": "7.2.0", | |
| 9440 | + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", | |
| 9441 | + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", | |
| 9442 | + "dev": true, | |
| 9443 | + "license": "MIT", | |
| 9444 | + "dependencies": { | |
| 9445 | + "has-flag": "^4.0.0" | |
| 9446 | + }, | |
| 9447 | + "engines": { | |
| 9448 | + "node": ">=8" | |
| 9449 | + } | |
| 9450 | + }, | |
| 9451 | + "node_modules/supports-preserve-symlinks-flag": { | |
| 9452 | + "version": "1.0.0", | |
| 9453 | + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", | |
| 9454 | + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", | |
| 9455 | + "dev": true, | |
| 9456 | + "license": "MIT", | |
| 9457 | + "engines": { | |
| 9458 | + "node": ">= 0.4" | |
| 9459 | + }, | |
| 9460 | + "funding": { | |
| 9461 | + "url": "https://github.com/sponsors/ljharb" | |
| 9462 | + } | |
| 9463 | + }, | |
| 9464 | + "node_modules/tailwindcss": { | |
| 9465 | + "version": "3.4.19", | |
| 9466 | + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", | |
| 9467 | + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", | |
| 9468 | + "dev": true, | |
| 9469 | + "license": "MIT", | |
| 9470 | + "dependencies": { | |
| 9471 | + "@alloc/quick-lru": "^5.2.0", | |
| 9472 | + "arg": "^5.0.2", | |
| 9473 | + "chokidar": "^3.6.0", | |
| 9474 | + "didyoumean": "^1.2.2", | |
| 9475 | + "dlv": "^1.1.3", | |
| 9476 | + "fast-glob": "^3.3.2", | |
| 9477 | + "glob-parent": "^6.0.2", | |
| 9478 | + "is-glob": "^4.0.3", | |
| 9479 | + "jiti": "^1.21.7", | |
| 9480 | + "lilconfig": "^3.1.3", | |
| 9481 | + "micromatch": "^4.0.8", | |
| 9482 | + "normalize-path": "^3.0.0", | |
| 9483 | + "object-hash": "^3.0.0", | |
| 9484 | + "picocolors": "^1.1.1", | |
| 9485 | + "postcss": "^8.4.47", | |
| 9486 | + "postcss-import": "^15.1.0", | |
| 9487 | + "postcss-js": "^4.0.1", | |
| 9488 | + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", | |
| 9489 | + "postcss-nested": "^6.2.0", | |
| 9490 | + "postcss-selector-parser": "^6.1.2", | |
| 9491 | + "resolve": "^1.22.8", | |
| 9492 | + "sucrase": "^3.35.0" | |
| 9493 | + }, | |
| 9494 | + "bin": { | |
| 9495 | + "tailwind": "lib/cli.js", | |
| 9496 | + "tailwindcss": "lib/cli.js" | |
| 9497 | + }, | |
| 9498 | + "engines": { | |
| 9499 | + "node": ">=14.0.0" | |
| 9500 | + } | |
| 9501 | + }, | |
| 9502 | + "node_modules/tar-stream": { | |
| 9503 | + "version": "3.1.7", | |
| 9504 | + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", | |
| 9505 | + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", | |
| 9506 | + "license": "MIT", | |
| 9507 | + "dependencies": { | |
| 9508 | + "b4a": "^1.6.4", | |
| 9509 | + "fast-fifo": "^1.2.0", | |
| 9510 | + "streamx": "^2.15.0" | |
| 9511 | + } | |
| 9512 | + }, | |
| 9513 | + "node_modules/tar-stream/node_modules/b4a": { | |
| 9514 | + "version": "1.7.5", | |
| 9515 | + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.5.tgz", | |
| 9516 | + "integrity": "sha512-iEsKNwDh1wiWTps1/hdkNdmBgDlDVZP5U57ZVOlt+dNFqpc/lpPouCIxZw+DYBgc4P9NDfIZMPNR4CHNhzwLIA==", | |
| 9517 | + "license": "Apache-2.0", | |
| 9518 | + "peerDependencies": { | |
| 9519 | + "react-native-b4a": "*" | |
| 9520 | + }, | |
| 9521 | + "peerDependenciesMeta": { | |
| 9522 | + "react-native-b4a": { | |
| 9523 | + "optional": true | |
| 9524 | + } | |
| 9525 | + } | |
| 9526 | + }, | |
| 9527 | + "node_modules/text-decoder": { | |
| 9528 | + "version": "1.2.7", | |
| 9529 | + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", | |
| 9530 | + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", | |
| 9531 | + "license": "Apache-2.0", | |
| 9532 | + "dependencies": { | |
| 9533 | + "b4a": "^1.6.4" | |
| 9534 | + } | |
| 9535 | + }, | |
| 9536 | + "node_modules/text-decoder/node_modules/b4a": { | |
| 9537 | + "version": "1.7.5", | |
| 9538 | + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.5.tgz", | |
| 9539 | + "integrity": "sha512-iEsKNwDh1wiWTps1/hdkNdmBgDlDVZP5U57ZVOlt+dNFqpc/lpPouCIxZw+DYBgc4P9NDfIZMPNR4CHNhzwLIA==", | |
| 9540 | + "license": "Apache-2.0", | |
| 9541 | + "peerDependencies": { | |
| 9542 | + "react-native-b4a": "*" | |
| 9543 | + }, | |
| 9544 | + "peerDependenciesMeta": { | |
Diff truncated — file too large.