(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

new features, enhancements, and bug fixes

scanash00 a38432a5 7103fe98

+3126 -12254
-22
.github/workflows/deploy.yml
··· 1 - name: Deploy 2 - 3 - on: 4 - workflow_run: 5 - workflows: ["Build and Publish Docker Image"] 6 - types: 7 - - completed 8 - branches: 9 - - main 10 - 11 - jobs: 12 - deploy: 13 - runs-on: blacksmith-2vcpu-ubuntu-2404 14 - if: ${{ github.event.workflow_run.conclusion == 'success' }} 15 - steps: 16 - - name: Deploy to Dokku 17 - uses: appleboy/ssh-action@master 18 - with: 19 - host: ${{ secrets.DOKKU_HOST }} 20 - username: deploy 21 - key: ${{ secrets.DEPLOY_KEY }} 22 - script: echo deploy
+59 -21
backend/cmd/server/main.go
··· 5 5 "net/http" 6 6 "os" 7 7 "os/signal" 8 + "strings" 8 9 "syscall" 9 10 "time" 10 11 ··· 27 28 func main() { 28 29 godotenv.Load("../.env", ".env") 29 30 30 - database, err := db.New(getEnv("DATABASE_URL", "margin.db")) 31 + dsn := os.Getenv("DATABASE_URL") 32 + if dsn == "" { 33 + logger.Fatal("DATABASE_URL environment variable is required") 34 + } 35 + database, err := db.New(dsn) 31 36 if err != nil { 32 37 logger.Fatal("Failed to connect to database: %v", err) 33 38 } ··· 70 75 firehose.RelayURL = getEnv("BLOCK_RELAY_URL", "wss://jetstream2.us-east.bsky.network/subscribe") 71 76 logger.Info("Firehose URL: %s", firehose.RelayURL) 72 77 78 + backfillCtx, backfillCancel := context.WithCancel(context.Background()) 79 + defer backfillCancel() 80 + 73 81 if recService.IsEnabled() { 74 82 ingester.SetOnAnnotation(recService.OnAnnotation) 75 83 ingester.SetOnDocument(recService.OnDocument) 76 84 77 - go func() { 78 - logger.Info("Starting recommendation backfill...") 79 - if err := recService.BackfillDocumentEmbeddings(200); err != nil { 80 - logger.Error("Document embedding backfill error: %v", err) 81 - } 82 - annCount, err := recService.BackfillAnnotationEmbeddings(200) 83 - if err != nil { 84 - logger.Error("Annotation embedding backfill error: %v", err) 85 - } 86 - hlCount, err := recService.BackfillHighlightEmbeddings(200) 87 - if err != nil { 88 - logger.Error("Highlight embedding backfill error: %v", err) 89 - } 90 - profileCount, err := recService.RebuildAllProfiles() 91 - if err != nil { 92 - logger.Error("Profile rebuild error: %v", err) 93 - } 94 - logger.Info("Recommendation backfill complete (annotations: %d, highlights: %d, profiles: %d)", annCount, hlCount, profileCount) 95 - }() 85 + if getEnv("DISABLE_BACKFILL", "") == "" { 86 + go func() { 87 + time.Sleep(5 * time.Second) 88 + select { 89 + case <-backfillCtx.Done(): 90 + return 91 + default: 92 + } 93 + logger.Info("Starting recommendation backfill...") 94 + if err := recService.BackfillDocumentEmbeddings(200); err != nil { 95 + logger.Error("Document embedding backfill error: %v", err) 96 + } 97 + if backfillCtx.Err() != nil { 98 + return 99 + } 100 + annCount, err := recService.BackfillAnnotationEmbeddings(200) 101 + if err != nil { 102 + logger.Error("Annotation embedding backfill error: %v", err) 103 + } 104 + if backfillCtx.Err() != nil { 105 + return 106 + } 107 + hlCount, err := recService.BackfillHighlightEmbeddings(200) 108 + if err != nil { 109 + logger.Error("Highlight embedding backfill error: %v", err) 110 + } 111 + if backfillCtx.Err() != nil { 112 + return 113 + } 114 + profileCount, err := recService.RebuildAllProfiles() 115 + if err != nil { 116 + logger.Error("Profile rebuild error: %v", err) 117 + } 118 + logger.Info("Recommendation backfill complete (annotations: %d, highlights: %d, profiles: %d)", annCount, hlCount, profileCount) 119 + }() 120 + } else { 121 + logger.Info("Recommendation backfill disabled (DISABLE_BACKFILL is set)") 122 + } 96 123 } 97 124 98 125 go func() { ··· 111 138 r.Use(middleware.Throttle(100)) 112 139 113 140 r.Use(cors.Handler(cors.Options{ 114 - AllowedOrigins: []string{"https://*", "http://*", "chrome-extension://*"}, 141 + AllowOriginFunc: func(r *http.Request, origin string) bool { 142 + if strings.HasPrefix(origin, "chrome-extension://") || 143 + strings.HasPrefix(origin, "moz-extension://") || 144 + strings.HasPrefix(origin, "safari-web-extension://") { 145 + return true 146 + } 147 + if baseURL := os.Getenv("BASE_URL"); baseURL != "" { 148 + return origin == baseURL 149 + } 150 + return false 151 + }, 115 152 AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 116 153 AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Session-Token"}, 117 154 ExposedHeaders: []string{"Link"}, ··· 174 211 <-quit 175 212 176 213 logger.Infoln("Shutting down server...") 214 + backfillCancel() 177 215 ingester.Stop() 178 216 179 217 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
-1
backend/go.mod
··· 11 11 github.com/ipfs/go-cid v0.6.0 12 12 github.com/joho/godotenv v1.5.1 13 13 github.com/lib/pq v1.10.9 14 - github.com/mattn/go-sqlite3 v1.14.22 15 14 github.com/multiformats/go-multihash v0.2.3 16 15 ) 17 16
-2
backend/go.sum
··· 21 21 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 22 22 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 23 23 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 24 - github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= 25 - github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= 26 24 github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= 27 25 github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= 28 26 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
+3 -3
backend/internal/api/apikey.go
··· 482 482 } 483 483 484 484 func (h *APIKeyHandler) getSessionByDID(did string) (*SessionData, error) { 485 - rows, err := h.db.Query(h.db.Rebind(` 485 + rows, err := h.db.Query(` 486 486 SELECT id, did, handle, access_token, refresh_token, COALESCE(dpop_key, '') 487 487 FROM sessions 488 - WHERE did = ? AND expires_at > ? 488 + WHERE did = $1 AND expires_at > $2 489 489 ORDER BY created_at DESC 490 490 LIMIT 1 491 - `), did, time.Now()) 491 + `, did, time.Now()) 492 492 if err != nil { 493 493 return nil, err 494 494 }
+184 -16
backend/internal/api/handler.go
··· 10 10 "sort" 11 11 "strconv" 12 12 "strings" 13 + "sync" 13 14 "time" 14 15 15 16 "github.com/go-chi/chi/v5" ··· 22 23 "margin.at/internal/xrpc" 23 24 ) 24 25 26 + type urlMetaCacheEntry struct { 27 + data map[string]string 28 + expiresAt time.Time 29 + } 30 + 31 + type urlMetaCache struct { 32 + mu sync.RWMutex 33 + entries map[string]urlMetaCacheEntry 34 + inflight sync.Map 35 + } 36 + 37 + type singleflight struct { 38 + wg sync.WaitGroup 39 + data map[string]string 40 + err error 41 + } 42 + 43 + func newURLMetaCache() *urlMetaCache { 44 + c := &urlMetaCache{entries: make(map[string]urlMetaCacheEntry)} 45 + go c.evictLoop() 46 + return c 47 + } 48 + 49 + func (c *urlMetaCache) get(key string) (map[string]string, bool) { 50 + c.mu.RLock() 51 + defer c.mu.RUnlock() 52 + e, ok := c.entries[key] 53 + if !ok || time.Now().After(e.expiresAt) { 54 + return nil, false 55 + } 56 + return e.data, true 57 + } 58 + 59 + func (c *urlMetaCache) set(key string, data map[string]string, ttl time.Duration) { 60 + c.mu.Lock() 61 + defer c.mu.Unlock() 62 + c.entries[key] = urlMetaCacheEntry{data: data, expiresAt: time.Now().Add(ttl)} 63 + } 64 + 65 + func (c *urlMetaCache) evictLoop() { 66 + ticker := time.NewTicker(5 * time.Minute) 67 + for range ticker.C { 68 + c.mu.Lock() 69 + now := time.Now() 70 + for k, e := range c.entries { 71 + if now.After(e.expiresAt) { 72 + delete(c.entries, k) 73 + } 74 + } 75 + c.mu.Unlock() 76 + } 77 + } 78 + 25 79 type Handler struct { 26 80 db *db.DB 27 81 annotationService *AnnotationService ··· 30 84 syncService *internal_sync.Service 31 85 moderation *ModerationHandler 32 86 recommendations *recommendations.Service 87 + metaCache *urlMetaCache 88 + metaSem chan struct{} 33 89 } 34 90 35 91 func NewHandler(database *db.DB, annotationService *AnnotationService, refresher *TokenRefresher, syncService *internal_sync.Service, recService *recommendations.Service) *Handler { ··· 41 97 syncService: syncService, 42 98 moderation: NewModerationHandler(database, refresher), 43 99 recommendations: recService, 100 + metaCache: newURLMetaCache(), 101 + metaSem: make(chan struct{}, 5), 44 102 } 45 103 } 46 104 ··· 346 404 } 347 405 } 348 406 349 - authAnnos, _ := hydrateAnnotations(h.db, annotations, viewerDID) 350 - authHighs, _ := hydrateHighlights(h.db, highlights, viewerDID) 351 - authBooks, _ := hydrateBookmarks(h.db, bookmarks, viewerDID) 407 + allDIDs := make(map[string]bool) 408 + for _, a := range annotations { 409 + allDIDs[a.AuthorDID] = true 410 + } 411 + for _, h := range highlights { 412 + allDIDs[h.AuthorDID] = true 413 + } 414 + for _, b := range bookmarks { 415 + allDIDs[b.AuthorDID] = true 416 + } 417 + for _, ci := range collectionItems { 418 + allDIDs[ci.AuthorDID] = true 419 + } 420 + didSlice := make([]string, 0, len(allDIDs)) 421 + for did := range allDIDs { 422 + didSlice = append(didSlice, did) 423 + } 424 + profiles := fetchProfilesForDIDs(h.db, didSlice) 425 + shared := &hydrationData{profiles: profiles} 426 + 427 + var ( 428 + authAnnos []APIAnnotation 429 + authHighs []APIHighlight 430 + authBooks []APIBookmark 431 + authCollectionItems []APICollectionItem 432 + wg sync.WaitGroup 433 + ) 434 + 435 + wg.Add(3) 436 + go func() { 437 + defer wg.Done() 438 + authAnnos, _ = hydrateAnnotationsWithData(h.db, annotations, viewerDID, shared) 439 + }() 440 + go func() { 441 + defer wg.Done() 442 + authHighs, _ = hydrateHighlightsWithData(h.db, highlights, viewerDID, shared) 443 + }() 444 + go func() { 445 + defer wg.Done() 446 + authBooks, _ = hydrateBookmarksWithData(h.db, bookmarks, viewerDID, shared) 447 + }() 352 448 353 449 if len(collectionItems) > 0 { 354 450 var sembleURIs []string ··· 362 458 defer cancel() 363 459 ensureSembleCardsIndexed(ctx, h.db, sembleURIs) 364 460 } 461 + wg.Add(1) 462 + go func() { 463 + defer wg.Done() 464 + authCollectionItems, _ = hydrateCollectionItemsWithData(h.db, collectionItems, viewerDID, shared) 465 + }() 365 466 } 366 467 367 - authCollectionItems, _ := hydrateCollectionItems(h.db, collectionItems, viewerDID) 468 + wg.Wait() 368 469 369 470 collectionItemURIs := make(map[string]string) 370 471 for _, ci := range authCollectionItems { ··· 1194 1295 return 1195 1296 } 1196 1297 1197 - client := &http.Client{Timeout: 10 * time.Second} 1198 - resp, err := client.Get(targetURL) 1199 - if err != nil { 1298 + if cached, ok := h.metaCache.get(targetURL); ok { 1200 1299 w.Header().Set("Content-Type", "application/json") 1201 - json.NewEncoder(w).Encode(map[string]string{"title": "", "error": "failed to fetch"}) 1300 + w.Header().Set("X-Cache", "HIT") 1301 + json.NewEncoder(w).Encode(cached) 1202 1302 return 1203 1303 } 1204 - defer resp.Body.Close() 1304 + 1305 + sfVal, loaded := h.metaCache.inflight.LoadOrStore(targetURL, &singleflight{}) 1306 + sf := sfVal.(*singleflight) 1307 + if loaded { 1308 + sf.wg.Wait() 1309 + w.Header().Set("Content-Type", "application/json") 1310 + w.Header().Set("X-Cache", "DEDUP") 1311 + if sf.data != nil { 1312 + json.NewEncoder(w).Encode(sf.data) 1313 + } else { 1314 + json.NewEncoder(w).Encode(map[string]string{"title": "", "error": "failed to fetch"}) 1315 + } 1316 + return 1317 + } 1318 + 1319 + sf.wg.Add(1) 1320 + defer func() { 1321 + sf.wg.Done() 1322 + go func() { 1323 + time.Sleep(100 * time.Millisecond) 1324 + h.metaCache.inflight.Delete(targetURL) 1325 + }() 1326 + }() 1205 1327 1206 - body, err := io.ReadAll(io.LimitReader(resp.Body, 500*1024)) 1207 - if err != nil { 1328 + select { 1329 + case h.metaSem <- struct{}{}: 1330 + defer func() { <-h.metaSem }() 1331 + case <-r.Context().Done(): 1332 + sf.data = map[string]string{"title": "", "error": "timeout"} 1208 1333 w.Header().Set("Content-Type", "application/json") 1209 - json.NewEncoder(w).Encode(map[string]string{"title": ""}) 1334 + json.NewEncoder(w).Encode(sf.data) 1210 1335 return 1336 + } 1337 + 1338 + data := h.fetchURLMetadata(r.Context(), targetURL) 1339 + sf.data = data 1340 + 1341 + ttl := 1 * time.Hour 1342 + if data["title"] == "" && data["error"] != "" { 1343 + ttl = 2 * time.Minute 1344 + } 1345 + h.metaCache.set(targetURL, data, ttl) 1346 + 1347 + w.Header().Set("Content-Type", "application/json") 1348 + w.Header().Set("Cache-Control", "public, max-age=3600") 1349 + json.NewEncoder(w).Encode(data) 1350 + } 1351 + 1352 + func (h *Handler) fetchURLMetadata(ctx context.Context, targetURL string) map[string]string { 1353 + ctx, cancel := context.WithTimeout(ctx, 4*time.Second) 1354 + defer cancel() 1355 + 1356 + req, err := http.NewRequestWithContext(ctx, "GET", targetURL, nil) 1357 + if err != nil { 1358 + return map[string]string{"title": "", "error": "invalid url"} 1359 + } 1360 + req.Header.Set("User-Agent", "Margin/1.0 (metadata fetcher)") 1361 + req.Header.Set("Accept", "text/html") 1362 + 1363 + client := &http.Client{ 1364 + Timeout: 4 * time.Second, 1365 + CheckRedirect: func(req *http.Request, via []*http.Request) error { 1366 + if len(via) >= 3 { 1367 + return fmt.Errorf("too many redirects") 1368 + } 1369 + return nil 1370 + }, 1371 + } 1372 + 1373 + resp, err := client.Do(req) 1374 + if err != nil { 1375 + return map[string]string{"title": "", "error": "failed to fetch"} 1376 + } 1377 + defer resp.Body.Close() 1378 + 1379 + body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024)) 1380 + if err != nil { 1381 + return map[string]string{"title": ""} 1211 1382 } 1212 1383 1213 1384 content := string(body) ··· 1310 1481 } 1311 1482 } 1312 1483 1313 - data := map[string]string{ 1484 + return map[string]string{ 1314 1485 "title": title, 1315 1486 "description": description, 1316 1487 "image": image, 1317 1488 "icon": favicon, 1318 1489 } 1319 - 1320 - w.Header().Set("Content-Type", "application/json") 1321 - json.NewEncoder(w).Encode(data) 1322 1490 } 1323 1491 1324 1492 func (h *Handler) GetNotifications(w http.ResponseWriter, r *http.Request) {
+239 -68
backend/internal/api/hydration.go
··· 19 19 var ( 20 20 Cache ProfileCache = NewInMemoryCache(5 * time.Minute) 21 21 ConstellationClient *constellation.Client = constellation.NewClient() // Enabled by default 22 + 23 + bskyHTTPClient = &http.Client{Timeout: 5 * time.Second} 22 24 ) 23 25 24 26 func init() { ··· 168 170 ReadAt *time.Time `json:"readAt,omitempty"` 169 171 } 170 172 173 + type hydrationData struct { 174 + profiles map[string]Author 175 + subscribedLabelers []string 176 + } 177 + 171 178 func fetchCounts(ctx context.Context, database *db.DB, uris []string, viewerDID string) (likeCounts, replyCounts map[string]int, viewerLikes map[string]bool) { 172 179 likeCounts = make(map[string]int) 173 180 replyCounts = make(map[string]int) ··· 177 184 return 178 185 } 179 186 187 + var wg sync.WaitGroup 188 + var mu sync.Mutex 189 + 180 190 if database != nil { 181 - likeCounts, _ = database.GetLikeCounts(uris) 182 - replyCounts, _ = database.GetReplyCounts(uris) 191 + wg.Add(2) 192 + go func() { 193 + defer wg.Done() 194 + if lc, err := database.GetLikeCounts(uris); err == nil { 195 + mu.Lock() 196 + likeCounts = lc 197 + mu.Unlock() 198 + } 199 + }() 200 + go func() { 201 + defer wg.Done() 202 + if rc, err := database.GetReplyCounts(uris); err == nil { 203 + mu.Lock() 204 + replyCounts = rc 205 + mu.Unlock() 206 + } 207 + }() 183 208 if viewerDID != "" { 184 - viewerLikes, _ = database.GetViewerLikes(viewerDID, uris) 209 + wg.Add(1) 210 + go func() { 211 + defer wg.Done() 212 + if vl, err := database.GetViewerLikes(viewerDID, uris); err == nil { 213 + mu.Lock() 214 + viewerLikes = vl 215 + mu.Unlock() 216 + } 217 + }() 185 218 } 219 + wg.Wait() 186 220 } 187 221 188 222 if ConstellationClient != nil && len(uris) <= 5 { ··· 205 239 return 206 240 } 207 241 242 + func fetchEngagementData(database *db.DB, uris []string, authorDIDs []string, viewerDID string) ( 243 + likeCounts, replyCounts map[string]int, 244 + viewerLikes map[string]bool, 245 + uriLabels, didLabels map[string][]db.ContentLabel, 246 + editTimes map[string]time.Time, 247 + ) { 248 + likeCounts = make(map[string]int) 249 + replyCounts = make(map[string]int) 250 + viewerLikes = make(map[string]bool) 251 + uriLabels = make(map[string][]db.ContentLabel) 252 + didLabels = make(map[string][]db.ContentLabel) 253 + editTimes = make(map[string]time.Time) 254 + 255 + if len(uris) == 0 { 256 + return 257 + } 258 + 259 + subscribedLabelers := getSubscribedLabelers(database, viewerDID) 260 + labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) 261 + 262 + var wg sync.WaitGroup 263 + var mu sync.Mutex 264 + 265 + wg.Add(1) 266 + go func() { 267 + defer wg.Done() 268 + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 269 + defer cancel() 270 + lc, rc, vl := fetchCounts(ctx, database, uris, viewerDID) 271 + mu.Lock() 272 + likeCounts = lc 273 + replyCounts = rc 274 + viewerLikes = vl 275 + mu.Unlock() 276 + }() 277 + 278 + wg.Add(1) 279 + go func() { 280 + defer wg.Done() 281 + if ul, err := database.GetContentLabelsForURIs(uris, labelerDIDs); err == nil { 282 + mu.Lock() 283 + uriLabels = ul 284 + mu.Unlock() 285 + } 286 + }() 287 + 288 + wg.Add(1) 289 + go func() { 290 + defer wg.Done() 291 + if dl, err := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs); err == nil { 292 + mu.Lock() 293 + didLabels = dl 294 + mu.Unlock() 295 + } 296 + }() 297 + 298 + wg.Add(1) 299 + go func() { 300 + defer wg.Done() 301 + if et, err := database.GetLatestEditTimes(uris); err == nil { 302 + mu.Lock() 303 + editTimes = et 304 + mu.Unlock() 305 + } 306 + }() 307 + 308 + wg.Wait() 309 + return 310 + } 311 + 208 312 func hydrateAnnotations(database *db.DB, annotations []db.Annotation, viewerDID string) ([]APIAnnotation, error) { 313 + return hydrateAnnotationsWithData(database, annotations, viewerDID, nil) 314 + } 315 + 316 + func hydrateAnnotationsWithData(database *db.DB, annotations []db.Annotation, viewerDID string, shared *hydrationData) ([]APIAnnotation, error) { 209 317 if len(annotations) == 0 { 210 318 return []APIAnnotation{}, nil 211 319 } 212 320 213 - profiles := fetchProfilesForDIDs(database, collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID })) 321 + var profiles map[string]Author 322 + if shared != nil && shared.profiles != nil { 323 + profiles = shared.profiles 324 + } else { 325 + profiles = fetchProfilesForDIDs(database, collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID })) 326 + } 214 327 215 328 uris := make([]string, len(annotations)) 216 329 for i, a := range annotations { 217 330 uris[i] = a.URI 218 331 } 332 + authorDIDs := collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID }) 219 333 220 - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 221 - defer cancel() 222 - likeCounts, replyCounts, viewerLikes := fetchCounts(ctx, database, uris, viewerDID) 223 - 224 - subscribedLabelers := getSubscribedLabelers(database, viewerDID) 225 - authorDIDs := collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID }) 226 - labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) 227 - uriLabels, _ := database.GetContentLabelsForURIs(uris, labelerDIDs) 228 - didLabels, _ := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs) 229 - editTimes, _ := database.GetLatestEditTimes(uris) 334 + likeCounts, replyCounts, viewerLikes, uriLabels, didLabels, editTimes := fetchEngagementData(database, uris, authorDIDs, viewerDID) 230 335 231 336 result := make([]APIAnnotation, len(annotations)) 232 337 for i, a := range annotations { ··· 303 408 } 304 409 305 410 func hydrateHighlights(database *db.DB, highlights []db.Highlight, viewerDID string) ([]APIHighlight, error) { 411 + return hydrateHighlightsWithData(database, highlights, viewerDID, nil) 412 + } 413 + 414 + func hydrateHighlightsWithData(database *db.DB, highlights []db.Highlight, viewerDID string, shared *hydrationData) ([]APIHighlight, error) { 306 415 if len(highlights) == 0 { 307 416 return []APIHighlight{}, nil 308 417 } 309 418 310 - profiles := fetchProfilesForDIDs(database, collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID })) 419 + var profiles map[string]Author 420 + if shared != nil && shared.profiles != nil { 421 + profiles = shared.profiles 422 + } else { 423 + profiles = fetchProfilesForDIDs(database, collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID })) 424 + } 311 425 312 426 uris := make([]string, len(highlights)) 313 427 for i, h := range highlights { 314 428 uris[i] = h.URI 315 429 } 430 + authorDIDs := collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID }) 316 431 317 - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 318 - defer cancel() 319 - likeCounts, replyCounts, viewerLikes := fetchCounts(ctx, database, uris, viewerDID) 320 - 321 - subscribedLabelers := getSubscribedLabelers(database, viewerDID) 322 - authorDIDs := collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID }) 323 - labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) 324 - uriLabels, _ := database.GetContentLabelsForURIs(uris, labelerDIDs) 325 - didLabels, _ := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs) 326 - editTimes, _ := database.GetLatestEditTimes(uris) 432 + likeCounts, replyCounts, viewerLikes, uriLabels, didLabels, editTimes := fetchEngagementData(database, uris, authorDIDs, viewerDID) 327 433 328 434 result := make([]APIHighlight, len(highlights)) 329 435 for i, h := range highlights { ··· 385 491 } 386 492 387 493 func hydrateBookmarks(database *db.DB, bookmarks []db.Bookmark, viewerDID string) ([]APIBookmark, error) { 494 + return hydrateBookmarksWithData(database, bookmarks, viewerDID, nil) 495 + } 496 + 497 + func hydrateBookmarksWithData(database *db.DB, bookmarks []db.Bookmark, viewerDID string, shared *hydrationData) ([]APIBookmark, error) { 388 498 if len(bookmarks) == 0 { 389 499 return []APIBookmark{}, nil 390 500 } 391 501 392 - profiles := fetchProfilesForDIDs(database, collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID })) 502 + var profiles map[string]Author 503 + if shared != nil && shared.profiles != nil { 504 + profiles = shared.profiles 505 + } else { 506 + profiles = fetchProfilesForDIDs(database, collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID })) 507 + } 393 508 394 509 uris := make([]string, len(bookmarks)) 395 510 for i, b := range bookmarks { 396 511 uris[i] = b.URI 397 512 } 513 + authorDIDs := collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID }) 398 514 399 - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 400 - defer cancel() 401 - likeCounts, replyCounts, viewerLikes := fetchCounts(ctx, database, uris, viewerDID) 402 - 403 - subscribedLabelers := getSubscribedLabelers(database, viewerDID) 404 - authorDIDs := collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID }) 405 - labelerDIDs := appendUnique(subscribedLabelers, authorDIDs) 406 - uriLabels, _ := database.GetContentLabelsForURIs(uris, labelerDIDs) 407 - didLabels, _ := database.GetContentLabelsForDIDs(authorDIDs, labelerDIDs) 408 - editTimes, _ := database.GetLatestEditTimes(uris) 515 + likeCounts, replyCounts, viewerLikes, uriLabels, didLabels, editTimes := fetchEngagementData(database, uris, authorDIDs, viewerDID) 409 516 410 517 result := make([]APIBookmark, len(bookmarks)) 411 518 for i, b := range bookmarks { ··· 504 611 505 612 func fetchProfilesForDIDs(database *db.DB, dids []string) map[string]Author { 506 613 profiles := make(map[string]Author) 507 - missingDIDs := make([]string, 0) 614 + if len(dids) == 0 { 615 + return profiles 616 + } 508 617 618 + missingDIDs := make([]string, 0) 509 619 for _, did := range dids { 510 620 if author, ok := Cache.Get(did); ok { 511 621 profiles[did] = author ··· 514 624 } 515 625 } 516 626 627 + if len(missingDIDs) == 0 { 628 + return profiles 629 + } 630 + 631 + if database != nil { 632 + marginProfiles, err := database.GetProfilesByDIDs(missingDIDs) 633 + if err == nil { 634 + for did, mp := range marginProfiles { 635 + author := Author{DID: did} 636 + if mp.DisplayName != nil && *mp.DisplayName != "" { 637 + author.DisplayName = *mp.DisplayName 638 + } 639 + if mp.Avatar != nil && *mp.Avatar != "" { 640 + author.Avatar = getProxiedAvatarURL(did, *mp.Avatar) 641 + } 642 + profiles[did] = author 643 + Cache.Set(did, author) 644 + } 645 + } 646 + 647 + stillMissing := make([]string, 0) 648 + for _, did := range missingDIDs { 649 + if _, ok := profiles[did]; !ok { 650 + stillMissing = append(stillMissing, did) 651 + } 652 + } 653 + missingDIDs = stillMissing 654 + } 655 + 517 656 if len(missingDIDs) > 0 { 518 657 batchSize := 25 519 658 var wg sync.WaitGroup ··· 532 671 fetched, err := fetchProfiles(actors) 533 672 if err == nil { 534 673 mu.Lock() 535 - defer mu.Unlock() 536 674 for k, v := range fetched { 537 675 profiles[k] = v 676 + Cache.Set(k, v) 538 677 } 678 + mu.Unlock() 539 679 } 540 680 }(batch) 541 681 } ··· 548 688 for did, mp := range marginProfiles { 549 689 author, exists := profiles[did] 550 690 if !exists { 551 - author = Author{ 552 - DID: did, 553 - } 691 + author = Author{DID: did} 554 692 } 555 - 556 693 if mp.DisplayName != nil && *mp.DisplayName != "" { 557 694 author.DisplayName = *mp.DisplayName 558 695 } ··· 560 697 author.Avatar = getProxiedAvatarURL(did, *mp.Avatar) 561 698 } 562 699 profiles[did] = author 563 - 564 700 Cache.Set(did, author) 565 701 } 566 702 } ··· 579 715 q.Add("actors", did) 580 716 } 581 717 582 - resp, err := http.Get(config.Get().BskyGetProfilesURL() + "?" + q.Encode()) 718 + resp, err := bskyHTTPClient.Get(config.Get().BskyGetProfilesURL() + "?" + q.Encode()) 583 719 if err != nil { 584 720 logger.Error("Hydration fetch error: %v", err) 585 721 return nil, err ··· 618 754 } 619 755 620 756 func hydrateCollectionItems(database *db.DB, items []db.CollectionItem, viewerDID string) ([]APICollectionItem, error) { 757 + return hydrateCollectionItemsWithData(database, items, viewerDID, nil) 758 + } 759 + 760 + func hydrateCollectionItemsWithData(database *db.DB, items []db.CollectionItem, viewerDID string, shared *hydrationData) ([]APICollectionItem, error) { 621 761 if len(items) == 0 { 622 762 return []APICollectionItem{}, nil 623 763 } 624 764 625 - profiles := fetchProfilesForDIDs(database, collectDIDs(items, func(i db.CollectionItem) string { return i.AuthorDID })) 765 + var profiles map[string]Author 766 + if shared != nil && shared.profiles != nil { 767 + profiles = shared.profiles 768 + } else { 769 + profiles = fetchProfilesForDIDs(database, collectDIDs(items, func(i db.CollectionItem) string { return i.AuthorDID })) 770 + } 626 771 627 772 var collectionURIs []string 628 773 var annotationURIs []string ··· 647 792 if len(collectionURIs) > 0 { 648 793 colls, err := database.GetCollectionsByURIs(collectionURIs) 649 794 if err == nil { 650 - collProfiles := fetchProfilesForDIDs(database, collectDIDs(colls, func(c db.Collection) string { return c.AuthorDID })) 651 795 for _, coll := range colls { 652 796 icon := "" 653 797 if coll.Icon != nil { ··· 662 806 Name: coll.Name, 663 807 Description: desc, 664 808 Icon: icon, 665 - Creator: collProfiles[coll.AuthorDID], 809 + Creator: profiles[coll.AuthorDID], 666 810 CreatedAt: coll.CreatedAt, 667 811 IndexedAt: coll.IndexedAt, 668 812 } ··· 670 814 } 671 815 } 672 816 673 - annotationsMap := make(map[string]APIAnnotation) 817 + var ( 818 + annotationsMap = make(map[string]APIAnnotation) 819 + highlightsMap = make(map[string]APIHighlight) 820 + bookmarksMap = make(map[string]APIBookmark) 821 + wg sync.WaitGroup 822 + mu sync.Mutex 823 + ) 824 + 825 + nestedShared := &hydrationData{profiles: profiles} 826 + 674 827 if len(annotationURIs) > 0 { 675 - rawAnnos, err := database.GetAnnotationsByURIs(annotationURIs) 676 - if err == nil { 677 - hydrated, _ := hydrateAnnotations(database, rawAnnos, viewerDID) 678 - for _, a := range hydrated { 679 - annotationsMap[a.ID] = a 828 + wg.Add(1) 829 + go func() { 830 + defer wg.Done() 831 + rawAnnos, err := database.GetAnnotationsByURIs(annotationURIs) 832 + if err == nil { 833 + hydrated, _ := hydrateAnnotationsWithData(database, rawAnnos, viewerDID, nestedShared) 834 + mu.Lock() 835 + for _, a := range hydrated { 836 + annotationsMap[a.ID] = a 837 + } 838 + mu.Unlock() 680 839 } 681 - } 840 + }() 682 841 } 683 842 684 - highlightsMap := make(map[string]APIHighlight) 685 843 if len(highlightURIs) > 0 { 686 - rawHighlights, err := database.GetHighlightsByURIs(highlightURIs) 687 - if err == nil { 688 - hydrated, _ := hydrateHighlights(database, rawHighlights, viewerDID) 689 - for _, h := range hydrated { 690 - highlightsMap[h.ID] = h 844 + wg.Add(1) 845 + go func() { 846 + defer wg.Done() 847 + rawHighlights, err := database.GetHighlightsByURIs(highlightURIs) 848 + if err == nil { 849 + hydrated, _ := hydrateHighlightsWithData(database, rawHighlights, viewerDID, nestedShared) 850 + mu.Lock() 851 + for _, h := range hydrated { 852 + highlightsMap[h.ID] = h 853 + } 854 + mu.Unlock() 691 855 } 692 - } 856 + }() 693 857 } 694 858 695 - bookmarksMap := make(map[string]APIBookmark) 696 859 if len(bookmarkURIs) > 0 { 697 - rawBookmarks, err := database.GetBookmarksByURIs(bookmarkURIs) 698 - if err == nil { 699 - hydrated, _ := hydrateBookmarks(database, rawBookmarks, viewerDID) 700 - for _, b := range hydrated { 701 - bookmarksMap[b.ID] = b 860 + wg.Add(1) 861 + go func() { 862 + defer wg.Done() 863 + rawBookmarks, err := database.GetBookmarksByURIs(bookmarkURIs) 864 + if err == nil { 865 + hydrated, _ := hydrateBookmarksWithData(database, rawBookmarks, viewerDID, nestedShared) 866 + mu.Lock() 867 + for _, b := range hydrated { 868 + bookmarksMap[b.ID] = b 869 + } 870 + mu.Unlock() 702 871 } 703 - } 872 + }() 704 873 } 874 + 875 + wg.Wait() 705 876 706 877 var result []APICollectionItem 707 878 for _, item := range items {
+3 -1
backend/internal/api/pds.go
··· 11 11 "margin.at/internal/xrpc" 12 12 ) 13 13 14 + var pdsClient = &http.Client{Timeout: 10 * time.Second} 15 + 14 16 func (h *Handler) FetchLatestUserRecords(r *http.Request, did string, collection string, limit int) ([]interface{}, error) { 15 17 session, err := h.refresher.GetSessionWithAutoRefresh(r) 16 18 if err != nil { ··· 25 27 req, _ := http.NewRequestWithContext(r.Context(), "GET", url, nil) 26 28 req.Header.Set("Authorization", "Bearer "+client.AccessToken) 27 29 28 - resp, err := http.DefaultClient.Do(req) 30 + resp, err := pdsClient.Do(req) 29 31 if err != nil { 30 32 return fmt.Errorf("failed to fetch %s: %w", collection, err) 31 33 }
+107 -176
backend/internal/db/db.go
··· 8 8 "time" 9 9 10 10 _ "github.com/lib/pq" 11 - _ "github.com/mattn/go-sqlite3" 12 11 ) 13 12 14 13 type DB struct { 15 14 *sql.DB 16 - driver string 17 15 } 18 16 19 17 type Annotation struct { ··· 204 202 } 205 203 206 204 func New(dsn string) (*DB, error) { 207 - driver := "sqlite3" 208 - if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") { 209 - driver = "postgres" 205 + if !strings.HasPrefix(dsn, "postgres://") && !strings.HasPrefix(dsn, "postgresql://") { 206 + return nil, fmt.Errorf("only PostgreSQL is supported, DSN must start with postgres:// or postgresql://") 210 207 } 211 208 212 - db, err := sql.Open(driver, dsn) 209 + db, err := sql.Open("postgres", dsn) 213 210 if err != nil { 214 211 return nil, fmt.Errorf("failed to open database connection: %w", err) 215 212 } 216 213 217 - if driver == "sqlite3" { 218 - if _, err := db.Exec("PRAGMA journal_mode=WAL;"); err != nil { 219 - return nil, fmt.Errorf("failed to set WAL mode: %w", err) 220 - } 221 - db.Exec("PRAGMA synchronous=NORMAL;") 222 - db.Exec("PRAGMA busy_timeout=5000;") 223 - db.Exec("PRAGMA cache_size=-2000;") 224 - db.Exec("PRAGMA foreign_keys=ON;") 225 - 226 - db.SetMaxOpenConns(25) 227 - db.SetMaxIdleConns(25) 228 - db.SetConnMaxLifetime(5 * time.Minute) 229 - } else { 230 - db.SetMaxOpenConns(50) 231 - db.SetMaxIdleConns(25) 232 - db.SetConnMaxLifetime(10 * time.Minute) 233 - } 214 + db.SetMaxOpenConns(25) 215 + db.SetMaxIdleConns(10) 216 + db.SetConnMaxLifetime(5 * time.Minute) 217 + db.SetConnMaxIdleTime(2 * time.Minute) 234 218 235 219 if err := db.Ping(); err != nil { 236 220 return nil, fmt.Errorf("failed to ping database: %w", err) 237 221 } 238 222 239 - return &DB{DB: db, driver: driver}, nil 223 + return &DB{DB: db}, nil 240 224 } 241 225 242 226 func (db *DB) Migrate() error { 243 - 244 - dateType := "DATETIME" 245 - if db.driver == "postgres" { 246 - dateType = "TIMESTAMP" 247 - } 248 - 249 227 _, err := db.Exec(` 250 228 CREATE TABLE IF NOT EXISTS annotations ( 251 229 uri TEXT PRIMARY KEY, ··· 259 237 target_title TEXT, 260 238 selector_json TEXT, 261 239 tags_json TEXT, 262 - created_at ` + dateType + ` NOT NULL, 263 - indexed_at ` + dateType + ` NOT NULL, 240 + created_at TIMESTAMP NOT NULL, 241 + indexed_at TIMESTAMP NOT NULL, 264 242 cid TEXT 265 243 )`) 266 244 if err != nil { ··· 272 250 db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_author_did ON annotations(author_did)`) 273 251 db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_motivation ON annotations(motivation)`) 274 252 db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_created_at ON annotations(created_at DESC)`) 253 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_author_created ON annotations(author_did, created_at DESC)`) 254 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_annotations_uri_pattern ON annotations(uri text_pattern_ops)`) 275 255 276 256 db.Exec(`CREATE TABLE IF NOT EXISTS highlights ( 277 257 uri TEXT PRIMARY KEY, ··· 282 262 selector_json TEXT, 283 263 color TEXT, 284 264 tags_json TEXT, 285 - created_at ` + dateType + ` NOT NULL, 286 - indexed_at ` + dateType + ` NOT NULL, 265 + created_at TIMESTAMP NOT NULL, 266 + indexed_at TIMESTAMP NOT NULL, 287 267 cid TEXT 288 268 )`) 289 269 db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_target_hash ON highlights(target_hash)`) 290 270 db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_author_did ON highlights(author_did)`) 291 271 db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_created_at ON highlights(created_at DESC)`) 272 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_author_created ON highlights(author_did, created_at DESC)`) 273 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_highlights_uri_pattern ON highlights(uri text_pattern_ops)`) 292 274 293 275 db.Exec(`CREATE TABLE IF NOT EXISTS bookmarks ( 294 276 uri TEXT PRIMARY KEY, ··· 298 280 title TEXT, 299 281 description TEXT, 300 282 tags_json TEXT, 301 - created_at ` + dateType + ` NOT NULL, 302 - indexed_at ` + dateType + ` NOT NULL, 283 + created_at TIMESTAMP NOT NULL, 284 + indexed_at TIMESTAMP NOT NULL, 303 285 cid TEXT 304 286 )`) 305 287 db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_source_hash ON bookmarks(source_hash)`) 306 288 db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_author_did ON bookmarks(author_did)`) 307 289 db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_created_at ON bookmarks(created_at DESC)`) 290 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_author_created ON bookmarks(author_did, created_at DESC)`) 291 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_uri_pattern ON bookmarks(uri text_pattern_ops)`) 308 292 309 293 db.Exec(`CREATE TABLE IF NOT EXISTS replies ( 310 294 uri TEXT PRIMARY KEY, ··· 313 297 root_uri TEXT NOT NULL, 314 298 text TEXT NOT NULL, 315 299 format TEXT DEFAULT 'text/plain', 316 - created_at ` + dateType + ` NOT NULL, 317 - indexed_at ` + dateType + ` NOT NULL, 300 + created_at TIMESTAMP NOT NULL, 301 + indexed_at TIMESTAMP NOT NULL, 318 302 cid TEXT 319 303 )`) 320 304 db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_parent_uri ON replies(parent_uri)`) 321 305 db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_root_uri ON replies(root_uri)`) 322 306 db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_created_at ON replies(created_at DESC)`) 307 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_replies_author_did ON replies(author_did)`) 323 308 324 309 db.Exec(`CREATE TABLE IF NOT EXISTS likes ( 325 310 uri TEXT PRIMARY KEY, 326 311 author_did TEXT NOT NULL, 327 312 subject_uri TEXT NOT NULL, 328 - created_at ` + dateType + ` NOT NULL, 329 - indexed_at ` + dateType + ` NOT NULL 313 + created_at TIMESTAMP NOT NULL, 314 + indexed_at TIMESTAMP NOT NULL 330 315 )`) 331 316 db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_subject_uri ON likes(subject_uri)`) 332 317 db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_author_did ON likes(author_did)`) ··· 338 323 name TEXT NOT NULL, 339 324 description TEXT, 340 325 icon TEXT, 341 - created_at ` + dateType + ` NOT NULL, 342 - indexed_at ` + dateType + ` NOT NULL 326 + created_at TIMESTAMP NOT NULL, 327 + indexed_at TIMESTAMP NOT NULL 343 328 )`) 344 329 db.Exec(`CREATE INDEX IF NOT EXISTS idx_collections_author_did ON collections(author_did)`) 345 330 db.Exec(`CREATE INDEX IF NOT EXISTS idx_collections_created_at ON collections(created_at DESC)`) ··· 350 335 collection_uri TEXT NOT NULL, 351 336 annotation_uri TEXT NOT NULL, 352 337 position INTEGER DEFAULT 0, 353 - created_at ` + dateType + ` NOT NULL, 354 - indexed_at ` + dateType + ` NOT NULL 338 + created_at TIMESTAMP NOT NULL, 339 + indexed_at TIMESTAMP NOT NULL 355 340 )`) 356 341 db.Exec(`CREATE INDEX IF NOT EXISTS idx_collection_items_collection ON collection_items(collection_uri)`) 357 342 db.Exec(`CREATE INDEX IF NOT EXISTS idx_collection_items_annotation ON collection_items(annotation_uri)`) ··· 364 349 access_token TEXT NOT NULL, 365 350 refresh_token TEXT NOT NULL, 366 351 dpop_key TEXT, 367 - created_at ` + dateType + ` NOT NULL, 368 - expires_at ` + dateType + ` NOT NULL 352 + created_at TIMESTAMP NOT NULL, 353 + expires_at TIMESTAMP NOT NULL 369 354 )`) 370 355 db.Exec(`CREATE INDEX IF NOT EXISTS idx_sessions_did ON sessions(did)`) 371 - 372 - autoInc := "INTEGER PRIMARY KEY AUTOINCREMENT" 373 - if db.driver == "postgres" { 374 - autoInc = "SERIAL PRIMARY KEY" 375 - } 356 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at)`) 376 357 377 358 db.Exec(`CREATE TABLE IF NOT EXISTS edit_history ( 378 - id ` + autoInc + `, 359 + id SERIAL PRIMARY KEY, 379 360 uri TEXT NOT NULL, 380 361 record_type TEXT NOT NULL, 381 362 previous_content TEXT NOT NULL, 382 363 previous_cid TEXT, 383 - edited_at ` + dateType + ` NOT NULL 364 + edited_at TIMESTAMP NOT NULL 384 365 )`) 385 366 db.Exec(`CREATE INDEX IF NOT EXISTS idx_edit_history_uri ON edit_history(uri)`) 386 367 db.Exec(`CREATE INDEX IF NOT EXISTS idx_edit_history_edited_at ON edit_history(edited_at DESC)`) 387 368 388 369 db.Exec(`CREATE TABLE IF NOT EXISTS notifications ( 389 - id ` + autoInc + `, 370 + id SERIAL PRIMARY KEY, 390 371 recipient_did TEXT NOT NULL, 391 372 actor_did TEXT NOT NULL, 392 373 type TEXT NOT NULL, 393 374 subject_uri TEXT NOT NULL, 394 - created_at ` + dateType + ` NOT NULL, 395 - read_at ` + dateType + ` 375 + created_at TIMESTAMP NOT NULL, 376 + read_at TIMESTAMP 396 377 )`) 397 378 db.Exec(`CREATE INDEX IF NOT EXISTS idx_notifications_recipient ON notifications(recipient_did)`) 398 379 db.Exec(`CREATE INDEX IF NOT EXISTS idx_notifications_created_at ON notifications(created_at DESC)`) 380 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_notifications_unread ON notifications(recipient_did) WHERE read_at IS NULL`) 399 381 400 382 db.Exec(`CREATE TABLE IF NOT EXISTS api_keys ( 401 383 id TEXT PRIMARY KEY, 402 384 owner_did TEXT NOT NULL, 403 385 name TEXT NOT NULL, 404 386 key_hash TEXT NOT NULL, 405 - created_at ` + dateType + ` NOT NULL, 406 - last_used_at ` + dateType + `, 387 + created_at TIMESTAMP NOT NULL, 388 + last_used_at TIMESTAMP, 407 389 uri TEXT, 408 390 cid TEXT, 409 - indexed_at ` + dateType + ` DEFAULT CURRENT_TIMESTAMP 391 + indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 410 392 )`) 411 393 db.Exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_owner ON api_keys(owner_did)`) 412 394 db.Exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash)`) ··· 419 401 bio TEXT, 420 402 website TEXT, 421 403 links_json TEXT, 422 - created_at ` + dateType + ` NOT NULL, 423 - indexed_at ` + dateType + ` NOT NULL, 404 + created_at TIMESTAMP NOT NULL, 405 + indexed_at TIMESTAMP NOT NULL, 424 406 cid TEXT 425 407 )`) 426 408 db.Exec(`CREATE INDEX IF NOT EXISTS idx_profiles_author_did ON profiles(author_did)`) ··· 432 414 subscribed_labelers TEXT, 433 415 label_preferences TEXT, 434 416 disable_external_link_warning BOOLEAN, 435 - created_at ` + dateType + ` NOT NULL, 436 - indexed_at ` + dateType + ` NOT NULL, 417 + created_at TIMESTAMP NOT NULL, 418 + indexed_at TIMESTAMP NOT NULL, 437 419 cid TEXT 438 420 )`) 439 421 db.Exec(`CREATE INDEX IF NOT EXISTS idx_preferences_author_did ON preferences(author_did)`) ··· 443 425 db.Exec(`CREATE TABLE IF NOT EXISTS cursors ( 444 426 id TEXT PRIMARY KEY, 445 427 last_cursor BIGINT NOT NULL, 446 - updated_at ` + dateType + ` NOT NULL 428 + updated_at TIMESTAMP NOT NULL 447 429 )`) 448 430 449 431 db.Exec(`CREATE TABLE IF NOT EXISTS blocks ( 450 - id ` + autoInc + `, 432 + id SERIAL PRIMARY KEY, 451 433 actor_did TEXT NOT NULL, 452 434 subject_did TEXT NOT NULL, 453 - created_at ` + dateType + ` NOT NULL, 435 + created_at TIMESTAMP NOT NULL, 454 436 UNIQUE(actor_did, subject_did) 455 437 )`) 456 438 db.Exec(`CREATE INDEX IF NOT EXISTS idx_blocks_actor ON blocks(actor_did)`) 457 439 db.Exec(`CREATE INDEX IF NOT EXISTS idx_blocks_subject ON blocks(subject_did)`) 458 440 459 441 db.Exec(`CREATE TABLE IF NOT EXISTS mutes ( 460 - id ` + autoInc + `, 442 + id SERIAL PRIMARY KEY, 461 443 actor_did TEXT NOT NULL, 462 444 subject_did TEXT NOT NULL, 463 - created_at ` + dateType + ` NOT NULL, 445 + created_at TIMESTAMP NOT NULL, 464 446 UNIQUE(actor_did, subject_did) 465 447 )`) 466 448 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mutes_actor ON mutes(actor_did)`) 467 449 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mutes_subject ON mutes(subject_did)`) 468 450 469 451 db.Exec(`CREATE TABLE IF NOT EXISTS moderation_reports ( 470 - id ` + autoInc + `, 452 + id SERIAL PRIMARY KEY, 471 453 reporter_did TEXT NOT NULL, 472 454 subject_did TEXT NOT NULL, 473 455 subject_uri TEXT, 474 456 reason_type TEXT NOT NULL, 475 457 reason_text TEXT, 476 458 status TEXT NOT NULL DEFAULT 'pending', 477 - created_at ` + dateType + ` NOT NULL, 478 - resolved_at ` + dateType + `, 459 + created_at TIMESTAMP NOT NULL, 460 + resolved_at TIMESTAMP, 479 461 resolved_by TEXT 480 462 )`) 481 463 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_status ON moderation_reports(status)`) ··· 483 465 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_reporter ON moderation_reports(reporter_did)`) 484 466 485 467 db.Exec(`CREATE TABLE IF NOT EXISTS moderation_actions ( 486 - id ` + autoInc + `, 468 + id SERIAL PRIMARY KEY, 487 469 report_id INTEGER NOT NULL, 488 470 actor_did TEXT NOT NULL, 489 471 action TEXT NOT NULL, 490 472 comment TEXT, 491 - created_at ` + dateType + ` NOT NULL 473 + created_at TIMESTAMP NOT NULL 492 474 )`) 493 475 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_actions_report ON moderation_actions(report_id)`) 494 476 495 477 db.Exec(`CREATE TABLE IF NOT EXISTS content_labels ( 496 - id ` + autoInc + `, 478 + id SERIAL PRIMARY KEY, 497 479 src TEXT NOT NULL, 498 480 uri TEXT NOT NULL, 499 481 val TEXT NOT NULL, 500 482 neg INTEGER NOT NULL DEFAULT 0, 501 483 created_by TEXT NOT NULL, 502 - created_at ` + dateType + ` NOT NULL 484 + created_at TIMESTAMP NOT NULL 503 485 )`) 504 486 db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_uri ON content_labels(uri)`) 505 487 db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_src ON content_labels(src)`) ··· 511 493 name TEXT NOT NULL, 512 494 description TEXT, 513 495 show_in_discover BOOLEAN NOT NULL DEFAULT true, 514 - indexed_at ` + dateType + ` NOT NULL 496 + indexed_at TIMESTAMP NOT NULL 515 497 )`) 516 498 db.Exec(`CREATE INDEX IF NOT EXISTS idx_publications_author ON publications(author_did)`) 517 499 db.Exec(`CREATE INDEX IF NOT EXISTS idx_publications_url ON publications(url)`) ··· 526 508 text_content TEXT, 527 509 tags_json TEXT, 528 510 canonical_url TEXT, 529 - published_at ` + dateType + ` NOT NULL, 530 - indexed_at ` + dateType + ` NOT NULL 511 + published_at TIMESTAMP NOT NULL, 512 + indexed_at TIMESTAMP NOT NULL 531 513 )`) 532 514 db.Exec(`CREATE INDEX IF NOT EXISTS idx_documents_author ON documents(author_did)`) 533 515 db.Exec(`CREATE INDEX IF NOT EXISTS idx_documents_site ON documents(site)`) ··· 544 526 return nil, nil 545 527 } 546 528 547 - query := `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at FROM profiles WHERE author_did IN (` 548 - args := make([]interface{}, len(dids)) 549 529 placeholders := make([]string, len(dids)) 550 - 530 + args := make([]interface{}, len(dids)) 551 531 for i, did := range dids { 552 532 placeholders[i] = fmt.Sprintf("$%d", i+1) 553 533 args[i] = did 554 534 } 555 535 556 - query += strings.Join(placeholders, ",") + ")" 557 - 558 - if db.driver == "sqlite3" { 559 - query = strings.ReplaceAll(query, "$", "?") 560 - 561 - placeholders = make([]string, len(dids)) 562 - for i := range dids { 563 - placeholders[i] = "?" 564 - } 565 - query = `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at FROM profiles WHERE author_did IN (` + strings.Join(placeholders, ",") + ")" 566 - } 536 + query := `SELECT uri, author_did, display_name, bio, avatar, website, links_json, created_at, indexed_at FROM profiles WHERE author_did IN (` + strings.Join(placeholders, ",") + ")" 567 537 568 538 rows, err := db.Query(query, args...) 569 539 if err != nil { ··· 597 567 598 568 func (db *DB) SetCursor(id string, cursor int64) error { 599 569 query := ` 600 - INSERT INTO cursors (id, last_cursor, updated_at) 601 - VALUES ($1, $2, $3) 602 - ON CONFLICT(id) DO UPDATE SET 603 - last_cursor = EXCLUDED.last_cursor, 570 + INSERT INTO cursors (id, last_cursor, updated_at) 571 + VALUES ($1, $2, $3) 572 + ON CONFLICT(id) DO UPDATE SET 573 + last_cursor = EXCLUDED.last_cursor, 604 574 updated_at = EXCLUDED.updated_at 605 575 ` 606 576 _, err := db.Exec(query, id, cursor, time.Now()) ··· 623 593 624 594 func (db *DB) UpsertProfile(p *Profile) error { 625 595 query := ` 626 - INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) 627 - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) 628 - ON CONFLICT(uri) DO UPDATE SET 596 + INSERT INTO profiles (uri, author_did, display_name, avatar, bio, website, links_json, created_at, indexed_at) 597 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) 598 + ON CONFLICT(uri) DO UPDATE SET 629 599 display_name = EXCLUDED.display_name, 630 600 avatar = EXCLUDED.avatar, 631 - bio = EXCLUDED.bio, 601 + bio = EXCLUDED.bio, 632 602 website = EXCLUDED.website, 633 603 links_json = EXCLUDED.links_json, 634 604 indexed_at = EXCLUDED.indexed_at 635 605 ` 636 - _, err := db.Exec(db.Rebind(query), p.URI, p.AuthorDID, p.DisplayName, p.Avatar, p.Bio, p.Website, p.LinksJSON, p.CreatedAt, p.IndexedAt) 606 + _, err := db.Exec(query, p.URI, p.AuthorDID, p.DisplayName, p.Avatar, p.Bio, p.Website, p.LinksJSON, p.CreatedAt, p.IndexedAt) 637 607 return err 638 608 } 639 609 ··· 672 642 673 643 func (db *DB) UpsertPreferences(p *Preferences) error { 674 644 query := ` 675 - INSERT INTO preferences (uri, author_did, external_link_skipped_hostnames, subscribed_labelers, label_preferences, disable_external_link_warning, created_at, indexed_at, cid) 676 - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) 677 - ON CONFLICT(uri) DO UPDATE SET 645 + INSERT INTO preferences (uri, author_did, external_link_skipped_hostnames, subscribed_labelers, label_preferences, disable_external_link_warning, created_at, indexed_at, cid) 646 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) 647 + ON CONFLICT(uri) DO UPDATE SET 678 648 external_link_skipped_hostnames = EXCLUDED.external_link_skipped_hostnames, 679 649 subscribed_labelers = EXCLUDED.subscribed_labelers, 680 650 label_preferences = EXCLUDED.label_preferences, ··· 682 652 indexed_at = EXCLUDED.indexed_at, 683 653 cid = EXCLUDED.cid 684 654 ` 685 - _, err := db.Exec(db.Rebind(query), p.URI, p.AuthorDID, p.ExternalLinkSkippedHostnames, p.SubscribedLabelers, p.LabelPreferences, p.DisableExternalLinkWarning, p.CreatedAt, p.IndexedAt, p.CID) 655 + _, err := db.Exec(query, p.URI, p.AuthorDID, p.ExternalLinkSkippedHostnames, p.SubscribedLabelers, p.LabelPreferences, p.DisableExternalLinkWarning, p.CreatedAt, p.IndexedAt, p.CID) 686 656 return err 687 657 } 688 658 ··· 697 667 } 698 668 699 669 func (db *DB) GetAPIKeyURIs(ownerDID string) ([]string, error) { 700 - rows, err := db.Query(db.Rebind("SELECT uri FROM api_keys WHERE owner_did = ? AND uri IS NOT NULL AND uri != ''"), ownerDID) 670 + rows, err := db.Query("SELECT uri FROM api_keys WHERE owner_did = $1 AND uri IS NOT NULL AND uri != ''", ownerDID) 701 671 if err != nil { 702 672 return nil, err 703 673 } ··· 714 684 } 715 685 716 686 func (db *DB) GetPreferenceURIs(did string) ([]string, error) { 717 - rows, err := db.Query(db.Rebind("SELECT uri FROM preferences WHERE author_did = ? AND uri IS NOT NULL AND uri != ''"), did) 687 + rows, err := db.Query("SELECT uri FROM preferences WHERE author_did = $1 AND uri IS NOT NULL AND uri != ''", did) 718 688 if err != nil { 719 689 return nil, err 720 690 } ··· 731 701 } 732 702 733 703 func (db *DB) runMigrations() { 734 - dateType := "DATETIME" 735 - if db.driver == "postgres" { 736 - dateType = "TIMESTAMP" 737 - } 738 - db.Exec(`ALTER TABLE sessions ADD COLUMN dpop_key TEXT`) 704 + db.Exec(`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS dpop_key TEXT`) 739 705 740 - db.Exec(`ALTER TABLE annotations ADD COLUMN motivation TEXT`) 741 - db.Exec(`ALTER TABLE annotations ADD COLUMN body_value TEXT`) 742 - db.Exec(`ALTER TABLE annotations ADD COLUMN body_format TEXT DEFAULT 'text/plain'`) 743 - db.Exec(`ALTER TABLE annotations ADD COLUMN body_uri TEXT`) 744 - db.Exec(`ALTER TABLE annotations ADD COLUMN target_source TEXT`) 745 - db.Exec(`ALTER TABLE annotations ADD COLUMN target_hash TEXT`) 746 - db.Exec(`ALTER TABLE annotations ADD COLUMN target_title TEXT`) 747 - db.Exec(`ALTER TABLE annotations ADD COLUMN selector_json TEXT`) 748 - db.Exec(`ALTER TABLE annotations ADD COLUMN tags_json TEXT`) 749 - db.Exec(`ALTER TABLE annotations ADD COLUMN cid TEXT`) 706 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS motivation TEXT`) 707 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS body_value TEXT`) 708 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS body_format TEXT DEFAULT 'text/plain'`) 709 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS body_uri TEXT`) 710 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS target_source TEXT`) 711 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS target_hash TEXT`) 712 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS target_title TEXT`) 713 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS selector_json TEXT`) 714 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS tags_json TEXT`) 715 + db.Exec(`ALTER TABLE annotations ADD COLUMN IF NOT EXISTS cid TEXT`) 750 716 751 717 db.Exec(`UPDATE annotations SET target_source = url WHERE target_source IS NULL AND url IS NOT NULL`) 752 718 db.Exec(`UPDATE annotations SET target_hash = url_hash WHERE target_hash IS NULL AND url_hash IS NOT NULL`) ··· 754 720 db.Exec(`UPDATE annotations SET target_title = title WHERE target_title IS NULL AND title IS NOT NULL`) 755 721 db.Exec(`UPDATE annotations SET motivation = 'commenting' WHERE motivation IS NULL`) 756 722 757 - db.Exec(`ALTER TABLE profiles ADD COLUMN website TEXT`) 758 - db.Exec(`ALTER TABLE profiles ADD COLUMN display_name TEXT`) 759 - db.Exec(`ALTER TABLE profiles ADD COLUMN avatar TEXT`) 723 + db.Exec(`ALTER TABLE profiles ADD COLUMN IF NOT EXISTS website TEXT`) 724 + db.Exec(`ALTER TABLE profiles ADD COLUMN IF NOT EXISTS display_name TEXT`) 725 + db.Exec(`ALTER TABLE profiles ADD COLUMN IF NOT EXISTS avatar TEXT`) 760 726 761 - if db.driver == "postgres" { 762 - db.Exec(`ALTER TABLE cursors ALTER COLUMN last_cursor TYPE BIGINT`) 763 - } 727 + db.Exec(`ALTER TABLE cursors ALTER COLUMN last_cursor TYPE BIGINT`) 764 728 765 - db.Exec(`ALTER TABLE api_keys ADD COLUMN uri TEXT`) 766 - db.Exec(`ALTER TABLE api_keys ADD COLUMN cid TEXT`) 767 - db.Exec(`ALTER TABLE api_keys ADD COLUMN indexed_at ` + dateType + ` DEFAULT CURRENT_TIMESTAMP`) 729 + db.Exec(`ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS uri TEXT`) 730 + db.Exec(`ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS cid TEXT`) 731 + db.Exec(`ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP`) 768 732 769 - db.migrateModeration(dateType) 733 + db.migrateModeration() 770 734 771 - db.Exec(`ALTER TABLE preferences ADD COLUMN subscribed_labelers TEXT`) 772 - db.Exec(`ALTER TABLE preferences ADD COLUMN label_preferences TEXT`) 773 - db.Exec(`ALTER TABLE preferences ADD COLUMN disable_external_link_warning BOOLEAN`) 735 + db.Exec(`ALTER TABLE preferences ADD COLUMN IF NOT EXISTS subscribed_labelers TEXT`) 736 + db.Exec(`ALTER TABLE preferences ADD COLUMN IF NOT EXISTS label_preferences TEXT`) 737 + db.Exec(`ALTER TABLE preferences ADD COLUMN IF NOT EXISTS disable_external_link_warning BOOLEAN`) 774 738 } 775 739 776 - func (db *DB) migrateModeration(dateType string) { 740 + func (db *DB) migrateModeration() { 777 741 _, err := db.Exec(`SELECT subject_did FROM moderation_reports LIMIT 0`) 778 742 if err != nil { 779 743 db.Exec(`DROP TABLE IF EXISTS moderation_reports`) 780 744 db.Exec(`DROP TABLE IF EXISTS moderation_actions`) 781 745 782 - autoInc := "INTEGER PRIMARY KEY AUTOINCREMENT" 783 - if db.driver == "postgres" { 784 - autoInc = "SERIAL PRIMARY KEY" 785 - } 786 - 787 746 db.Exec(`CREATE TABLE IF NOT EXISTS moderation_reports ( 788 - id ` + autoInc + `, 747 + id SERIAL PRIMARY KEY, 789 748 reporter_did TEXT NOT NULL, 790 749 subject_did TEXT NOT NULL, 791 750 subject_uri TEXT, 792 751 reason_type TEXT NOT NULL, 793 752 reason_text TEXT, 794 753 status TEXT NOT NULL DEFAULT 'pending', 795 - created_at ` + dateType + ` NOT NULL, 796 - resolved_at ` + dateType + `, 754 + created_at TIMESTAMP NOT NULL, 755 + resolved_at TIMESTAMP, 797 756 resolved_by TEXT 798 757 )`) 799 758 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_status ON moderation_reports(status)`) ··· 801 760 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_reports_reporter ON moderation_reports(reporter_did)`) 802 761 803 762 db.Exec(`CREATE TABLE IF NOT EXISTS moderation_actions ( 804 - id ` + autoInc + `, 763 + id SERIAL PRIMARY KEY, 805 764 report_id INTEGER NOT NULL, 806 765 actor_did TEXT NOT NULL, 807 766 action TEXT NOT NULL, 808 767 comment TEXT, 809 - created_at ` + dateType + ` NOT NULL 768 + created_at TIMESTAMP NOT NULL 810 769 )`) 811 770 db.Exec(`CREATE INDEX IF NOT EXISTS idx_mod_actions_report ON moderation_actions(report_id)`) 812 771 } 813 772 814 - autoInc := "INTEGER PRIMARY KEY AUTOINCREMENT" 815 - if db.driver == "postgres" { 816 - autoInc = "SERIAL PRIMARY KEY" 817 - } 818 773 db.Exec(`CREATE TABLE IF NOT EXISTS content_labels ( 819 - id ` + autoInc + `, 774 + id SERIAL PRIMARY KEY, 820 775 src TEXT NOT NULL, 821 776 uri TEXT NOT NULL, 822 777 val TEXT NOT NULL, 823 778 neg INTEGER NOT NULL DEFAULT 0, 824 779 created_by TEXT NOT NULL, 825 - created_at ` + dateType + ` NOT NULL 780 + created_at TIMESTAMP NOT NULL 826 781 )`) 827 782 db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_uri ON content_labels(uri)`) 828 783 db.Exec(`CREATE INDEX IF NOT EXISTS idx_content_labels_src ON content_labels(src)`) ··· 830 785 831 786 func (db *DB) Close() error { 832 787 return db.DB.Close() 833 - } 834 - 835 - func (db *DB) Rebind(query string) string { 836 - if db.driver != "postgres" { 837 - return query 838 - } 839 - 840 - if !strings.Contains(query, "?") { 841 - return query 842 - } 843 - 844 - var builder strings.Builder 845 - builder.Grow(len(query) + 20) 846 - 847 - paramCount := 1 848 - for _, r := range query { 849 - if r == '?' { 850 - fmt.Fprintf(&builder, "$%d", paramCount) 851 - paramCount++ 852 - } else { 853 - builder.WriteRune(r) 854 - } 855 - } 856 - return builder.String() 857 788 } 858 789 859 790 func ParseSelector(selectorJSON *string) (*Selector, error) {
+22
backend/internal/db/pg_helpers.go
··· 1 + package db 2 + 3 + import ( 4 + "database/sql/driver" 5 + "fmt" 6 + "strings" 7 + ) 8 + 9 + type pqStringArray []string 10 + 11 + func (a pqStringArray) Value() (driver.Value, error) { 12 + if a == nil { 13 + return "{}", nil 14 + } 15 + parts := make([]string, len(a)) 16 + for i, s := range a { 17 + escaped := strings.ReplaceAll(s, "\\", "\\\\") 18 + escaped = strings.ReplaceAll(escaped, "\"", "\\\"") 19 + parts[i] = fmt.Sprintf(`"%s"`, escaped) 20 + } 21 + return "{" + strings.Join(parts, ",") + "}", nil 22 + }
+8 -8
backend/internal/db/queries.go
··· 35 35 } 36 36 37 37 func (db *DB) AnnotationExists(uri string) bool { 38 - var count int 39 - db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM annotations WHERE uri = ?`), uri).Scan(&count) 40 - return count > 0 38 + var exists bool 39 + db.QueryRow(`SELECT EXISTS(SELECT 1 FROM annotations WHERE uri = $1)`, uri).Scan(&exists) 40 + return exists 41 41 } 42 42 43 43 func HashURL(rawURL string) string { ··· 71 71 72 72 func (db *DB) GetAuthorByURI(uri string) (string, error) { 73 73 var authorDID string 74 - err := db.QueryRow(db.Rebind(`SELECT author_did FROM annotations WHERE uri = ?`), uri).Scan(&authorDID) 74 + err := db.QueryRow(`SELECT author_did FROM annotations WHERE uri = $1`, uri).Scan(&authorDID) 75 75 if err == nil { 76 76 return authorDID, nil 77 77 } 78 78 79 - err = db.QueryRow(db.Rebind(`SELECT author_did FROM highlights WHERE uri = ?`), uri).Scan(&authorDID) 79 + err = db.QueryRow(`SELECT author_did FROM highlights WHERE uri = $1`, uri).Scan(&authorDID) 80 80 if err == nil { 81 81 return authorDID, nil 82 82 } 83 83 84 - err = db.QueryRow(db.Rebind(`SELECT author_did FROM bookmarks WHERE uri = ?`), uri).Scan(&authorDID) 84 + err = db.QueryRow(`SELECT author_did FROM bookmarks WHERE uri = $1`, uri).Scan(&authorDID) 85 85 if err == nil { 86 86 return authorDID, nil 87 87 } ··· 89 89 return "", fmt.Errorf("uri not found or no author") 90 90 } 91 91 92 - func buildPlaceholders(n int) string { 92 + func buildPlaceholders(n, startAt int) string { 93 93 if n == 0 { 94 94 return "" 95 95 } 96 96 placeholders := make([]string, n) 97 97 for i := range placeholders { 98 - placeholders[i] = "?" 98 + placeholders[i] = fmt.Sprintf("$%d", startAt+i) 99 99 } 100 100 return strings.Join(placeholders, ", ") 101 101 }
+110 -125
backend/internal/db/queries_annotations.go
··· 5 5 ) 6 6 7 7 func (db *DB) CreateAnnotation(a *Annotation) error { 8 - _, err := db.Exec(db.Rebind(` 8 + _, err := db.Exec(` 9 9 INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) 10 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 10 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) 11 11 ON CONFLICT(uri) DO UPDATE SET 12 - motivation = excluded.motivation, 13 - body_value = excluded.body_value, 14 - body_format = excluded.body_format, 15 - body_uri = excluded.body_uri, 16 - target_source = excluded.target_source, 17 - target_hash = excluded.target_hash, 18 - target_title = excluded.target_title, 19 - selector_json = excluded.selector_json, 20 - tags_json = excluded.tags_json, 21 - indexed_at = excluded.indexed_at, 22 - cid = excluded.cid 23 - `), a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) 12 + motivation = EXCLUDED.motivation, 13 + body_value = EXCLUDED.body_value, 14 + body_format = EXCLUDED.body_format, 15 + body_uri = EXCLUDED.body_uri, 16 + target_source = EXCLUDED.target_source, 17 + target_hash = EXCLUDED.target_hash, 18 + target_title = EXCLUDED.target_title, 19 + selector_json = EXCLUDED.selector_json, 20 + tags_json = EXCLUDED.tags_json, 21 + indexed_at = EXCLUDED.indexed_at, 22 + cid = EXCLUDED.cid 23 + `, a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) 24 24 return err 25 25 } 26 26 27 27 func (db *DB) GetAnnotationByURI(uri string) (*Annotation, error) { 28 28 var a Annotation 29 - err := db.QueryRow(db.Rebind(` 29 + err := db.QueryRow(` 30 30 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 31 31 FROM annotations 32 - WHERE uri = ? 33 - `), uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) 32 + WHERE uri = $1 33 + `, uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) 34 34 if err != nil { 35 35 return nil, err 36 36 } ··· 38 38 } 39 39 40 40 func (db *DB) GetAnnotationsByTargetHash(targetHash string, limit, offset int) ([]Annotation, error) { 41 - rows, err := db.Query(db.Rebind(` 41 + rows, err := db.Query(` 42 42 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 43 43 FROM annotations 44 - WHERE target_hash = ? 44 + WHERE target_hash = $1 45 45 ORDER BY created_at DESC 46 - LIMIT ? OFFSET ? 47 - `), targetHash, limit, offset) 46 + LIMIT $2 OFFSET $3 47 + `, targetHash, limit, offset) 48 48 if err != nil { 49 49 return nil, err 50 50 } ··· 54 54 } 55 55 56 56 func (db *DB) GetAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { 57 - rows, err := db.Query(db.Rebind(` 57 + rows, err := db.Query(` 58 58 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 59 59 FROM annotations 60 - WHERE author_did = ? 60 + WHERE author_did = $1 61 61 ORDER BY created_at DESC 62 - LIMIT ? OFFSET ? 63 - `), authorDID, limit, offset) 62 + LIMIT $2 OFFSET $3 63 + `, authorDID, limit, offset) 64 64 if err != nil { 65 65 return nil, err 66 66 } ··· 70 70 } 71 71 72 72 func (db *DB) GetMarginAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { 73 - rows, err := db.Query(db.Rebind(` 73 + rows, err := db.Query(` 74 74 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 75 75 FROM annotations 76 - WHERE author_did = ? AND uri NOT LIKE '%network.cosmik%' 76 + WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' 77 77 ORDER BY created_at DESC 78 - LIMIT ? OFFSET ? 79 - `), authorDID, limit, offset) 78 + LIMIT $2 OFFSET $3 79 + `, authorDID, limit, offset) 80 80 if err != nil { 81 81 return nil, err 82 82 } ··· 86 86 } 87 87 88 88 func (db *DB) GetSembleAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { 89 - rows, err := db.Query(db.Rebind(` 89 + rows, err := db.Query(` 90 90 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 91 91 FROM annotations 92 - WHERE author_did = ? AND uri LIKE '%network.cosmik%' 92 + WHERE author_did = $1 AND uri LIKE '%network.cosmik%' 93 93 ORDER BY created_at DESC 94 - LIMIT ? OFFSET ? 95 - `), authorDID, limit, offset) 94 + LIMIT $2 OFFSET $3 95 + `, authorDID, limit, offset) 96 96 if err != nil { 97 97 return nil, err 98 98 } ··· 102 102 } 103 103 104 104 func (db *DB) GetAnnotationsByMotivation(motivation string, limit, offset int) ([]Annotation, error) { 105 - rows, err := db.Query(db.Rebind(` 105 + rows, err := db.Query(` 106 106 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 107 107 FROM annotations 108 - WHERE motivation = ? 108 + WHERE motivation = $1 109 109 ORDER BY created_at DESC 110 - LIMIT ? OFFSET ? 111 - `), motivation, limit, offset) 110 + LIMIT $2 OFFSET $3 111 + `, motivation, limit, offset) 112 112 if err != nil { 113 113 return nil, err 114 114 } ··· 118 118 } 119 119 120 120 func (db *DB) GetRecentAnnotations(limit, offset int) ([]Annotation, error) { 121 - rows, err := db.Query(db.Rebind(` 121 + rows, err := db.Query(` 122 122 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 123 123 FROM annotations 124 124 ORDER BY created_at DESC 125 - LIMIT ? OFFSET ? 126 - `), limit, offset) 125 + LIMIT $1 OFFSET $2 126 + `, limit, offset) 127 127 if err != nil { 128 128 return nil, err 129 129 } ··· 134 134 135 135 func (db *DB) GetPopularAnnotations(limit, offset int) ([]Annotation, error) { 136 136 since := time.Now().AddDate(0, 0, -14) 137 - rows, err := db.Query(db.Rebind(` 138 - SELECT 139 - a.uri, a.author_did, a.motivation, a.body_value, a.body_format, 140 - a.body_uri, a.target_source, a.target_hash, a.target_title, 137 + rows, err := db.Query(` 138 + SELECT 139 + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, 140 + a.body_uri, a.target_source, a.target_hash, a.target_title, 141 141 a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid 142 142 FROM annotations a 143 - LEFT JOIN ( 144 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 145 - ) l ON l.subject_uri = a.uri 146 - LEFT JOIN ( 147 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 148 - ) r ON r.root_uri = a.uri 149 - WHERE a.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 150 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, a.created_at DESC 151 - LIMIT ? OFFSET ? 152 - `), since, limit, offset) 143 + LEFT JOIN LATERAL ( 144 + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = a.uri 145 + ) l ON true 146 + LEFT JOIN LATERAL ( 147 + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = a.uri 148 + ) r ON true 149 + WHERE a.created_at > $1 AND (l.cnt + r.cnt) > 0 150 + ORDER BY (l.cnt + r.cnt) DESC, a.created_at DESC 151 + LIMIT $2 OFFSET $3 152 + `, since, limit, offset) 153 153 if err != nil { 154 154 return nil, err 155 155 } ··· 161 161 func (db *DB) GetShelvedAnnotations(limit, offset int) ([]Annotation, error) { 162 162 olderThan := time.Now().AddDate(0, 0, -1) 163 163 since := time.Now().AddDate(0, 0, -14) 164 - rows, err := db.Query(db.Rebind(` 165 - SELECT 166 - a.uri, a.author_did, a.motivation, a.body_value, a.body_format, 167 - a.body_uri, a.target_source, a.target_hash, a.target_title, 164 + rows, err := db.Query(` 165 + SELECT 166 + a.uri, a.author_did, a.motivation, a.body_value, a.body_format, 167 + a.body_uri, a.target_source, a.target_hash, a.target_title, 168 168 a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid 169 169 FROM annotations a 170 - LEFT JOIN ( 171 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 172 - ) l ON l.subject_uri = a.uri 173 - LEFT JOIN ( 174 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 175 - ) r ON r.root_uri = a.uri 176 - WHERE a.created_at < ? AND a.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 170 + WHERE a.created_at < $1 AND a.created_at > $2 171 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = a.uri) 172 + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = a.uri) 177 173 ORDER BY RANDOM() 178 - LIMIT ? OFFSET ? 179 - `), olderThan, since, limit, offset) 174 + LIMIT $3 OFFSET $4 175 + `, olderThan, since, limit, offset) 180 176 if err != nil { 181 177 return nil, err 182 178 } ··· 186 182 } 187 183 188 184 func (db *DB) GetMarginAnnotations(limit, offset int) ([]Annotation, error) { 189 - rows, err := db.Query(db.Rebind(` 185 + rows, err := db.Query(` 190 186 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 191 187 FROM annotations 192 188 WHERE uri NOT LIKE '%network.cosmik%' 193 189 ORDER BY created_at DESC 194 - LIMIT ? OFFSET ? 195 - `), limit, offset) 190 + LIMIT $1 OFFSET $2 191 + `, limit, offset) 196 192 if err != nil { 197 193 return nil, err 198 194 } ··· 202 198 } 203 199 204 200 func (db *DB) GetSembleAnnotations(limit, offset int) ([]Annotation, error) { 205 - rows, err := db.Query(db.Rebind(` 201 + rows, err := db.Query(` 206 202 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 207 203 FROM annotations 208 204 WHERE uri LIKE '%network.cosmik%' 209 205 ORDER BY created_at DESC 210 - LIMIT ? OFFSET ? 211 - `), limit, offset) 206 + LIMIT $1 OFFSET $2 207 + `, limit, offset) 212 208 if err != nil { 213 209 return nil, err 214 210 } ··· 218 214 } 219 215 220 216 func (db *DB) GetAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { 221 - pattern := "%\"" + tag + "\"%" 222 - rows, err := db.Query(db.Rebind(` 217 + rows, err := db.Query(` 223 218 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 224 219 FROM annotations 225 - WHERE tags_json LIKE ? 220 + WHERE tags_json::jsonb ? $1 226 221 ORDER BY created_at DESC 227 - LIMIT ? OFFSET ? 228 - `), pattern, limit, offset) 222 + LIMIT $2 OFFSET $3 223 + `, tag, limit, offset) 229 224 if err != nil { 230 225 return nil, err 231 226 } ··· 235 230 } 236 231 237 232 func (db *DB) GetMarginAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { 238 - pattern := "%\"" + tag + "\"%" 239 - rows, err := db.Query(db.Rebind(` 233 + rows, err := db.Query(` 240 234 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 241 235 FROM annotations 242 - WHERE tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' 236 + WHERE tags_json::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' 243 237 ORDER BY created_at DESC 244 - LIMIT ? OFFSET ? 245 - `), pattern, limit, offset) 238 + LIMIT $2 OFFSET $3 239 + `, tag, limit, offset) 246 240 if err != nil { 247 241 return nil, err 248 242 } ··· 252 246 } 253 247 254 248 func (db *DB) GetSembleAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { 255 - pattern := "%\"" + tag + "\"%" 256 - rows, err := db.Query(db.Rebind(` 249 + rows, err := db.Query(` 257 250 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 258 251 FROM annotations 259 - WHERE tags_json LIKE ? AND uri LIKE '%network.cosmik%' 252 + WHERE tags_json::jsonb ? $1 AND uri LIKE '%network.cosmik%' 260 253 ORDER BY created_at DESC 261 - LIMIT ? OFFSET ? 262 - `), pattern, limit, offset) 254 + LIMIT $2 OFFSET $3 255 + `, tag, limit, offset) 263 256 if err != nil { 264 257 return nil, err 265 258 } ··· 269 262 } 270 263 271 264 func (db *DB) DeleteAnnotation(uri string) error { 272 - _, err := db.Exec(db.Rebind(`DELETE FROM annotations WHERE uri = ?`), uri) 265 + _, err := db.Exec(`DELETE FROM annotations WHERE uri = $1`, uri) 273 266 return err 274 267 } 275 268 276 269 func (db *DB) UpdateAnnotation(uri, bodyValue, tagsJSON, cid string) error { 277 - _, err := db.Exec(db.Rebind(` 278 - UPDATE annotations 279 - SET body_value = ?, tags_json = ?, cid = ?, indexed_at = ? 280 - WHERE uri = ? 281 - `), bodyValue, tagsJSON, cid, time.Now(), uri) 270 + _, err := db.Exec(` 271 + UPDATE annotations 272 + SET body_value = $1, tags_json = $2, cid = $3, indexed_at = $4 273 + WHERE uri = $5 274 + `, bodyValue, tagsJSON, cid, time.Now(), uri) 282 275 return err 283 276 } 284 277 285 278 func (db *DB) GetAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { 286 - pattern := "%\"" + tag + "\"%" 287 - rows, err := db.Query(db.Rebind(` 279 + rows, err := db.Query(` 288 280 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 289 281 FROM annotations 290 - WHERE author_did = ? AND tags_json LIKE ? 282 + WHERE author_did = $1 AND tags_json::jsonb ? $2 291 283 ORDER BY created_at DESC 292 - LIMIT ? OFFSET ? 293 - `), authorDID, pattern, limit, offset) 284 + LIMIT $3 OFFSET $4 285 + `, authorDID, tag, limit, offset) 294 286 if err != nil { 295 287 return nil, err 296 288 } ··· 300 292 } 301 293 302 294 func (db *DB) GetMarginAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { 303 - pattern := "%\"" + tag + "\"%" 304 - rows, err := db.Query(db.Rebind(` 295 + rows, err := db.Query(` 305 296 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 306 297 FROM annotations 307 - WHERE author_did = ? AND tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' 298 + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' 308 299 ORDER BY created_at DESC 309 - LIMIT ? OFFSET ? 310 - `), authorDID, pattern, limit, offset) 300 + LIMIT $3 OFFSET $4 301 + `, authorDID, tag, limit, offset) 311 302 if err != nil { 312 303 return nil, err 313 304 } ··· 317 308 } 318 309 319 310 func (db *DB) GetSembleAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { 320 - pattern := "%\"" + tag + "\"%" 321 - rows, err := db.Query(db.Rebind(` 311 + rows, err := db.Query(` 322 312 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 323 313 FROM annotations 324 - WHERE author_did = ? AND tags_json LIKE ? AND uri LIKE '%network.cosmik%' 314 + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri LIKE '%network.cosmik%' 325 315 ORDER BY created_at DESC 326 - LIMIT ? OFFSET ? 327 - `), authorDID, pattern, limit, offset) 316 + LIMIT $3 OFFSET $4 317 + `, authorDID, tag, limit, offset) 328 318 if err != nil { 329 319 return nil, err 330 320 } ··· 334 324 } 335 325 336 326 func (db *DB) GetAnnotationsByAuthorAndTargetHash(authorDID, targetHash string, limit, offset int) ([]Annotation, error) { 337 - rows, err := db.Query(db.Rebind(` 327 + rows, err := db.Query(` 338 328 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 339 329 FROM annotations 340 - WHERE author_did = ? AND target_hash = ? 330 + WHERE author_did = $1 AND target_hash = $2 341 331 ORDER BY created_at DESC 342 - LIMIT ? OFFSET ? 343 - `), authorDID, targetHash, limit, offset) 332 + LIMIT $3 OFFSET $4 333 + `, authorDID, targetHash, limit, offset) 344 334 if err != nil { 345 335 return nil, err 346 336 } ··· 354 344 return []Annotation{}, nil 355 345 } 356 346 357 - query := db.Rebind(` 347 + query := ` 358 348 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 359 349 FROM annotations 360 - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 361 - `) 350 + WHERE uri = ANY($1) 351 + ` 362 352 363 - args := make([]interface{}, len(uris)) 364 - for i, uri := range uris { 365 - args[i] = uri 366 - } 367 - 368 - rows, err := db.Query(query, args...) 353 + rows, err := db.Query(query, pqStringArray(uris)) 369 354 if err != nil { 370 355 return nil, err 371 356 } ··· 375 360 } 376 361 377 362 func (db *DB) GetAnnotationURIs(authorDID string) ([]string, error) { 378 - rows, err := db.Query(db.Rebind(` 379 - SELECT uri FROM annotations WHERE author_did = ? 380 - `), authorDID) 363 + rows, err := db.Query(` 364 + SELECT uri FROM annotations WHERE author_did = $1 365 + `, authorDID) 381 366 if err != nil { 382 367 return nil, err 383 368 }
+117 -247
backend/internal/db/queries_bookmarks.go
··· 5 5 ) 6 6 7 7 func (db *DB) CreateBookmark(b *Bookmark) error { 8 - _, err := db.Exec(db.Rebind(` 8 + _, err := db.Exec(` 9 9 INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) 10 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 10 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) 11 11 ON CONFLICT(uri) DO UPDATE SET 12 - source = excluded.source, 13 - source_hash = excluded.source_hash, 14 - title = excluded.title, 15 - description = excluded.description, 16 - tags_json = excluded.tags_json, 17 - indexed_at = excluded.indexed_at, 18 - cid = excluded.cid 19 - `), b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) 12 + source = EXCLUDED.source, 13 + source_hash = EXCLUDED.source_hash, 14 + title = EXCLUDED.title, 15 + description = EXCLUDED.description, 16 + tags_json = EXCLUDED.tags_json, 17 + indexed_at = EXCLUDED.indexed_at, 18 + cid = EXCLUDED.cid 19 + `, b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) 20 20 return err 21 21 } 22 22 23 23 func (db *DB) GetBookmarkByURI(uri string) (*Bookmark, error) { 24 24 var b Bookmark 25 - err := db.QueryRow(db.Rebind(` 25 + err := db.QueryRow(` 26 26 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 27 27 FROM bookmarks 28 - WHERE uri = ? 29 - `), uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) 28 + WHERE uri = $1 29 + `, uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) 30 30 if err != nil { 31 31 return nil, err 32 32 } ··· 34 34 } 35 35 36 36 func (db *DB) GetRecentBookmarks(limit, offset int) ([]Bookmark, error) { 37 - rows, err := db.Query(db.Rebind(` 37 + rows, err := db.Query(` 38 38 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 39 39 FROM bookmarks 40 40 ORDER BY created_at DESC 41 - LIMIT ? OFFSET ? 42 - `), limit, offset) 41 + LIMIT $1 OFFSET $2 42 + `, limit, offset) 43 43 if err != nil { 44 44 return nil, err 45 45 } 46 46 defer rows.Close() 47 47 48 - var bookmarks []Bookmark 49 - for rows.Next() { 50 - var b Bookmark 51 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 52 - return nil, err 53 - } 54 - bookmarks = append(bookmarks, b) 55 - } 56 - return bookmarks, nil 48 + return scanBookmarks(rows) 57 49 } 58 50 59 51 func (db *DB) GetPopularBookmarks(limit, offset int) ([]Bookmark, error) { 60 52 since := time.Now().AddDate(0, 0, -14) 61 - rows, err := db.Query(db.Rebind(` 62 - SELECT 63 - b.uri, b.author_did, b.source, b.source_hash, b.title, 53 + rows, err := db.Query(` 54 + SELECT 55 + b.uri, b.author_did, b.source, b.source_hash, b.title, 64 56 b.description, b.tags_json, b.created_at, b.indexed_at, b.cid 65 57 FROM bookmarks b 66 - LEFT JOIN ( 67 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 68 - ) l ON l.subject_uri = b.uri 69 - LEFT JOIN ( 70 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 71 - ) r ON r.root_uri = b.uri 72 - WHERE b.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 73 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, b.created_at DESC 74 - LIMIT ? OFFSET ? 75 - `), since, limit, offset) 58 + LEFT JOIN LATERAL ( 59 + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = b.uri 60 + ) l ON true 61 + LEFT JOIN LATERAL ( 62 + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = b.uri 63 + ) r ON true 64 + WHERE b.created_at > $1 AND (l.cnt + r.cnt) > 0 65 + ORDER BY (l.cnt + r.cnt) DESC, b.created_at DESC 66 + LIMIT $2 OFFSET $3 67 + `, since, limit, offset) 76 68 if err != nil { 77 69 return nil, err 78 70 } 79 71 defer rows.Close() 80 72 81 - var bookmarks []Bookmark 82 - for rows.Next() { 83 - var b Bookmark 84 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 85 - return nil, err 86 - } 87 - bookmarks = append(bookmarks, b) 88 - } 89 - return bookmarks, nil 73 + return scanBookmarks(rows) 90 74 } 91 75 92 76 func (db *DB) GetShelvedBookmarks(limit, offset int) ([]Bookmark, error) { 93 77 olderThan := time.Now().AddDate(0, 0, -1) 94 78 since := time.Now().AddDate(0, 0, -14) 95 - rows, err := db.Query(db.Rebind(` 96 - SELECT 97 - b.uri, b.author_did, b.source, b.source_hash, b.title, 79 + rows, err := db.Query(` 80 + SELECT 81 + b.uri, b.author_did, b.source, b.source_hash, b.title, 98 82 b.description, b.tags_json, b.created_at, b.indexed_at, b.cid 99 83 FROM bookmarks b 100 - LEFT JOIN ( 101 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 102 - ) l ON l.subject_uri = b.uri 103 - LEFT JOIN ( 104 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 105 - ) r ON r.root_uri = b.uri 106 - WHERE b.created_at < ? AND b.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 84 + WHERE b.created_at < $1 AND b.created_at > $2 85 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = b.uri) 86 + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = b.uri) 107 87 ORDER BY RANDOM() 108 - LIMIT ? OFFSET ? 109 - `), olderThan, since, limit, offset) 88 + LIMIT $3 OFFSET $4 89 + `, olderThan, since, limit, offset) 110 90 if err != nil { 111 91 return nil, err 112 92 } 113 93 defer rows.Close() 114 94 115 - var bookmarks []Bookmark 116 - for rows.Next() { 117 - var b Bookmark 118 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 119 - return nil, err 120 - } 121 - bookmarks = append(bookmarks, b) 122 - } 123 - return bookmarks, nil 95 + return scanBookmarks(rows) 124 96 } 125 97 126 98 func (db *DB) GetMarginBookmarks(limit, offset int) ([]Bookmark, error) { 127 - rows, err := db.Query(db.Rebind(` 99 + rows, err := db.Query(` 128 100 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 129 101 FROM bookmarks 130 102 WHERE uri NOT LIKE '%network.cosmik%' 131 103 ORDER BY created_at DESC 132 - LIMIT ? OFFSET ? 133 - `), limit, offset) 104 + LIMIT $1 OFFSET $2 105 + `, limit, offset) 134 106 if err != nil { 135 107 return nil, err 136 108 } 137 109 defer rows.Close() 138 110 139 - var bookmarks []Bookmark 140 - for rows.Next() { 141 - var b Bookmark 142 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 143 - return nil, err 144 - } 145 - bookmarks = append(bookmarks, b) 146 - } 147 - return bookmarks, nil 111 + return scanBookmarks(rows) 148 112 } 149 113 150 114 func (db *DB) GetSembleBookmarks(limit, offset int) ([]Bookmark, error) { 151 - rows, err := db.Query(db.Rebind(` 115 + rows, err := db.Query(` 152 116 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 153 117 FROM bookmarks 154 118 WHERE uri LIKE '%network.cosmik%' 155 119 ORDER BY created_at DESC 156 - LIMIT ? OFFSET ? 157 - `), limit, offset) 120 + LIMIT $1 OFFSET $2 121 + `, limit, offset) 158 122 if err != nil { 159 123 return nil, err 160 124 } 161 125 defer rows.Close() 162 126 163 - var bookmarks []Bookmark 164 - for rows.Next() { 165 - var b Bookmark 166 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 167 - return nil, err 168 - } 169 - bookmarks = append(bookmarks, b) 170 - } 171 - return bookmarks, nil 127 + return scanBookmarks(rows) 172 128 } 173 129 174 130 func (db *DB) GetBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { 175 - pattern := "%\"" + tag + "\"%" 176 - rows, err := db.Query(db.Rebind(` 131 + rows, err := db.Query(` 177 132 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 178 133 FROM bookmarks 179 - WHERE tags_json LIKE ? 134 + WHERE tags_json::jsonb ? $1 180 135 ORDER BY created_at DESC 181 - LIMIT ? OFFSET ? 182 - `), pattern, limit, offset) 136 + LIMIT $2 OFFSET $3 137 + `, tag, limit, offset) 183 138 if err != nil { 184 139 return nil, err 185 140 } 186 141 defer rows.Close() 187 142 188 - var bookmarks []Bookmark 189 - for rows.Next() { 190 - var b Bookmark 191 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 192 - return nil, err 193 - } 194 - bookmarks = append(bookmarks, b) 195 - } 196 - return bookmarks, nil 143 + return scanBookmarks(rows) 197 144 } 198 145 199 146 func (db *DB) GetMarginBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { 200 - pattern := "%\"" + tag + "\"%" 201 - rows, err := db.Query(db.Rebind(` 147 + rows, err := db.Query(` 202 148 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 203 149 FROM bookmarks 204 - WHERE tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' 150 + WHERE tags_json::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' 205 151 ORDER BY created_at DESC 206 - LIMIT ? OFFSET ? 207 - `), pattern, limit, offset) 152 + LIMIT $2 OFFSET $3 153 + `, tag, limit, offset) 208 154 if err != nil { 209 155 return nil, err 210 156 } 211 157 defer rows.Close() 212 158 213 - var bookmarks []Bookmark 214 - for rows.Next() { 215 - var b Bookmark 216 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 217 - return nil, err 218 - } 219 - bookmarks = append(bookmarks, b) 220 - } 221 - return bookmarks, nil 159 + return scanBookmarks(rows) 222 160 } 223 161 224 162 func (db *DB) GetSembleBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { 225 - pattern := "%\"" + tag + "\"%" 226 - rows, err := db.Query(db.Rebind(` 163 + rows, err := db.Query(` 227 164 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 228 165 FROM bookmarks 229 - WHERE tags_json LIKE ? AND uri LIKE '%network.cosmik%' 166 + WHERE tags_json::jsonb ? $1 AND uri LIKE '%network.cosmik%' 230 167 ORDER BY created_at DESC 231 - LIMIT ? OFFSET ? 232 - `), pattern, limit, offset) 168 + LIMIT $2 OFFSET $3 169 + `, tag, limit, offset) 233 170 if err != nil { 234 171 return nil, err 235 172 } 236 173 defer rows.Close() 237 174 238 - var bookmarks []Bookmark 239 - for rows.Next() { 240 - var b Bookmark 241 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 242 - return nil, err 243 - } 244 - bookmarks = append(bookmarks, b) 245 - } 246 - return bookmarks, nil 175 + return scanBookmarks(rows) 247 176 } 248 177 249 178 func (db *DB) GetBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { 250 - pattern := "%\"" + tag + "\"%" 251 - rows, err := db.Query(db.Rebind(` 179 + rows, err := db.Query(` 252 180 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 253 181 FROM bookmarks 254 - WHERE author_did = ? AND tags_json LIKE ? 182 + WHERE author_did = $1 AND tags_json::jsonb ? $2 255 183 ORDER BY created_at DESC 256 - LIMIT ? OFFSET ? 257 - `), authorDID, pattern, limit, offset) 184 + LIMIT $3 OFFSET $4 185 + `, authorDID, tag, limit, offset) 258 186 if err != nil { 259 187 return nil, err 260 188 } 261 189 defer rows.Close() 262 190 263 - var bookmarks []Bookmark 264 - for rows.Next() { 265 - var b Bookmark 266 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 267 - return nil, err 268 - } 269 - bookmarks = append(bookmarks, b) 270 - } 271 - return bookmarks, nil 191 + return scanBookmarks(rows) 272 192 } 273 193 274 194 func (db *DB) GetMarginBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { 275 - pattern := "%\"" + tag + "\"%" 276 - rows, err := db.Query(db.Rebind(` 195 + rows, err := db.Query(` 277 196 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 278 197 FROM bookmarks 279 - WHERE author_did = ? AND tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' 198 + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' 280 199 ORDER BY created_at DESC 281 - LIMIT ? OFFSET ? 282 - `), authorDID, pattern, limit, offset) 200 + LIMIT $3 OFFSET $4 201 + `, authorDID, tag, limit, offset) 283 202 if err != nil { 284 203 return nil, err 285 204 } 286 205 defer rows.Close() 287 206 288 - var bookmarks []Bookmark 289 - for rows.Next() { 290 - var b Bookmark 291 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 292 - return nil, err 293 - } 294 - bookmarks = append(bookmarks, b) 295 - } 296 - return bookmarks, nil 207 + return scanBookmarks(rows) 297 208 } 298 209 299 210 func (db *DB) GetSembleBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { 300 - pattern := "%\"" + tag + "\"%" 301 - rows, err := db.Query(db.Rebind(` 211 + rows, err := db.Query(` 302 212 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 303 213 FROM bookmarks 304 - WHERE author_did = ? AND tags_json LIKE ? AND uri LIKE '%network.cosmik%' 214 + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri LIKE '%network.cosmik%' 305 215 ORDER BY created_at DESC 306 - LIMIT ? OFFSET ? 307 - `), authorDID, pattern, limit, offset) 216 + LIMIT $3 OFFSET $4 217 + `, authorDID, tag, limit, offset) 308 218 if err != nil { 309 219 return nil, err 310 220 } 311 221 defer rows.Close() 312 222 313 - var bookmarks []Bookmark 314 - for rows.Next() { 315 - var b Bookmark 316 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 317 - return nil, err 318 - } 319 - bookmarks = append(bookmarks, b) 320 - } 321 - return bookmarks, nil 223 + return scanBookmarks(rows) 322 224 } 323 225 324 226 func (db *DB) GetBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { 325 - rows, err := db.Query(db.Rebind(` 227 + rows, err := db.Query(` 326 228 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 327 229 FROM bookmarks 328 - WHERE author_did = ? 230 + WHERE author_did = $1 329 231 ORDER BY created_at DESC 330 - LIMIT ? OFFSET ? 331 - `), authorDID, limit, offset) 232 + LIMIT $2 OFFSET $3 233 + `, authorDID, limit, offset) 332 234 if err != nil { 333 235 return nil, err 334 236 } 335 237 defer rows.Close() 336 238 337 - var bookmarks []Bookmark 338 - for rows.Next() { 339 - var b Bookmark 340 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 341 - return nil, err 342 - } 343 - bookmarks = append(bookmarks, b) 344 - } 345 - return bookmarks, nil 239 + return scanBookmarks(rows) 346 240 } 347 241 348 242 func (db *DB) GetMarginBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { 349 - rows, err := db.Query(db.Rebind(` 243 + rows, err := db.Query(` 350 244 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 351 245 FROM bookmarks 352 - WHERE author_did = ? AND uri NOT LIKE '%network.cosmik%' 246 + WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' 353 247 ORDER BY created_at DESC 354 - LIMIT ? OFFSET ? 355 - `), authorDID, limit, offset) 248 + LIMIT $2 OFFSET $3 249 + `, authorDID, limit, offset) 356 250 if err != nil { 357 251 return nil, err 358 252 } 359 253 defer rows.Close() 360 254 361 - var bookmarks []Bookmark 362 - for rows.Next() { 363 - var b Bookmark 364 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 365 - return nil, err 366 - } 367 - bookmarks = append(bookmarks, b) 368 - } 369 - return bookmarks, nil 255 + return scanBookmarks(rows) 370 256 } 371 257 372 258 func (db *DB) GetSembleBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { 373 - rows, err := db.Query(db.Rebind(` 259 + rows, err := db.Query(` 374 260 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 375 261 FROM bookmarks 376 - WHERE author_did = ? AND uri LIKE '%network.cosmik%' 262 + WHERE author_did = $1 AND uri LIKE '%network.cosmik%' 377 263 ORDER BY created_at DESC 378 - LIMIT ? OFFSET ? 379 - `), authorDID, limit, offset) 264 + LIMIT $2 OFFSET $3 265 + `, authorDID, limit, offset) 380 266 if err != nil { 381 267 return nil, err 382 268 } 383 269 defer rows.Close() 384 270 385 - var bookmarks []Bookmark 386 - for rows.Next() { 387 - var b Bookmark 388 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 389 - return nil, err 390 - } 391 - bookmarks = append(bookmarks, b) 392 - } 393 - return bookmarks, nil 271 + return scanBookmarks(rows) 394 272 } 395 273 396 274 func (db *DB) DeleteBookmark(uri string) error { 397 - _, err := db.Exec(db.Rebind(`DELETE FROM bookmarks WHERE uri = ?`), uri) 275 + _, err := db.Exec(`DELETE FROM bookmarks WHERE uri = $1`, uri) 398 276 return err 399 277 } 400 278 401 279 func (db *DB) UpdateBookmark(uri, title, description, tagsJSON, cid string) error { 402 - _, err := db.Exec(db.Rebind(` 403 - UPDATE bookmarks 404 - SET title = ?, description = ?, tags_json = ?, cid = ?, indexed_at = ? 405 - WHERE uri = ? 406 - `), title, description, tagsJSON, cid, time.Now(), uri) 280 + _, err := db.Exec(` 281 + UPDATE bookmarks 282 + SET title = $1, description = $2, tags_json = $3, cid = $4, indexed_at = $5 283 + WHERE uri = $6 284 + `, title, description, tagsJSON, cid, time.Now(), uri) 407 285 return err 408 286 } 409 287 ··· 412 290 return []Bookmark{}, nil 413 291 } 414 292 415 - query := db.Rebind(` 293 + rows, err := db.Query(` 416 294 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 417 295 FROM bookmarks 418 - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 419 - `) 420 - 421 - args := make([]interface{}, len(uris)) 422 - for i, uri := range uris { 423 - args[i] = uri 424 - } 425 - 426 - rows, err := db.Query(query, args...) 296 + WHERE uri = ANY($1) 297 + `, pqStringArray(uris)) 427 298 if err != nil { 428 299 return nil, err 429 300 } 430 301 defer rows.Close() 431 302 432 - var bookmarks []Bookmark 433 - for rows.Next() { 434 - var b Bookmark 435 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 436 - return nil, err 437 - } 438 - bookmarks = append(bookmarks, b) 439 - } 440 - return bookmarks, nil 303 + return scanBookmarks(rows) 441 304 } 442 305 443 306 func (db *DB) GetBookmarkURIs(authorDID string) ([]string, error) { 444 - rows, err := db.Query(db.Rebind(` 445 - SELECT uri FROM bookmarks WHERE author_did = ? 446 - `), authorDID) 307 + rows, err := db.Query(` 308 + SELECT uri FROM bookmarks WHERE author_did = $1 309 + `, authorDID) 447 310 if err != nil { 448 311 return nil, err 449 312 } ··· 461 324 } 462 325 463 326 func (db *DB) GetBookmarksByTargetHash(targetHash string, limit, offset int) ([]Bookmark, error) { 464 - rows, err := db.Query(db.Rebind(` 327 + rows, err := db.Query(` 465 328 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 466 329 FROM bookmarks 467 - WHERE source_hash = ? 330 + WHERE source_hash = $1 468 331 ORDER BY created_at DESC 469 - LIMIT ? OFFSET ? 470 - `), targetHash, limit, offset) 332 + LIMIT $2 OFFSET $3 333 + `, targetHash, limit, offset) 471 334 if err != nil { 472 335 return nil, err 473 336 } 474 337 defer rows.Close() 475 338 339 + return scanBookmarks(rows) 340 + } 341 + 342 + func scanBookmarks(rows interface { 343 + Next() bool 344 + Scan(...interface{}) error 345 + }) ([]Bookmark, error) { 476 346 var bookmarks []Bookmark 477 347 for rows.Next() { 478 348 var b Bookmark
+79 -115
backend/internal/db/queries_collections.go
··· 3 3 import "time" 4 4 5 5 func (db *DB) CreateCollection(c *Collection) error { 6 - _, err := db.Exec(db.Rebind(` 6 + _, err := db.Exec(` 7 7 INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) 8 - VALUES (?, ?, ?, ?, ?, ?, ?) 8 + VALUES ($1, $2, $3, $4, $5, $6, $7) 9 9 ON CONFLICT(uri) DO UPDATE SET 10 - name = excluded.name, 11 - description = excluded.description, 12 - icon = excluded.icon, 13 - indexed_at = excluded.indexed_at 14 - `), c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) 10 + name = EXCLUDED.name, 11 + description = EXCLUDED.description, 12 + icon = EXCLUDED.icon, 13 + indexed_at = EXCLUDED.indexed_at 14 + `, c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) 15 15 return err 16 16 } 17 17 18 18 func (db *DB) GetCollectionsByAuthor(authorDID string) ([]Collection, error) { 19 - rows, err := db.Query(db.Rebind(` 19 + rows, err := db.Query(` 20 20 SELECT uri, author_did, name, description, icon, created_at, indexed_at 21 21 FROM collections 22 - WHERE author_did = ? 22 + WHERE author_did = $1 23 23 ORDER BY created_at DESC 24 - `), authorDID) 24 + `, authorDID) 25 25 if err != nil { 26 26 return nil, err 27 27 } ··· 40 40 41 41 func (db *DB) GetCollectionByURI(uri string) (*Collection, error) { 42 42 var c Collection 43 - err := db.QueryRow(db.Rebind(` 43 + err := db.QueryRow(` 44 44 SELECT uri, author_did, name, description, icon, created_at, indexed_at 45 45 FROM collections 46 - WHERE uri = ? 47 - `), uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) 46 + WHERE uri = $1 47 + `, uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) 48 48 if err != nil { 49 49 return nil, err 50 50 } ··· 52 52 } 53 53 54 54 func (db *DB) DeleteCollection(uri string) error { 55 - 56 - db.Exec(db.Rebind(`DELETE FROM collection_items WHERE collection_uri = ?`), uri) 57 - _, err := db.Exec(db.Rebind(`DELETE FROM collections WHERE uri = ?`), uri) 55 + db.Exec(`DELETE FROM collection_items WHERE collection_uri = $1`, uri) 56 + _, err := db.Exec(`DELETE FROM collections WHERE uri = $1`, uri) 58 57 return err 59 58 } 60 59 61 60 func (db *DB) AddToCollection(item *CollectionItem) error { 62 - _, err := db.Exec(db.Rebind(` 61 + _, err := db.Exec(` 63 62 INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) 64 - VALUES (?, ?, ?, ?, ?, ?, ?) 63 + VALUES ($1, $2, $3, $4, $5, $6, $7) 65 64 ON CONFLICT(uri) DO UPDATE SET 66 - position = excluded.position, 67 - indexed_at = excluded.indexed_at 68 - `), item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) 65 + position = EXCLUDED.position, 66 + indexed_at = EXCLUDED.indexed_at 67 + `, item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) 69 68 return err 70 69 } 71 70 72 71 func (db *DB) GetCollectionItems(collectionURI string) ([]CollectionItem, error) { 73 - rows, err := db.Query(db.Rebind(` 72 + rows, err := db.Query(` 74 73 SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 75 74 FROM collection_items 76 - WHERE collection_uri = ? 75 + WHERE collection_uri = $1 77 76 ORDER BY position ASC, created_at DESC 78 - `), collectionURI) 77 + `, collectionURI) 79 78 if err != nil { 80 79 return nil, err 81 80 } ··· 93 92 } 94 93 95 94 func (db *DB) RemoveFromCollection(uri string) error { 96 - _, err := db.Exec(db.Rebind(`DELETE FROM collection_items WHERE uri = ?`), uri) 95 + _, err := db.Exec(`DELETE FROM collection_items WHERE uri = $1`, uri) 97 96 return err 98 97 } 99 98 100 99 func (db *DB) GetRecentCollectionItems(limit, offset int) ([]CollectionItem, error) { 101 - rows, err := db.Query(db.Rebind(` 100 + rows, err := db.Query(` 102 101 SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 103 102 FROM collection_items 104 103 ORDER BY created_at DESC 105 - LIMIT ? OFFSET ? 106 - `), limit, offset) 104 + LIMIT $1 OFFSET $2 105 + `, limit, offset) 107 106 if err != nil { 108 107 return nil, err 109 108 } 110 109 defer rows.Close() 111 110 112 - var items []CollectionItem 113 - for rows.Next() { 114 - var item CollectionItem 115 - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 116 - return nil, err 117 - } 118 - items = append(items, item) 119 - } 120 - return items, nil 111 + return scanCollectionItems(rows) 121 112 } 122 113 123 114 func (db *DB) GetPopularCollectionItems(limit, offset int) ([]CollectionItem, error) { 124 115 since := time.Now().AddDate(0, 0, -14) 125 - rows, err := db.Query(db.Rebind(` 126 - SELECT 127 - c.uri, c.author_did, c.collection_uri, c.annotation_uri, 116 + rows, err := db.Query(` 117 + SELECT 118 + c.uri, c.author_did, c.collection_uri, c.annotation_uri, 128 119 c.position, c.created_at, c.indexed_at 129 120 FROM collection_items c 130 - LEFT JOIN ( 131 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 132 - ) l ON l.subject_uri = c.annotation_uri 133 - LEFT JOIN ( 134 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 135 - ) r ON r.root_uri = c.annotation_uri 136 - WHERE c.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 137 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, c.created_at DESC 138 - LIMIT ? OFFSET ? 139 - `), since, limit, offset) 121 + LEFT JOIN LATERAL ( 122 + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = c.annotation_uri 123 + ) l ON true 124 + LEFT JOIN LATERAL ( 125 + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = c.annotation_uri 126 + ) r ON true 127 + WHERE c.created_at > $1 AND (l.cnt + r.cnt) > 0 128 + ORDER BY (l.cnt + r.cnt) DESC, c.created_at DESC 129 + LIMIT $2 OFFSET $3 130 + `, since, limit, offset) 140 131 if err != nil { 141 132 return nil, err 142 133 } 143 134 defer rows.Close() 144 135 145 - var items []CollectionItem 146 - for rows.Next() { 147 - var item CollectionItem 148 - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 149 - return nil, err 150 - } 151 - items = append(items, item) 152 - } 153 - return items, nil 136 + return scanCollectionItems(rows) 154 137 } 155 138 156 139 func (db *DB) GetShelvedCollectionItems(limit, offset int) ([]CollectionItem, error) { 157 140 olderThan := time.Now().AddDate(0, 0, -1) 158 141 since := time.Now().AddDate(0, 0, -14) 159 - rows, err := db.Query(db.Rebind(` 160 - SELECT 161 - c.uri, c.author_did, c.collection_uri, c.annotation_uri, 142 + rows, err := db.Query(` 143 + SELECT 144 + c.uri, c.author_did, c.collection_uri, c.annotation_uri, 162 145 c.position, c.created_at, c.indexed_at 163 146 FROM collection_items c 164 - LEFT JOIN ( 165 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 166 - ) l ON l.subject_uri = c.annotation_uri 167 - LEFT JOIN ( 168 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 169 - ) r ON r.root_uri = c.annotation_uri 170 - WHERE c.created_at < ? AND c.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 147 + WHERE c.created_at < $1 AND c.created_at > $2 148 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = c.annotation_uri) 149 + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = c.annotation_uri) 171 150 ORDER BY RANDOM() 172 - LIMIT ? OFFSET ? 173 - `), olderThan, since, limit, offset) 151 + LIMIT $3 OFFSET $4 152 + `, olderThan, since, limit, offset) 174 153 if err != nil { 175 154 return nil, err 176 155 } 177 156 defer rows.Close() 178 157 179 - var items []CollectionItem 180 - for rows.Next() { 181 - var item CollectionItem 182 - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 183 - return nil, err 184 - } 185 - items = append(items, item) 186 - } 187 - return items, nil 158 + return scanCollectionItems(rows) 188 159 } 189 160 190 161 func (db *DB) GetCollectionItemsByAuthor(authorDID string) ([]CollectionItem, error) { 191 - rows, err := db.Query(db.Rebind(` 162 + rows, err := db.Query(` 192 163 SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 193 164 FROM collection_items 194 - WHERE author_did = ? 165 + WHERE author_did = $1 195 166 ORDER BY created_at DESC 196 - `), authorDID) 167 + `, authorDID) 197 168 if err != nil { 198 169 return nil, err 199 170 } 200 171 defer rows.Close() 201 172 202 - var items []CollectionItem 203 - for rows.Next() { 204 - var item CollectionItem 205 - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 206 - return nil, err 207 - } 208 - items = append(items, item) 209 - } 210 - return items, nil 173 + return scanCollectionItems(rows) 211 174 } 212 175 213 176 func (db *DB) GetCollectionURIsForAnnotation(annotationURI string) ([]string, error) { 214 - rows, err := db.Query(db.Rebind(` 215 - SELECT collection_uri FROM collection_items WHERE annotation_uri = ? 216 - `), annotationURI) 177 + rows, err := db.Query(` 178 + SELECT collection_uri FROM collection_items WHERE annotation_uri = $1 179 + `, annotationURI) 217 180 if err != nil { 218 181 return nil, err 219 182 } ··· 235 198 return map[string]int{}, nil 236 199 } 237 200 238 - query := db.Rebind(` 201 + rows, err := db.Query(` 239 202 SELECT collection_uri, COUNT(*) 240 203 FROM collection_items 241 - WHERE collection_uri IN (` + buildPlaceholders(len(uris)) + `) 204 + WHERE collection_uri = ANY($1) 242 205 GROUP BY collection_uri 243 - `) 244 - 245 - args := make([]interface{}, len(uris)) 246 - for i, uri := range uris { 247 - args[i] = uri 248 - } 249 - 250 - rows, err := db.Query(query, args...) 206 + `, pqStringArray(uris)) 251 207 if err != nil { 252 208 return nil, err 253 209 } ··· 270 226 return []Collection{}, nil 271 227 } 272 228 273 - query := db.Rebind(` 229 + rows, err := db.Query(` 274 230 SELECT uri, author_did, name, description, icon, created_at, indexed_at 275 231 FROM collections 276 - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 277 - `) 278 - 279 - args := make([]interface{}, len(uris)) 280 - for i, uri := range uris { 281 - args[i] = uri 282 - } 283 - 284 - rows, err := db.Query(query, args...) 232 + WHERE uri = ANY($1) 233 + `, pqStringArray(uris)) 285 234 if err != nil { 286 235 return nil, err 287 236 } ··· 297 246 } 298 247 return collections, nil 299 248 } 249 + 250 + func scanCollectionItems(rows interface { 251 + Next() bool 252 + Scan(...interface{}) error 253 + }) ([]CollectionItem, error) { 254 + var items []CollectionItem 255 + for rows.Next() { 256 + var item CollectionItem 257 + if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 258 + return nil, err 259 + } 260 + items = append(items, item) 261 + } 262 + return items, nil 263 + }
+132 -270
backend/internal/db/queries_highlights.go
··· 5 5 ) 6 6 7 7 func (db *DB) CreateHighlight(h *Highlight) error { 8 - _, err := db.Exec(db.Rebind(` 8 + _, err := db.Exec(` 9 9 INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) 10 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 10 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) 11 11 ON CONFLICT(uri) DO UPDATE SET 12 - target_source = excluded.target_source, 13 - target_hash = excluded.target_hash, 14 - target_title = excluded.target_title, 15 - selector_json = excluded.selector_json, 16 - color = excluded.color, 17 - tags_json = excluded.tags_json, 18 - indexed_at = excluded.indexed_at, 19 - cid = excluded.cid 20 - `), h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) 12 + target_source = EXCLUDED.target_source, 13 + target_hash = EXCLUDED.target_hash, 14 + target_title = EXCLUDED.target_title, 15 + selector_json = EXCLUDED.selector_json, 16 + color = EXCLUDED.color, 17 + tags_json = EXCLUDED.tags_json, 18 + indexed_at = EXCLUDED.indexed_at, 19 + cid = EXCLUDED.cid 20 + `, h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) 21 21 return err 22 22 } 23 23 24 24 func (db *DB) GetHighlightByURI(uri string) (*Highlight, error) { 25 25 var h Highlight 26 - err := db.QueryRow(db.Rebind(` 26 + err := db.QueryRow(` 27 27 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 28 28 FROM highlights 29 - WHERE uri = ? 30 - `), uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) 29 + WHERE uri = $1 30 + `, uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) 31 31 if err != nil { 32 32 return nil, err 33 33 } ··· 35 35 } 36 36 37 37 func (db *DB) GetRecentHighlights(limit, offset int) ([]Highlight, error) { 38 - rows, err := db.Query(db.Rebind(` 38 + rows, err := db.Query(` 39 39 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 40 40 FROM highlights 41 41 ORDER BY created_at DESC 42 - LIMIT ? OFFSET ? 43 - `), limit, offset) 42 + LIMIT $1 OFFSET $2 43 + `, limit, offset) 44 44 if err != nil { 45 45 return nil, err 46 46 } 47 47 defer rows.Close() 48 48 49 - var highlights []Highlight 50 - for rows.Next() { 51 - var h Highlight 52 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 53 - return nil, err 54 - } 55 - highlights = append(highlights, h) 56 - } 57 - return highlights, nil 49 + return scanHighlights(rows) 58 50 } 59 51 60 52 func (db *DB) GetPopularHighlights(limit, offset int) ([]Highlight, error) { 61 53 since := time.Now().AddDate(0, 0, -14) 62 - rows, err := db.Query(db.Rebind(` 63 - SELECT 64 - h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, 54 + rows, err := db.Query(` 55 + SELECT 56 + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, 65 57 h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid 66 58 FROM highlights h 67 - LEFT JOIN ( 68 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 69 - ) l ON l.subject_uri = h.uri 70 - LEFT JOIN ( 71 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 72 - ) r ON r.root_uri = h.uri 73 - WHERE h.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) > 0 74 - ORDER BY (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) DESC, h.created_at DESC 75 - LIMIT ? OFFSET ? 76 - `), since, limit, offset) 59 + LEFT JOIN LATERAL ( 60 + SELECT COUNT(*) as cnt FROM likes WHERE subject_uri = h.uri 61 + ) l ON true 62 + LEFT JOIN LATERAL ( 63 + SELECT COUNT(*) as cnt FROM replies WHERE root_uri = h.uri 64 + ) r ON true 65 + WHERE h.created_at > $1 AND (l.cnt + r.cnt) > 0 66 + ORDER BY (l.cnt + r.cnt) DESC, h.created_at DESC 67 + LIMIT $2 OFFSET $3 68 + `, since, limit, offset) 77 69 if err != nil { 78 70 return nil, err 79 71 } 80 72 defer rows.Close() 81 73 82 - var highlights []Highlight 83 - for rows.Next() { 84 - var h Highlight 85 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 86 - return nil, err 87 - } 88 - highlights = append(highlights, h) 89 - } 90 - return highlights, nil 74 + return scanHighlights(rows) 91 75 } 92 76 93 77 func (db *DB) GetShelvedHighlights(limit, offset int) ([]Highlight, error) { 94 78 olderThan := time.Now().AddDate(0, 0, -1) 95 79 since := time.Now().AddDate(0, 0, -14) 96 - rows, err := db.Query(db.Rebind(` 97 - SELECT 98 - h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, 80 + rows, err := db.Query(` 81 + SELECT 82 + h.uri, h.author_did, h.target_source, h.target_hash, h.target_title, 99 83 h.selector_json, h.color, h.tags_json, h.created_at, h.indexed_at, h.cid 100 84 FROM highlights h 101 - LEFT JOIN ( 102 - SELECT subject_uri, COUNT(*) as cnt FROM likes GROUP BY subject_uri 103 - ) l ON l.subject_uri = h.uri 104 - LEFT JOIN ( 105 - SELECT root_uri, COUNT(*) as cnt FROM replies GROUP BY root_uri 106 - ) r ON r.root_uri = h.uri 107 - WHERE h.created_at < ? AND h.created_at > ? AND (COALESCE(l.cnt, 0) + COALESCE(r.cnt, 0)) = 0 85 + WHERE h.created_at < $1 AND h.created_at > $2 86 + AND NOT EXISTS (SELECT 1 FROM likes WHERE subject_uri = h.uri) 87 + AND NOT EXISTS (SELECT 1 FROM replies WHERE root_uri = h.uri) 108 88 ORDER BY RANDOM() 109 - LIMIT ? OFFSET ? 110 - `), olderThan, since, limit, offset) 89 + LIMIT $3 OFFSET $4 90 + `, olderThan, since, limit, offset) 111 91 if err != nil { 112 92 return nil, err 113 93 } 114 94 defer rows.Close() 115 95 116 - var highlights []Highlight 117 - for rows.Next() { 118 - var h Highlight 119 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 120 - return nil, err 121 - } 122 - highlights = append(highlights, h) 123 - } 124 - return highlights, nil 96 + return scanHighlights(rows) 125 97 } 126 98 127 99 func (db *DB) GetMarginHighlights(limit, offset int) ([]Highlight, error) { 128 - rows, err := db.Query(db.Rebind(` 100 + rows, err := db.Query(` 129 101 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 130 102 FROM highlights 131 103 WHERE uri NOT LIKE '%network.cosmik%' 132 104 ORDER BY created_at DESC 133 - LIMIT ? OFFSET ? 134 - `), limit, offset) 105 + LIMIT $1 OFFSET $2 106 + `, limit, offset) 135 107 if err != nil { 136 108 return nil, err 137 109 } 138 110 defer rows.Close() 139 111 140 - var highlights []Highlight 141 - for rows.Next() { 142 - var h Highlight 143 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 144 - return nil, err 145 - } 146 - highlights = append(highlights, h) 147 - } 148 - return highlights, nil 112 + return scanHighlights(rows) 149 113 } 150 114 151 115 func (db *DB) GetSembleHighlights(limit, offset int) ([]Highlight, error) { 152 - rows, err := db.Query(db.Rebind(` 116 + rows, err := db.Query(` 153 117 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 154 118 FROM highlights 155 119 WHERE uri LIKE '%network.cosmik%' 156 120 ORDER BY created_at DESC 157 - LIMIT ? OFFSET ? 158 - `), limit, offset) 121 + LIMIT $1 OFFSET $2 122 + `, limit, offset) 159 123 if err != nil { 160 124 return nil, err 161 125 } 162 126 defer rows.Close() 163 127 164 - var highlights []Highlight 165 - for rows.Next() { 166 - var h Highlight 167 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 168 - return nil, err 169 - } 170 - highlights = append(highlights, h) 171 - } 172 - return highlights, nil 128 + return scanHighlights(rows) 173 129 } 174 130 175 131 func (db *DB) GetHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { 176 - pattern := "%\"" + tag + "\"%" 177 - rows, err := db.Query(db.Rebind(` 132 + rows, err := db.Query(` 178 133 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 179 134 FROM highlights 180 - WHERE tags_json LIKE ? 135 + WHERE tags_json::jsonb ? $1 181 136 ORDER BY created_at DESC 182 - LIMIT ? OFFSET ? 183 - `), pattern, limit, offset) 137 + LIMIT $2 OFFSET $3 138 + `, tag, limit, offset) 184 139 if err != nil { 185 140 return nil, err 186 141 } 187 142 defer rows.Close() 188 143 189 - var highlights []Highlight 190 - for rows.Next() { 191 - var h Highlight 192 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 193 - return nil, err 194 - } 195 - highlights = append(highlights, h) 196 - } 197 - return highlights, nil 144 + return scanHighlights(rows) 198 145 } 199 146 200 147 func (db *DB) GetMarginHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { 201 - pattern := "%\"" + tag + "\"%" 202 - rows, err := db.Query(db.Rebind(` 148 + rows, err := db.Query(` 203 149 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 204 150 FROM highlights 205 - WHERE tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' 151 + WHERE tags_json::jsonb ? $1 AND uri NOT LIKE '%network.cosmik%' 206 152 ORDER BY created_at DESC 207 - LIMIT ? OFFSET ? 208 - `), pattern, limit, offset) 153 + LIMIT $2 OFFSET $3 154 + `, tag, limit, offset) 209 155 if err != nil { 210 156 return nil, err 211 157 } 212 158 defer rows.Close() 213 159 214 - var highlights []Highlight 215 - for rows.Next() { 216 - var h Highlight 217 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 218 - return nil, err 219 - } 220 - highlights = append(highlights, h) 221 - } 222 - return highlights, nil 160 + return scanHighlights(rows) 223 161 } 224 162 225 163 func (db *DB) GetSembleHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { 226 - pattern := "%\"" + tag + "\"%" 227 - rows, err := db.Query(db.Rebind(` 164 + rows, err := db.Query(` 228 165 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 229 166 FROM highlights 230 - WHERE tags_json LIKE ? AND uri LIKE '%network.cosmik%' 167 + WHERE tags_json::jsonb ? $1 AND uri LIKE '%network.cosmik%' 231 168 ORDER BY created_at DESC 232 - LIMIT ? OFFSET ? 233 - `), pattern, limit, offset) 169 + LIMIT $2 OFFSET $3 170 + `, tag, limit, offset) 234 171 if err != nil { 235 172 return nil, err 236 173 } 237 174 defer rows.Close() 238 175 239 - var highlights []Highlight 240 - for rows.Next() { 241 - var h Highlight 242 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 243 - return nil, err 244 - } 245 - highlights = append(highlights, h) 246 - } 247 - return highlights, nil 176 + return scanHighlights(rows) 248 177 } 249 178 250 179 func (db *DB) GetHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { 251 - pattern := "%\"" + tag + "\"%" 252 - rows, err := db.Query(db.Rebind(` 180 + rows, err := db.Query(` 253 181 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 254 182 FROM highlights 255 - WHERE author_did = ? AND tags_json LIKE ? 183 + WHERE author_did = $1 AND tags_json::jsonb ? $2 256 184 ORDER BY created_at DESC 257 - LIMIT ? OFFSET ? 258 - `), authorDID, pattern, limit, offset) 185 + LIMIT $3 OFFSET $4 186 + `, authorDID, tag, limit, offset) 259 187 if err != nil { 260 188 return nil, err 261 189 } 262 190 defer rows.Close() 263 191 264 - var highlights []Highlight 265 - for rows.Next() { 266 - var h Highlight 267 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 268 - return nil, err 269 - } 270 - highlights = append(highlights, h) 271 - } 272 - return highlights, nil 192 + return scanHighlights(rows) 273 193 } 274 194 275 195 func (db *DB) GetMarginHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { 276 - pattern := "%\"" + tag + "\"%" 277 - rows, err := db.Query(db.Rebind(` 196 + rows, err := db.Query(` 278 197 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 279 198 FROM highlights 280 - WHERE author_did = ? AND tags_json LIKE ? AND uri NOT LIKE '%network.cosmik%' 199 + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri NOT LIKE '%network.cosmik%' 281 200 ORDER BY created_at DESC 282 - LIMIT ? OFFSET ? 283 - `), authorDID, pattern, limit, offset) 201 + LIMIT $3 OFFSET $4 202 + `, authorDID, tag, limit, offset) 284 203 if err != nil { 285 204 return nil, err 286 205 } 287 206 defer rows.Close() 288 207 289 - var highlights []Highlight 290 - for rows.Next() { 291 - var h Highlight 292 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 293 - return nil, err 294 - } 295 - highlights = append(highlights, h) 296 - } 297 - return highlights, nil 208 + return scanHighlights(rows) 298 209 } 299 210 300 211 func (db *DB) GetSembleHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { 301 - pattern := "%\"" + tag + "\"%" 302 - rows, err := db.Query(db.Rebind(` 212 + rows, err := db.Query(` 303 213 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 304 214 FROM highlights 305 - WHERE author_did = ? AND tags_json LIKE ? AND uri LIKE '%network.cosmik%' 215 + WHERE author_did = $1 AND tags_json::jsonb ? $2 AND uri LIKE '%network.cosmik%' 306 216 ORDER BY created_at DESC 307 - LIMIT ? OFFSET ? 308 - `), authorDID, pattern, limit, offset) 217 + LIMIT $3 OFFSET $4 218 + `, authorDID, tag, limit, offset) 309 219 if err != nil { 310 220 return nil, err 311 221 } 312 222 defer rows.Close() 313 223 314 - var highlights []Highlight 315 - for rows.Next() { 316 - var h Highlight 317 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 318 - return nil, err 319 - } 320 - highlights = append(highlights, h) 321 - } 322 - return highlights, nil 224 + return scanHighlights(rows) 323 225 } 324 226 325 227 func (db *DB) GetHighlightsByTargetHash(targetHash string, limit, offset int) ([]Highlight, error) { 326 - rows, err := db.Query(db.Rebind(` 228 + rows, err := db.Query(` 327 229 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 328 230 FROM highlights 329 - WHERE target_hash = ? 231 + WHERE target_hash = $1 330 232 ORDER BY created_at DESC 331 - LIMIT ? OFFSET ? 332 - `), targetHash, limit, offset) 233 + LIMIT $2 OFFSET $3 234 + `, targetHash, limit, offset) 333 235 if err != nil { 334 236 return nil, err 335 237 } 336 238 defer rows.Close() 337 239 338 - var highlights []Highlight 339 - for rows.Next() { 340 - var h Highlight 341 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 342 - return nil, err 343 - } 344 - highlights = append(highlights, h) 345 - } 346 - return highlights, nil 240 + return scanHighlights(rows) 347 241 } 348 242 349 243 func (db *DB) GetHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { 350 - rows, err := db.Query(db.Rebind(` 244 + rows, err := db.Query(` 351 245 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 352 246 FROM highlights 353 - WHERE author_did = ? 247 + WHERE author_did = $1 354 248 ORDER BY created_at DESC 355 - LIMIT ? OFFSET ? 356 - `), authorDID, limit, offset) 249 + LIMIT $2 OFFSET $3 250 + `, authorDID, limit, offset) 357 251 if err != nil { 358 252 return nil, err 359 253 } 360 254 defer rows.Close() 361 255 362 - var highlights []Highlight 363 - for rows.Next() { 364 - var h Highlight 365 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 366 - return nil, err 367 - } 368 - highlights = append(highlights, h) 369 - } 370 - return highlights, nil 256 + return scanHighlights(rows) 371 257 } 372 258 373 259 func (db *DB) GetMarginHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { 374 - rows, err := db.Query(db.Rebind(` 260 + rows, err := db.Query(` 375 261 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 376 262 FROM highlights 377 - WHERE author_did = ? AND uri NOT LIKE '%network.cosmik%' 263 + WHERE author_did = $1 AND uri NOT LIKE '%network.cosmik%' 378 264 ORDER BY created_at DESC 379 - LIMIT ? OFFSET ? 380 - `), authorDID, limit, offset) 265 + LIMIT $2 OFFSET $3 266 + `, authorDID, limit, offset) 381 267 if err != nil { 382 268 return nil, err 383 269 } 384 270 defer rows.Close() 385 271 386 - var highlights []Highlight 387 - for rows.Next() { 388 - var h Highlight 389 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 390 - return nil, err 391 - } 392 - highlights = append(highlights, h) 393 - } 394 - return highlights, nil 272 + return scanHighlights(rows) 395 273 } 396 274 397 275 func (db *DB) GetSembleHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { 398 - rows, err := db.Query(db.Rebind(` 276 + rows, err := db.Query(` 399 277 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 400 278 FROM highlights 401 - WHERE author_did = ? AND uri LIKE '%network.cosmik%' 279 + WHERE author_did = $1 AND uri LIKE '%network.cosmik%' 402 280 ORDER BY created_at DESC 403 - LIMIT ? OFFSET ? 404 - `), authorDID, limit, offset) 281 + LIMIT $2 OFFSET $3 282 + `, authorDID, limit, offset) 405 283 if err != nil { 406 284 return nil, err 407 285 } 408 286 defer rows.Close() 409 287 410 - var highlights []Highlight 411 - for rows.Next() { 412 - var h Highlight 413 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 414 - return nil, err 415 - } 416 - highlights = append(highlights, h) 417 - } 418 - return highlights, nil 288 + return scanHighlights(rows) 419 289 } 420 290 421 291 func (db *DB) GetHighlightsByAuthorAndTargetHash(authorDID, targetHash string, limit, offset int) ([]Highlight, error) { 422 - rows, err := db.Query(db.Rebind(` 292 + rows, err := db.Query(` 423 293 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 424 294 FROM highlights 425 - WHERE author_did = ? AND target_hash = ? 295 + WHERE author_did = $1 AND target_hash = $2 426 296 ORDER BY created_at DESC 427 - LIMIT ? OFFSET ? 428 - `), authorDID, targetHash, limit, offset) 297 + LIMIT $3 OFFSET $4 298 + `, authorDID, targetHash, limit, offset) 429 299 if err != nil { 430 300 return nil, err 431 301 } 432 302 defer rows.Close() 433 303 434 - var highlights []Highlight 435 - for rows.Next() { 436 - var h Highlight 437 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 438 - return nil, err 439 - } 440 - highlights = append(highlights, h) 441 - } 442 - return highlights, nil 304 + return scanHighlights(rows) 443 305 } 444 306 445 307 func (db *DB) DeleteHighlight(uri string) error { 446 - _, err := db.Exec(db.Rebind(`DELETE FROM highlights WHERE uri = ?`), uri) 308 + _, err := db.Exec(`DELETE FROM highlights WHERE uri = $1`, uri) 447 309 return err 448 310 } 449 311 450 312 func (db *DB) UpdateHighlight(uri, color, tagsJSON, cid string) error { 451 - _, err := db.Exec(db.Rebind(` 452 - UPDATE highlights 453 - SET color = ?, tags_json = ?, cid = ?, indexed_at = ? 454 - WHERE uri = ? 455 - `), color, tagsJSON, cid, time.Now(), uri) 313 + _, err := db.Exec(` 314 + UPDATE highlights 315 + SET color = $1, tags_json = $2, cid = $3, indexed_at = $4 316 + WHERE uri = $5 317 + `, color, tagsJSON, cid, time.Now(), uri) 456 318 return err 457 319 } 458 320 ··· 461 323 return []Highlight{}, nil 462 324 } 463 325 464 - query := db.Rebind(` 326 + rows, err := db.Query(` 465 327 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 466 328 FROM highlights 467 - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 468 - `) 469 - 470 - args := make([]interface{}, len(uris)) 471 - for i, uri := range uris { 472 - args[i] = uri 473 - } 474 - 475 - rows, err := db.Query(query, args...) 329 + WHERE uri = ANY($1) 330 + `, pqStringArray(uris)) 476 331 if err != nil { 477 332 return nil, err 478 333 } 479 334 defer rows.Close() 480 335 481 - var highlights []Highlight 482 - for rows.Next() { 483 - var h Highlight 484 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 485 - return nil, err 486 - } 487 - highlights = append(highlights, h) 488 - } 489 - return highlights, nil 336 + return scanHighlights(rows) 490 337 } 491 338 492 339 func (db *DB) GetHighlightURIs(authorDID string) ([]string, error) { 493 - rows, err := db.Query(db.Rebind(` 494 - SELECT uri FROM highlights WHERE author_did = ? 495 - `), authorDID) 340 + rows, err := db.Query(` 341 + SELECT uri FROM highlights WHERE author_did = $1 342 + `, authorDID) 496 343 if err != nil { 497 344 return nil, err 498 345 } ··· 508 355 } 509 356 return uris, nil 510 357 } 358 + 359 + func scanHighlights(rows interface { 360 + Next() bool 361 + Scan(...interface{}) error 362 + }) ([]Highlight, error) { 363 + var highlights []Highlight 364 + for rows.Next() { 365 + var h Highlight 366 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 367 + return nil, err 368 + } 369 + highlights = append(highlights, h) 370 + } 371 + return highlights, nil 372 + }
+15 -83
backend/internal/db/queries_history.go
··· 7 7 ) 8 8 9 9 func (db *DB) SaveEditHistory(uri, recordType, previousContent string, previousCID *string) error { 10 - _, err := db.Exec(db.Rebind(` 10 + _, err := db.Exec(` 11 11 INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) 12 - VALUES (?, ?, ?, ?, ?) 13 - `), uri, recordType, previousContent, previousCID, time.Now()) 12 + VALUES ($1, $2, $3, $4, $5) 13 + `, uri, recordType, previousContent, previousCID, time.Now()) 14 14 return err 15 15 } 16 16 17 17 func (db *DB) GetEditHistory(uri string) ([]EditHistory, error) { 18 - rows, err := db.Query(db.Rebind(` 18 + rows, err := db.Query(` 19 19 SELECT id, uri, record_type, previous_content, previous_cid, edited_at 20 20 FROM edit_history 21 - WHERE uri = ? 21 + WHERE uri = $1 22 22 ORDER BY edited_at DESC 23 - `), uri) 23 + `, uri) 24 24 if err != nil { 25 25 return nil, err 26 26 } ··· 29 29 var history []EditHistory 30 30 for rows.Next() { 31 31 var h EditHistory 32 - var editedAt interface{} 33 - if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &editedAt); err != nil { 32 + if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &h.EditedAt); err != nil { 34 33 return nil, err 35 34 } 36 - 37 - switch v := editedAt.(type) { 38 - case time.Time: 39 - h.EditedAt = v 40 - case []byte: 41 - parsed, err := parseTime(string(v)) 42 - if err != nil { 43 - return nil, err 44 - } 45 - h.EditedAt = parsed 46 - case string: 47 - parsed, err := parseTime(v) 48 - if err != nil { 49 - return nil, err 50 - } 51 - h.EditedAt = parsed 52 - } 53 - 54 35 history = append(history, h) 55 36 } 56 37 return history, nil ··· 61 42 return nil, nil 62 43 } 63 44 64 - query := ` 65 - SELECT uri, MAX(edited_at) as edited_at 66 - FROM edit_history 67 - WHERE uri IN (` 68 - args := make([]interface{}, len(uris)) 69 45 placeholders := make([]string, len(uris)) 70 - 46 + args := make([]interface{}, len(uris)) 71 47 for i, uri := range uris { 72 48 placeholders[i] = fmt.Sprintf("$%d", i+1) 73 49 args[i] = uri 74 50 } 75 51 76 - query += strings.Join(placeholders, ",") + ") GROUP BY uri" 77 - 78 - if db.driver == "sqlite3" { 79 - query = strings.ReplaceAll(query, "$", "?") 80 - placeholders = make([]string, len(uris)) 81 - for i := range uris { 82 - placeholders[i] = "?" 83 - } 84 - query = ` 52 + query := ` 85 53 SELECT uri, MAX(edited_at) as edited_at 86 54 FROM edit_history 87 - WHERE uri IN (` + strings.Join(placeholders, ",") + ") GROUP BY uri" 88 - } 55 + WHERE uri IN (` + strings.Join(placeholders, ",") + `) 56 + GROUP BY uri 57 + ` 89 58 90 - rows, err := db.Query(db.Rebind(query), args...) 59 + rows, err := db.Query(query, args...) 91 60 if err != nil { 92 61 return nil, err 93 62 } ··· 96 65 result := make(map[string]time.Time) 97 66 for rows.Next() { 98 67 var uri string 99 - var editedAt interface{} 68 + var editedAt time.Time 100 69 if err := rows.Scan(&uri, &editedAt); err != nil { 101 70 continue 102 71 } 103 - 104 - var finalTime time.Time 105 - switch v := editedAt.(type) { 106 - case time.Time: 107 - finalTime = v 108 - case []byte: 109 - parsed, err := parseTime(string(v)) 110 - if err != nil { 111 - continue 112 - } 113 - finalTime = parsed 114 - case string: 115 - parsed, err := parseTime(v) 116 - if err != nil { 117 - continue 118 - } 119 - finalTime = parsed 120 - default: 121 - continue 122 - } 123 - 124 - result[uri] = finalTime 72 + result[uri] = editedAt 125 73 } 126 74 127 75 return result, nil 128 76 } 129 - 130 - func parseTime(s string) (time.Time, error) { 131 - formats := []string{ 132 - time.RFC3339, 133 - time.RFC3339Nano, 134 - "2006-01-02 15:04:05.999999999-07:00", 135 - "2006-01-02 15:04:05", 136 - } 137 - 138 - for _, f := range formats { 139 - if t, err := time.Parse(f, s); err == nil { 140 - return t, nil 141 - } 142 - } 143 - return time.Time{}, fmt.Errorf("could not parse time: %s", s) 144 - }
+10 -10
backend/internal/db/queries_keys.go
··· 5 5 ) 6 6 7 7 func (db *DB) CreateAPIKey(key *APIKey) error { 8 - _, err := db.Exec(db.Rebind(` 8 + _, err := db.Exec(` 9 9 INSERT INTO api_keys (id, owner_did, name, key_hash, created_at, uri, cid) 10 - VALUES (?, ?, ?, ?, ?, ?, ?) 10 + VALUES ($1, $2, $3, $4, $5, $6, $7) 11 11 ON CONFLICT (id) DO UPDATE SET 12 12 name = EXCLUDED.name, 13 13 key_hash = EXCLUDED.key_hash, 14 14 uri = EXCLUDED.uri, 15 15 cid = EXCLUDED.cid 16 - `), key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt, key.URI, key.CID) 16 + `, key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt, key.URI, key.CID) 17 17 return err 18 18 } 19 19 20 20 func (db *DB) GetAPIKeysByOwner(ownerDID string) ([]APIKey, error) { 21 - rows, err := db.Query(db.Rebind(` 21 + rows, err := db.Query(` 22 22 SELECT id, owner_did, name, key_hash, created_at, last_used_at 23 23 FROM api_keys 24 - WHERE owner_did = ? 24 + WHERE owner_did = $1 25 25 ORDER BY created_at DESC 26 - `), ownerDID) 26 + `, ownerDID) 27 27 if err != nil { 28 28 return nil, err 29 29 } ··· 42 42 43 43 func (db *DB) GetAPIKeyByHash(keyHash string) (*APIKey, error) { 44 44 var k APIKey 45 - err := db.QueryRow(db.Rebind(` 45 + err := db.QueryRow(` 46 46 SELECT id, owner_did, name, key_hash, created_at, last_used_at 47 47 FROM api_keys 48 - WHERE key_hash = ? 49 - `), keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) 48 + WHERE key_hash = $1 49 + `, keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) 50 50 if err != nil { 51 51 return nil, err 52 52 } ··· 54 54 } 55 55 56 56 func (db *DB) UpdateAPIKeyLastUsed(id string) error { 57 - _, err := db.Exec(db.Rebind(`UPDATE api_keys SET last_used_at = ? WHERE id = ?`), time.Now(), id) 57 + _, err := db.Exec(`UPDATE api_keys SET last_used_at = $1 WHERE id = $2`, time.Now(), id) 58 58 return err 59 59 }
+23 -21
backend/internal/db/queries_likes.go
··· 1 1 package db 2 2 3 + import "fmt" 4 + 3 5 func (db *DB) CreateLike(l *Like) error { 4 - _, err := db.Exec(db.Rebind(` 6 + _, err := db.Exec(` 5 7 INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) 6 - VALUES (?, ?, ?, ?, ?) 8 + VALUES ($1, $2, $3, $4, $5) 7 9 ON CONFLICT(uri) DO NOTHING 8 - `), l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) 10 + `, l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) 9 11 return err 10 12 } 11 13 12 14 func (db *DB) DeleteLike(uri string) error { 13 - _, err := db.Exec(db.Rebind(`DELETE FROM likes WHERE uri = ?`), uri) 15 + _, err := db.Exec(`DELETE FROM likes WHERE uri = $1`, uri) 14 16 return err 15 17 } 16 18 17 19 func (db *DB) GetLikesByAuthor(authorDID string) ([]Like, error) { 18 - rows, err := db.Query(db.Rebind(` 20 + rows, err := db.Query(` 19 21 SELECT uri, author_did, subject_uri, created_at, indexed_at 20 22 FROM likes 21 - WHERE author_did = ? 23 + WHERE author_did = $1 22 24 ORDER BY created_at DESC 23 - `), authorDID) 25 + `, authorDID) 24 26 if err != nil { 25 27 return nil, err 26 28 } ··· 39 41 40 42 func (db *DB) GetLikeCount(subjectURI string) (int, error) { 41 43 var count int 42 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM likes WHERE subject_uri = ?`), subjectURI).Scan(&count) 44 + err := db.QueryRow(`SELECT COUNT(*) FROM likes WHERE subject_uri = $1`, subjectURI).Scan(&count) 43 45 return count, err 44 46 } 45 47 46 48 func (db *DB) GetLikeByUserAndSubject(userDID, subjectURI string) (*Like, error) { 47 49 var like Like 48 - err := db.QueryRow(db.Rebind(` 50 + err := db.QueryRow(` 49 51 SELECT uri, author_did, subject_uri, created_at, indexed_at 50 52 FROM likes 51 - WHERE author_did = ? AND subject_uri = ? 52 - `), userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) 53 + WHERE author_did = $1 AND subject_uri = $2 54 + `, userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) 53 55 if err != nil { 54 56 return nil, err 55 57 } ··· 61 63 return map[string]int{}, nil 62 64 } 63 65 64 - query := db.Rebind(` 65 - SELECT subject_uri, COUNT(*) 66 - FROM likes 67 - WHERE subject_uri IN (` + buildPlaceholders(len(subjectURIs)) + `) 66 + query := ` 67 + SELECT subject_uri, COUNT(*) 68 + FROM likes 69 + WHERE subject_uri IN (` + buildPlaceholders(len(subjectURIs), 1) + `) 68 70 GROUP BY subject_uri 69 - `) 71 + ` 70 72 71 73 args := make([]interface{}, len(subjectURIs)) 72 74 for i, uri := range subjectURIs { ··· 97 99 return map[string]bool{}, nil 98 100 } 99 101 100 - query := db.Rebind(` 101 - SELECT subject_uri 102 - FROM likes 103 - WHERE author_did = ? AND subject_uri IN (` + buildPlaceholders(len(subjectURIs)) + `) 104 - `) 102 + query := fmt.Sprintf(` 103 + SELECT subject_uri 104 + FROM likes 105 + WHERE author_did = $1 AND subject_uri IN (%s) 106 + `, buildPlaceholders(len(subjectURIs), 2)) 105 107 106 108 args := make([]interface{}, len(subjectURIs)+1) 107 109 args[0] = viewerDID
+64 -88
backend/internal/db/queries_moderation.go
··· 1 1 package db 2 2 3 - import "time" 3 + import ( 4 + "fmt" 5 + "strings" 6 + "time" 7 + ) 4 8 5 9 func (db *DB) CreateBlock(actorDID, subjectDID string) error { 6 - query := `INSERT INTO blocks (actor_did, subject_did, created_at) VALUES (?, ?, ?) 7 - ON CONFLICT(actor_did, subject_did) DO NOTHING` 8 - _, err := db.Exec(db.Rebind(query), actorDID, subjectDID, time.Now()) 10 + _, err := db.Exec(` 11 + INSERT INTO blocks (actor_did, subject_did, created_at) VALUES ($1, $2, $3) 12 + ON CONFLICT(actor_did, subject_did) DO NOTHING 13 + `, actorDID, subjectDID, time.Now()) 9 14 return err 10 15 } 11 16 12 17 func (db *DB) DeleteBlock(actorDID, subjectDID string) error { 13 - _, err := db.Exec(db.Rebind(`DELETE FROM blocks WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID) 18 + _, err := db.Exec(`DELETE FROM blocks WHERE actor_did = $1 AND subject_did = $2`, actorDID, subjectDID) 14 19 return err 15 20 } 16 21 17 22 func (db *DB) GetBlocks(actorDID string) ([]Block, error) { 18 - rows, err := db.Query(db.Rebind(`SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = ? ORDER BY created_at DESC`), actorDID) 23 + rows, err := db.Query(`SELECT id, actor_did, subject_did, created_at FROM blocks WHERE actor_did = $1 ORDER BY created_at DESC`, actorDID) 19 24 if err != nil { 20 25 return nil, err 21 26 } ··· 33 38 } 34 39 35 40 func (db *DB) IsBlocked(actorDID, subjectDID string) (bool, error) { 36 - var count int 37 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM blocks WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID).Scan(&count) 38 - return count > 0, err 41 + var exists bool 42 + err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM blocks WHERE actor_did = $1 AND subject_did = $2)`, actorDID, subjectDID).Scan(&exists) 43 + return exists, err 39 44 } 40 45 41 46 func (db *DB) IsBlockedEither(did1, did2 string) (bool, error) { 42 - var count int 43 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM blocks WHERE (actor_did = ? AND subject_did = ?) OR (actor_did = ? AND subject_did = ?)`), did1, did2, did2, did1).Scan(&count) 44 - return count > 0, err 47 + var exists bool 48 + err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM blocks WHERE (actor_did = $1 AND subject_did = $2) OR (actor_did = $2 AND subject_did = $1))`, did1, did2).Scan(&exists) 49 + return exists, err 45 50 } 46 51 47 52 func (db *DB) GetBlockedDIDs(actorDID string) ([]string, error) { 48 - rows, err := db.Query(db.Rebind(`SELECT subject_did FROM blocks WHERE actor_did = ?`), actorDID) 53 + rows, err := db.Query(`SELECT subject_did FROM blocks WHERE actor_did = $1`, actorDID) 49 54 if err != nil { 50 55 return nil, err 51 56 } ··· 63 68 } 64 69 65 70 func (db *DB) GetBlockedByDIDs(actorDID string) ([]string, error) { 66 - rows, err := db.Query(db.Rebind(`SELECT actor_did FROM blocks WHERE subject_did = ?`), actorDID) 71 + rows, err := db.Query(`SELECT actor_did FROM blocks WHERE subject_did = $1`, actorDID) 67 72 if err != nil { 68 73 return nil, err 69 74 } ··· 81 86 } 82 87 83 88 func (db *DB) CreateMute(actorDID, subjectDID string) error { 84 - query := `INSERT INTO mutes (actor_did, subject_did, created_at) VALUES (?, ?, ?) 85 - ON CONFLICT(actor_did, subject_did) DO NOTHING` 86 - _, err := db.Exec(db.Rebind(query), actorDID, subjectDID, time.Now()) 89 + _, err := db.Exec(` 90 + INSERT INTO mutes (actor_did, subject_did, created_at) VALUES ($1, $2, $3) 91 + ON CONFLICT(actor_did, subject_did) DO NOTHING 92 + `, actorDID, subjectDID, time.Now()) 87 93 return err 88 94 } 89 95 90 96 func (db *DB) DeleteMute(actorDID, subjectDID string) error { 91 - _, err := db.Exec(db.Rebind(`DELETE FROM mutes WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID) 97 + _, err := db.Exec(`DELETE FROM mutes WHERE actor_did = $1 AND subject_did = $2`, actorDID, subjectDID) 92 98 return err 93 99 } 94 100 95 101 func (db *DB) GetMutes(actorDID string) ([]Mute, error) { 96 - rows, err := db.Query(db.Rebind(`SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = ? ORDER BY created_at DESC`), actorDID) 102 + rows, err := db.Query(`SELECT id, actor_did, subject_did, created_at FROM mutes WHERE actor_did = $1 ORDER BY created_at DESC`, actorDID) 97 103 if err != nil { 98 104 return nil, err 99 105 } ··· 111 117 } 112 118 113 119 func (db *DB) IsMuted(actorDID, subjectDID string) (bool, error) { 114 - var count int 115 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM mutes WHERE actor_did = ? AND subject_did = ?`), actorDID, subjectDID).Scan(&count) 116 - return count > 0, err 120 + var exists bool 121 + err := db.QueryRow(`SELECT EXISTS(SELECT 1 FROM mutes WHERE actor_did = $1 AND subject_did = $2)`, actorDID, subjectDID).Scan(&exists) 122 + return exists, err 117 123 } 118 124 119 125 func (db *DB) GetMutedDIDs(actorDID string) ([]string, error) { 120 - rows, err := db.Query(db.Rebind(`SELECT subject_did FROM mutes WHERE actor_did = ?`), actorDID) 126 + rows, err := db.Query(`SELECT subject_did FROM mutes WHERE actor_did = $1`, actorDID) 121 127 if err != nil { 122 128 return nil, err 123 129 } ··· 187 193 } 188 194 189 195 func (db *DB) CreateReport(reporterDID, subjectDID string, subjectURI *string, reasonType string, reasonText *string) (int, error) { 190 - query := `INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) 191 - VALUES (?, ?, ?, ?, ?, 'pending', ?)` 192 - 193 - result, err := db.Exec(db.Rebind(query), reporterDID, subjectDID, subjectURI, reasonType, reasonText, time.Now()) 194 - if err != nil { 195 - return 0, err 196 - } 197 - 198 - id, err := result.LastInsertId() 199 - return int(id), err 196 + var id int 197 + err := db.QueryRow(` 198 + INSERT INTO moderation_reports (reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at) 199 + VALUES ($1, $2, $3, $4, $5, 'pending', $6) 200 + RETURNING id 201 + `, reporterDID, subjectDID, subjectURI, reasonType, reasonText, time.Now()).Scan(&id) 202 + return id, err 200 203 } 201 204 202 205 func (db *DB) GetReports(status string, limit, offset int) ([]ModerationReport, error) { 203 206 query := `SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by 204 207 FROM moderation_reports` 205 208 args := []interface{}{} 209 + paramIdx := 1 206 210 207 211 if status != "" { 208 - query += ` WHERE status = ?` 212 + query += ` WHERE status = $1` 209 213 args = append(args, status) 214 + paramIdx = 2 210 215 } 211 216 212 - query += ` ORDER BY created_at DESC LIMIT ? OFFSET ?` 217 + query += ` ORDER BY created_at DESC LIMIT $` + itoa(paramIdx) + ` OFFSET $` + itoa(paramIdx+1) 213 218 args = append(args, limit, offset) 214 219 215 - rows, err := db.Query(db.Rebind(query), args...) 220 + rows, err := db.Query(query, args...) 216 221 if err != nil { 217 222 return nil, err 218 223 } ··· 231 236 232 237 func (db *DB) GetReport(id int) (*ModerationReport, error) { 233 238 var r ModerationReport 234 - err := db.QueryRow(db.Rebind(`SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = ?`), id).Scan( 239 + err := db.QueryRow(`SELECT id, reporter_did, subject_did, subject_uri, reason_type, reason_text, status, created_at, resolved_at, resolved_by FROM moderation_reports WHERE id = $1`, id).Scan( 235 240 &r.ID, &r.ReporterDID, &r.SubjectDID, &r.SubjectURI, &r.ReasonType, &r.ReasonText, &r.Status, &r.CreatedAt, &r.ResolvedAt, &r.ResolvedBy, 236 241 ) 237 242 if err != nil { ··· 241 246 } 242 247 243 248 func (db *DB) ResolveReport(id int, resolvedBy string, status string) error { 244 - _, err := db.Exec(db.Rebind(`UPDATE moderation_reports SET status = ?, resolved_at = ?, resolved_by = ? WHERE id = ?`), status, time.Now(), resolvedBy, id) 249 + _, err := db.Exec(`UPDATE moderation_reports SET status = $1, resolved_at = $2, resolved_by = $3 WHERE id = $4`, status, time.Now(), resolvedBy, id) 245 250 return err 246 251 } 247 252 248 253 func (db *DB) CreateModerationAction(reportID int, actorDID, action string, comment *string) error { 249 - query := `INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES (?, ?, ?, ?, ?)` 250 - _, err := db.Exec(db.Rebind(query), reportID, actorDID, action, comment, time.Now()) 254 + _, err := db.Exec(`INSERT INTO moderation_actions (report_id, actor_did, action, comment, created_at) VALUES ($1, $2, $3, $4, $5)`, reportID, actorDID, action, comment, time.Now()) 251 255 return err 252 256 } 253 257 254 258 func (db *DB) GetReportActions(reportID int) ([]ModerationAction, error) { 255 - rows, err := db.Query(db.Rebind(`SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = ? ORDER BY created_at DESC`), reportID) 259 + rows, err := db.Query(`SELECT id, report_id, actor_did, action, comment, created_at FROM moderation_actions WHERE report_id = $1 ORDER BY created_at DESC`, reportID) 256 260 if err != nil { 257 261 return nil, err 258 262 } ··· 273 277 query := `SELECT COUNT(*) FROM moderation_reports` 274 278 args := []interface{}{} 275 279 if status != "" { 276 - query += ` WHERE status = ?` 280 + query += ` WHERE status = $1` 277 281 args = append(args, status) 278 282 } 279 283 var count int 280 - err := db.QueryRow(db.Rebind(query), args...).Scan(&count) 284 + err := db.QueryRow(query, args...).Scan(&count) 281 285 return count, err 282 286 } 283 287 284 288 func (db *DB) CreateContentLabel(src, uri, val, createdBy string) error { 285 - query := `INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES (?, ?, ?, 0, ?, ?)` 286 - _, err := db.Exec(db.Rebind(query), src, uri, val, createdBy, time.Now()) 289 + _, err := db.Exec(`INSERT INTO content_labels (src, uri, val, neg, created_by, created_at) VALUES ($1, $2, $3, 0, $4, $5)`, src, uri, val, createdBy, time.Now()) 287 290 return err 288 291 } 289 292 290 293 func (db *DB) SyncSelfLabels(authorDID, uri string, labels []string) error { 291 - _, err := db.Exec(db.Rebind(`DELETE FROM content_labels WHERE src = ? AND uri = ? AND created_by = ?`), authorDID, uri, authorDID) 294 + _, err := db.Exec(`DELETE FROM content_labels WHERE src = $1 AND uri = $2 AND created_by = $3`, authorDID, uri, authorDID) 292 295 if err != nil { 293 296 return err 294 297 } ··· 301 304 } 302 305 303 306 func (db *DB) NegateContentLabel(id int) error { 304 - _, err := db.Exec(db.Rebind(`UPDATE content_labels SET neg = 1 WHERE id = ?`), id) 307 + _, err := db.Exec(`UPDATE content_labels SET neg = 1 WHERE id = $1`, id) 305 308 return err 306 309 } 307 310 308 311 func (db *DB) DeleteContentLabel(id int) error { 309 - _, err := db.Exec(db.Rebind(`DELETE FROM content_labels WHERE id = ?`), id) 312 + _, err := db.Exec(`DELETE FROM content_labels WHERE id = $1`, id) 310 313 return err 311 314 } 312 315 ··· 316 319 return result, nil 317 320 } 318 321 319 - placeholders := make([]string, len(uris)) 320 - args := make([]interface{}, len(uris)) 321 - for i, uri := range uris { 322 - placeholders[i] = "?" 323 - args[i] = uri 324 - } 325 - 326 322 query := `SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels 327 - WHERE uri IN (` + joinStrings(placeholders, ",") + `) AND neg = 0` 323 + WHERE uri = ANY($1) AND neg = 0` 324 + args := []interface{}{pqStringArray(uris)} 328 325 329 326 if len(labelerDIDs) > 0 { 330 - srcPlaceholders := make([]string, len(labelerDIDs)) 331 - for i, did := range labelerDIDs { 332 - srcPlaceholders[i] = "?" 333 - args = append(args, did) 334 - } 335 - query += ` AND src IN (` + joinStrings(srcPlaceholders, ",") + `)` 327 + query += ` AND src = ANY($2)` 328 + args = append(args, pqStringArray(labelerDIDs)) 336 329 } 337 330 338 331 query += ` ORDER BY created_at DESC` 339 332 340 - rows, err := db.Query(db.Rebind(query), args...) 333 + rows, err := db.Query(query, args...) 341 334 if err != nil { 342 335 return result, err 343 336 } ··· 359 352 return result, nil 360 353 } 361 354 362 - placeholders := make([]string, len(dids)) 363 - args := make([]interface{}, len(dids)) 364 - for i, did := range dids { 365 - placeholders[i] = "?" 366 - args[i] = did 367 - } 368 - 369 355 query := `SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels 370 - WHERE uri IN (` + joinStrings(placeholders, ",") + `) AND neg = 0` 356 + WHERE uri = ANY($1) AND neg = 0` 357 + args := []interface{}{pqStringArray(dids)} 371 358 372 359 if len(labelerDIDs) > 0 { 373 - srcPlaceholders := make([]string, len(labelerDIDs)) 374 - for i, did := range labelerDIDs { 375 - srcPlaceholders[i] = "?" 376 - args = append(args, did) 377 - } 378 - query += ` AND src IN (` + joinStrings(srcPlaceholders, ",") + `)` 360 + query += ` AND src = ANY($2)` 361 + args = append(args, pqStringArray(labelerDIDs)) 379 362 } 380 363 381 364 query += ` ORDER BY created_at DESC` 382 365 383 - rows, err := db.Query(db.Rebind(query), args...) 366 + rows, err := db.Query(query, args...) 384 367 if err != nil { 385 368 return result, err 386 369 } ··· 397 380 } 398 381 399 382 func (db *DB) GetAllContentLabels(limit, offset int) ([]ContentLabel, error) { 400 - rows, err := db.Query(db.Rebind(`SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT ? OFFSET ?`), limit, offset) 383 + rows, err := db.Query(`SELECT id, src, uri, val, neg, created_by, created_at FROM content_labels ORDER BY created_at DESC LIMIT $1 OFFSET $2`, limit, offset) 401 384 if err != nil { 402 385 return nil, err 403 386 } ··· 414 397 return labels, nil 415 398 } 416 399 417 - func joinStrings(strs []string, sep string) string { 418 - result := "" 419 - for i, s := range strs { 420 - if i > 0 { 421 - result += sep 422 - } 423 - result += s 424 - } 425 - return result 400 + func itoa(i int) string { 401 + return strings.Repeat("", 0) + fmt.Sprintf("%d", i) 426 402 }
+13 -13
backend/internal/db/queries_notifications.go
··· 5 5 ) 6 6 7 7 func (db *DB) CreateNotification(n *Notification) error { 8 - _, err := db.Exec(db.Rebind(` 8 + _, err := db.Exec(` 9 9 INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) 10 - VALUES (?, ?, ?, ?, ?) 11 - `), n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) 10 + VALUES ($1, $2, $3, $4, $5) 11 + `, n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) 12 12 return err 13 13 } 14 14 15 15 func (db *DB) GetNotifications(recipientDID string, limit, offset int) ([]Notification, error) { 16 - rows, err := db.Query(db.Rebind(` 16 + rows, err := db.Query(` 17 17 SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at 18 18 FROM notifications 19 - WHERE recipient_did = ? 19 + WHERE recipient_did = $1 20 20 ORDER BY created_at DESC 21 - LIMIT ? OFFSET ? 22 - `), recipientDID, limit, offset) 21 + LIMIT $2 OFFSET $3 22 + `, recipientDID, limit, offset) 23 23 if err != nil { 24 24 return nil, err 25 25 } ··· 38 38 39 39 func (db *DB) GetUnreadNotificationCount(recipientDID string) (int, error) { 40 40 var count int 41 - err := db.QueryRow(db.Rebind(` 42 - SELECT COUNT(*) FROM notifications WHERE recipient_did = ? AND read_at IS NULL 43 - `), recipientDID).Scan(&count) 41 + err := db.QueryRow(` 42 + SELECT COUNT(*) FROM notifications WHERE recipient_did = $1 AND read_at IS NULL 43 + `, recipientDID).Scan(&count) 44 44 return count, err 45 45 } 46 46 47 47 func (db *DB) MarkNotificationsRead(recipientDID string) error { 48 - _, err := db.Exec(db.Rebind(` 49 - UPDATE notifications SET read_at = ? WHERE recipient_did = ? AND read_at IS NULL 50 - `), time.Now(), recipientDID) 48 + _, err := db.Exec(` 49 + UPDATE notifications SET read_at = $1 WHERE recipient_did = $2 AND read_at IS NULL 50 + `, time.Now(), recipientDID) 51 51 return err 52 52 }
+27 -35
backend/internal/db/queries_recommendations.go
··· 55 55 } 56 56 57 57 func (db *DB) MigrateRecommendations() error { 58 - dateType := "TIMESTAMP" 59 - if db.driver == "sqlite3" { 60 - dateType = "DATETIME" 61 - } 62 - 63 58 _, err := db.Exec(` 64 59 CREATE TABLE IF NOT EXISTS document_embeddings ( 65 60 document_uri TEXT PRIMARY KEY, 66 61 embedding TEXT NOT NULL, 67 - updated_at ` + dateType + ` NOT NULL 62 + updated_at TIMESTAMP NOT NULL 68 63 )`) 69 64 if err != nil { 70 65 return fmt.Errorf("create document_embeddings table: %w", err) ··· 76 71 author_did TEXT NOT NULL, 77 72 document_uri TEXT, 78 73 embedding TEXT NOT NULL, 79 - updated_at ` + dateType + ` NOT NULL 74 + updated_at TIMESTAMP NOT NULL 80 75 )`) 81 76 if err != nil { 82 77 return fmt.Errorf("create annotation_embeddings table: %w", err) ··· 90 85 embedding TEXT NOT NULL, 91 86 tag_affinities TEXT DEFAULT '{}', 92 87 annotation_count INTEGER NOT NULL DEFAULT 0, 93 - updated_at ` + dateType + ` NOT NULL 88 + updated_at TIMESTAMP NOT NULL 94 89 )`) 95 90 if err != nil { 96 91 return fmt.Errorf("create user_profiles table: %w", err) ··· 154 149 func (db *DB) GetDocumentByCanonicalURL(canonicalURL string) (*Document, error) { 155 150 var d Document 156 151 err := db.QueryRow( 157 - `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at 152 + `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at 158 153 FROM documents WHERE canonical_url = $1`, 159 154 canonicalURL, 160 155 ).Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt) ··· 167 162 func (db *DB) GetDocumentByURI(uri string) (*Document, error) { 168 163 var d Document 169 164 err := db.QueryRow( 170 - `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at 165 + `SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at 171 166 FROM documents WHERE uri = $1`, 172 167 uri, 173 168 ).Scan(&d.URI, &d.AuthorDID, &d.Site, &d.Path, &d.Title, &d.Description, &d.TextContent, &d.TagsJSON, &d.CanonicalURL, &d.PublishedAt, &d.IndexedAt) ··· 178 173 } 179 174 180 175 func (db *DB) GetDocumentsWithoutEmbeddings(limit int) ([]Document, error) { 181 - rows, err := db.Query(db.Rebind(` 176 + rows, err := db.Query(` 182 177 SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at 183 178 FROM documents d 184 179 LEFT JOIN document_embeddings de ON d.uri = de.document_uri 185 180 WHERE de.document_uri IS NULL 186 181 ORDER BY d.indexed_at DESC 187 - LIMIT ? 188 - `), limit) 182 + LIMIT $1 183 + `, limit) 189 184 if err != nil { 190 185 return nil, err 191 186 } ··· 194 189 } 195 190 196 191 func (db *DB) GetAnnotationsWithoutEmbeddings(limit int) ([]Annotation, error) { 197 - rows, err := db.Query(db.Rebind(` 192 + rows, err := db.Query(` 198 193 SELECT a.uri, a.author_did, a.motivation, a.body_value, a.body_format, a.body_uri, a.target_source, a.target_hash, a.target_title, a.selector_json, a.tags_json, a.created_at, a.indexed_at, a.cid 199 194 FROM annotations a 200 195 LEFT JOIN annotation_embeddings ae ON a.uri = ae.annotation_uri 201 196 WHERE ae.annotation_uri IS NULL AND a.motivation IN ('commenting', 'highlighting') 202 197 ORDER BY a.created_at DESC 203 - LIMIT ? 204 - `), limit) 198 + LIMIT $1 199 + `, limit) 205 200 if err != nil { 206 201 return nil, err 207 202 } ··· 219 214 } 220 215 221 216 func (db *DB) GetHighlightsWithoutEmbeddings(limit int) ([]HighlightForEmbedding, error) { 222 - rows, err := db.Query(db.Rebind(` 217 + rows, err := db.Query(` 223 218 SELECT h.uri, h.author_did, h.target_source, h.target_title, h.selector_json, h.tags_json 224 219 FROM highlights h 225 220 LEFT JOIN annotation_embeddings ae ON h.uri = ae.annotation_uri 226 221 WHERE ae.annotation_uri IS NULL 227 222 ORDER BY h.created_at DESC 228 - LIMIT ? 229 - `), limit) 223 + LIMIT $1 224 + `, limit) 230 225 if err != nil { 231 226 return nil, err 232 227 } ··· 276 271 } 277 272 278 273 func (db *DB) GetRecentDocuments(limit, offset int) ([]Document, error) { 279 - rows, err := db.Query(db.Rebind(` 274 + rows, err := db.Query(` 280 275 SELECT uri, author_did, site, path, title, description, text_content, tags_json, canonical_url, published_at, indexed_at 281 276 FROM documents 282 277 ORDER BY published_at DESC 283 - LIMIT ? OFFSET ? 284 - `), limit, offset) 278 + LIMIT $1 OFFSET $2 279 + `, limit, offset) 285 280 if err != nil { 286 281 return nil, err 287 282 } ··· 290 285 } 291 286 292 287 func (db *DB) GetPopularDocuments(limit, offset int) ([]Document, error) { 293 - rows, err := db.Query(db.Rebind(` 288 + rows, err := db.Query(` 294 289 SELECT d.uri, d.author_did, d.site, d.path, d.title, d.description, d.text_content, d.tags_json, d.canonical_url, d.published_at, d.indexed_at 295 290 FROM documents d 296 291 LEFT JOIN annotations a ON a.target_source = d.canonical_url 297 292 GROUP BY d.uri 298 293 ORDER BY COUNT(a.uri) DESC, d.published_at DESC 299 - LIMIT ? OFFSET ? 300 - `), limit, offset) 294 + LIMIT $1 OFFSET $2 295 + `, limit, offset) 301 296 if err != nil { 302 297 return nil, err 303 298 } ··· 386 381 387 382 func (db *DB) GetRecentAnnotationEmbeddingsByAuthor(authorDID string, limit int) ([]AnnotationEmbedding, error) { 388 383 rows, err := db.Query( 389 - db.Rebind(`SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = ? ORDER BY updated_at DESC LIMIT ?`), 384 + `SELECT annotation_uri, author_did, document_uri, embedding, updated_at FROM annotation_embeddings WHERE author_did = $1 ORDER BY updated_at DESC LIMIT $2`, 390 385 authorDID, limit, 391 386 ) 392 387 if err != nil { ··· 422 417 } 423 418 424 419 func (db *DB) GetCandidateDocuments(userDID string, limit int) ([]CandidateDocument, error) { 425 - // Note: We use NOT LIKE instead of !~* for cross-database compatibility and performance. 426 - // The engagement count sub-select is also constrained to recent elements if possible, but 427 - // for now we just optimize the regex and exact grouping. 428 - rows, err := db.Query(db.Rebind(` 429 - SELECT 420 + rows, err := db.Query(` 421 + SELECT 430 422 d.uri, d.author_did, d.site, d.path, d.title, d.description, d.tags_json, 431 423 d.canonical_url, d.published_at, de.embedding, 432 424 COALESCE(eng.cnt, 0) AS engagement ··· 439 431 GROUP BY document_uri 440 432 ) eng ON eng.document_uri = d.uri 441 433 LEFT JOIN publications p ON d.site = p.uri OR d.site = p.url 442 - WHERE d.author_did != ? 434 + WHERE d.author_did != $1 443 435 AND (p.show_in_discover IS NULL OR p.show_in_discover = true) 444 436 AND LENGTH(d.title) > 15 445 437 AND (LENGTH(COALESCE(d.description, '')) >= 30 OR LENGTH(COALESCE(d.text_content, '')) >= 100) ··· 453 445 AND LOWER(d.title) NOT LIKE '%placeholder%' 454 446 AND d.uri NOT IN ( 455 447 SELECT DISTINCT document_uri FROM annotation_embeddings 456 - WHERE author_did = ? AND document_uri IS NOT NULL 448 + WHERE author_did = $2 AND document_uri IS NOT NULL 457 449 ) 458 450 ORDER BY d.published_at DESC 459 - LIMIT ? 460 - `), userDID, userDID, limit) 451 + LIMIT $3 452 + `, userDID, userDID, limit) 461 453 if err != nil { 462 454 return nil, fmt.Errorf("candidate query: %w", err) 463 455 }
+40 -69
backend/internal/db/queries_replies.go
··· 1 1 package db 2 2 3 3 func (db *DB) CreateReply(r *Reply) error { 4 - _, err := db.Exec(db.Rebind(` 4 + _, err := db.Exec(` 5 5 INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) 6 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) 6 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) 7 7 ON CONFLICT(uri) DO UPDATE SET 8 - text = excluded.text, 9 - format = excluded.format, 10 - indexed_at = excluded.indexed_at, 11 - cid = excluded.cid 12 - `), r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) 8 + text = EXCLUDED.text, 9 + format = EXCLUDED.format, 10 + indexed_at = EXCLUDED.indexed_at, 11 + cid = EXCLUDED.cid 12 + `, r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) 13 13 return err 14 14 } 15 15 16 16 func (db *DB) GetRepliesByRoot(rootURI string) ([]Reply, error) { 17 - rows, err := db.Query(db.Rebind(` 17 + rows, err := db.Query(` 18 18 SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 19 19 FROM replies 20 - WHERE root_uri = ? 20 + WHERE root_uri = $1 21 21 ORDER BY created_at ASC 22 - `), rootURI) 22 + `, rootURI) 23 23 if err != nil { 24 24 return nil, err 25 25 } 26 26 defer rows.Close() 27 27 28 - var replies []Reply 29 - for rows.Next() { 30 - var r Reply 31 - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 32 - return nil, err 33 - } 34 - replies = append(replies, r) 35 - } 36 - return replies, nil 28 + return scanReplies(rows) 37 29 } 38 30 39 31 func (db *DB) GetReplyByURI(uri string) (*Reply, error) { 40 32 var r Reply 41 - err := db.QueryRow(db.Rebind(` 33 + err := db.QueryRow(` 42 34 SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 43 35 FROM replies 44 - WHERE uri = ? 45 - `), uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) 36 + WHERE uri = $1 37 + `, uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) 46 38 if err != nil { 47 39 return nil, err 48 40 } ··· 50 42 } 51 43 52 44 func (db *DB) DeleteReply(uri string) error { 53 - _, err := db.Exec(db.Rebind(`DELETE FROM replies WHERE uri = ?`), uri) 45 + _, err := db.Exec(`DELETE FROM replies WHERE uri = $1`, uri) 54 46 return err 55 47 } 56 48 57 49 func (db *DB) GetRepliesByAuthor(authorDID string) ([]Reply, error) { 58 - rows, err := db.Query(db.Rebind(` 50 + rows, err := db.Query(` 59 51 SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 60 52 FROM replies 61 - WHERE author_did = ? 53 + WHERE author_did = $1 62 54 ORDER BY created_at DESC 63 - `), authorDID) 55 + `, authorDID) 64 56 if err != nil { 65 57 return nil, err 66 58 } 67 59 defer rows.Close() 68 60 69 - var replies []Reply 70 - for rows.Next() { 71 - var r Reply 72 - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 73 - return nil, err 74 - } 75 - replies = append(replies, r) 76 - } 77 - return replies, nil 61 + return scanReplies(rows) 78 62 } 79 63 80 64 func (db *DB) GetOrphanedRepliesByAuthor(authorDID string) ([]Reply, error) { 81 - rows, err := db.Query(db.Rebind(` 65 + rows, err := db.Query(` 82 66 SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid 83 67 FROM replies r 84 68 LEFT JOIN annotations a ON r.root_uri = a.uri 85 - WHERE r.author_did = ? AND a.uri IS NULL 86 - `), authorDID) 69 + WHERE r.author_did = $1 AND a.uri IS NULL 70 + `, authorDID) 87 71 if err != nil { 88 72 return nil, err 89 73 } 90 74 defer rows.Close() 91 75 92 - var replies []Reply 93 - for rows.Next() { 94 - var r Reply 95 - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 96 - return nil, err 97 - } 98 - replies = append(replies, r) 99 - } 100 - return replies, nil 76 + return scanReplies(rows) 101 77 } 102 78 103 79 func (db *DB) GetReplyCount(rootURI string) (int, error) { 104 80 var count int 105 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM replies WHERE root_uri = ?`), rootURI).Scan(&count) 81 + err := db.QueryRow(`SELECT COUNT(*) FROM replies WHERE root_uri = $1`, rootURI).Scan(&count) 106 82 return count, err 107 83 } 108 84 ··· 111 87 return map[string]int{}, nil 112 88 } 113 89 114 - query := db.Rebind(` 115 - SELECT root_uri, COUNT(*) 116 - FROM replies 117 - WHERE root_uri IN (` + buildPlaceholders(len(rootURIs)) + `) 90 + query := ` 91 + SELECT root_uri, COUNT(*) 92 + FROM replies 93 + WHERE root_uri = ANY($1) 118 94 GROUP BY root_uri 119 - `) 120 - 121 - args := make([]interface{}, len(rootURIs)) 122 - for i, uri := range rootURIs { 123 - args[i] = uri 124 - } 95 + ` 125 96 126 - rows, err := db.Query(query, args...) 97 + rows, err := db.Query(query, pqStringArray(rootURIs)) 127 98 if err != nil { 128 99 return nil, err 129 100 } ··· 147 118 return []Reply{}, nil 148 119 } 149 120 150 - query := db.Rebind(` 121 + rows, err := db.Query(` 151 122 SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 152 123 FROM replies 153 - WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 154 - `) 155 - 156 - args := make([]interface{}, len(uris)) 157 - for i, uri := range uris { 158 - args[i] = uri 159 - } 160 - 161 - rows, err := db.Query(query, args...) 124 + WHERE uri = ANY($1) 125 + `, pqStringArray(uris)) 162 126 if err != nil { 163 127 return nil, err 164 128 } 165 129 defer rows.Close() 166 130 131 + return scanReplies(rows) 132 + } 133 + 134 + func scanReplies(rows interface { 135 + Next() bool 136 + Scan(...interface{}) error 137 + }) ([]Reply, error) { 167 138 var replies []Reply 168 139 for rows.Next() { 169 140 var r Reply
+41 -48
backend/internal/db/queries_search.go
··· 1 1 package db 2 2 3 + import "strings" 4 + 5 + func escapeLike(s string) string { 6 + s = strings.ReplaceAll(s, "\\", "\\\\") 7 + s = strings.ReplaceAll(s, "%", "\\%") 8 + s = strings.ReplaceAll(s, "_", "\\_") 9 + return s 10 + } 11 + 3 12 func (db *DB) SearchAnnotations(query string, authorDID string, limit, offset int) ([]Annotation, error) { 4 - pattern := "%" + query + "%" 13 + pattern := "%" + escapeLike(query) + "%" 5 14 6 15 var baseQuery string 7 16 var args []interface{} 8 17 9 18 if authorDID != "" { 10 - baseQuery = db.Rebind(` 19 + baseQuery = ` 11 20 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 12 21 FROM annotations 13 - WHERE author_did = ? 14 - AND (body_value LIKE ? OR target_source LIKE ? OR target_title LIKE ? OR tags_json LIKE ? OR selector_json LIKE ?) 22 + WHERE author_did = $1 23 + AND (body_value ILIKE $2 OR target_source ILIKE $3 OR target_title ILIKE $4 OR tags_json ILIKE $5 OR selector_json ILIKE $6) 15 24 ORDER BY created_at DESC 16 - LIMIT ? OFFSET ? 17 - `) 25 + LIMIT $7 OFFSET $8 26 + ` 18 27 args = []interface{}{authorDID, pattern, pattern, pattern, pattern, pattern, limit, offset} 19 28 } else { 20 - baseQuery = db.Rebind(` 29 + baseQuery = ` 21 30 SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 22 31 FROM annotations 23 - WHERE body_value LIKE ? OR target_source LIKE ? OR target_title LIKE ? OR tags_json LIKE ? OR selector_json LIKE ? 32 + WHERE body_value ILIKE $1 OR target_source ILIKE $2 OR target_title ILIKE $3 OR tags_json ILIKE $4 OR selector_json ILIKE $5 24 33 ORDER BY created_at DESC 25 - LIMIT ? OFFSET ? 26 - `) 34 + LIMIT $6 OFFSET $7 35 + ` 27 36 args = []interface{}{pattern, pattern, pattern, pattern, pattern, limit, offset} 28 37 } 29 38 ··· 37 46 } 38 47 39 48 func (db *DB) SearchHighlights(query string, authorDID string, limit, offset int) ([]Highlight, error) { 40 - pattern := "%" + query + "%" 49 + pattern := "%" + escapeLike(query) + "%" 41 50 42 51 var baseQuery string 43 52 var args []interface{} 44 53 45 54 if authorDID != "" { 46 - baseQuery = db.Rebind(` 55 + baseQuery = ` 47 56 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 48 57 FROM highlights 49 - WHERE author_did = ? 50 - AND (target_source LIKE ? OR target_title LIKE ? OR selector_json LIKE ? OR tags_json LIKE ?) 58 + WHERE author_did = $1 59 + AND (target_source ILIKE $2 OR target_title ILIKE $3 OR selector_json ILIKE $4 OR tags_json ILIKE $5) 51 60 ORDER BY created_at DESC 52 - LIMIT ? OFFSET ? 53 - `) 61 + LIMIT $6 OFFSET $7 62 + ` 54 63 args = []interface{}{authorDID, pattern, pattern, pattern, pattern, limit, offset} 55 64 } else { 56 - baseQuery = db.Rebind(` 65 + baseQuery = ` 57 66 SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 58 67 FROM highlights 59 - WHERE target_source LIKE ? OR target_title LIKE ? OR selector_json LIKE ? OR tags_json LIKE ? 68 + WHERE target_source ILIKE $1 OR target_title ILIKE $2 OR selector_json ILIKE $3 OR tags_json ILIKE $4 60 69 ORDER BY created_at DESC 61 - LIMIT ? OFFSET ? 62 - `) 70 + LIMIT $5 OFFSET $6 71 + ` 63 72 args = []interface{}{pattern, pattern, pattern, pattern, limit, offset} 64 73 } 65 74 ··· 69 78 } 70 79 defer rows.Close() 71 80 72 - var highlights []Highlight 73 - for rows.Next() { 74 - var h Highlight 75 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 76 - return nil, err 77 - } 78 - highlights = append(highlights, h) 79 - } 80 - return highlights, nil 81 + return scanHighlights(rows) 81 82 } 82 83 83 84 func (db *DB) SearchBookmarks(query string, authorDID string, limit, offset int) ([]Bookmark, error) { 84 - pattern := "%" + query + "%" 85 + pattern := "%" + escapeLike(query) + "%" 85 86 86 87 var baseQuery string 87 88 var args []interface{} 88 89 89 90 if authorDID != "" { 90 - baseQuery = db.Rebind(` 91 + baseQuery = ` 91 92 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 92 93 FROM bookmarks 93 - WHERE author_did = ? 94 - AND (source LIKE ? OR title LIKE ? OR description LIKE ? OR tags_json LIKE ?) 94 + WHERE author_did = $1 95 + AND (source ILIKE $2 OR title ILIKE $3 OR description ILIKE $4 OR tags_json ILIKE $5) 95 96 ORDER BY created_at DESC 96 - LIMIT ? OFFSET ? 97 - `) 97 + LIMIT $6 OFFSET $7 98 + ` 98 99 args = []interface{}{authorDID, pattern, pattern, pattern, pattern, limit, offset} 99 100 } else { 100 - baseQuery = db.Rebind(` 101 + baseQuery = ` 101 102 SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 102 103 FROM bookmarks 103 - WHERE source LIKE ? OR title LIKE ? OR description LIKE ? OR tags_json LIKE ? 104 + WHERE source ILIKE $1 OR title ILIKE $2 OR description ILIKE $3 OR tags_json ILIKE $4 104 105 ORDER BY created_at DESC 105 - LIMIT ? OFFSET ? 106 - `) 106 + LIMIT $5 OFFSET $6 107 + ` 107 108 args = []interface{}{pattern, pattern, pattern, pattern, limit, offset} 108 109 } 109 110 ··· 113 114 } 114 115 defer rows.Close() 115 116 116 - var bookmarks []Bookmark 117 - for rows.Next() { 118 - var b Bookmark 119 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 120 - return nil, err 121 - } 122 - bookmarks = append(bookmarks, b) 123 - } 124 - return bookmarks, nil 117 + return scanBookmarks(rows) 125 118 }
+12 -12
backend/internal/db/queries_sessions.go
··· 5 5 ) 6 6 7 7 func (db *DB) SaveSession(id, did, handle, accessToken, refreshToken, dpopKey string, expiresAt time.Time) error { 8 - _, err := db.Exec(db.Rebind(` 8 + _, err := db.Exec(` 9 9 INSERT INTO sessions (id, did, handle, access_token, refresh_token, dpop_key, created_at, expires_at) 10 - VALUES (?, ?, ?, ?, ?, ?, ?, ?) 10 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 11 11 ON CONFLICT(id) DO UPDATE SET 12 - access_token = excluded.access_token, 13 - refresh_token = excluded.refresh_token, 14 - dpop_key = excluded.dpop_key, 15 - expires_at = excluded.expires_at 16 - `), id, did, handle, accessToken, refreshToken, dpopKey, time.Now(), expiresAt) 12 + access_token = EXCLUDED.access_token, 13 + refresh_token = EXCLUDED.refresh_token, 14 + dpop_key = EXCLUDED.dpop_key, 15 + expires_at = EXCLUDED.expires_at 16 + `, id, did, handle, accessToken, refreshToken, dpopKey, time.Now(), expiresAt) 17 17 return err 18 18 } 19 19 20 20 func (db *DB) GetSession(id string) (did, handle, accessToken, refreshToken, dpopKey string, err error) { 21 - err = db.QueryRow(db.Rebind(` 21 + err = db.QueryRow(` 22 22 SELECT did, handle, access_token, refresh_token, COALESCE(dpop_key, '') 23 23 FROM sessions 24 - WHERE id = ? AND expires_at > ? 25 - `), id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) 24 + WHERE id = $1 AND expires_at > $2 25 + `, id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) 26 26 return 27 27 } 28 28 29 29 func (db *DB) DeleteSession(id string) error { 30 - _, err := db.Exec(db.Rebind(`DELETE FROM sessions WHERE id = ?`), id) 30 + _, err := db.Exec(`DELETE FROM sessions WHERE id = $1`, id) 31 31 return err 32 32 } 33 33 34 34 func (db *DB) DeleteExpiredSessions() error { 35 - _, err := db.Exec(db.Rebind(`DELETE FROM sessions WHERE expires_at <= ?`), time.Now()) 35 + _, err := db.Exec(`DELETE FROM sessions WHERE expires_at <= $1`, time.Now()) 36 36 return err 37 37 }
+48 -113
backend/internal/db/tags.go
··· 1 1 package db 2 2 3 - import "database/sql" 4 - 5 - type TrendingTag struct { 6 - Tag string `json:"tag"` 7 - Count int `json:"count"` 8 - } 9 - 10 3 func (db *DB) GetTrendingTags(limit int) ([]TrendingTag, error) { 11 - var query string 12 - if db.driver == "postgres" { 13 - query = ` 14 - SELECT tag, COUNT(*) as count FROM ( 15 - SELECT value as tag, author_did 16 - FROM annotations, json_array_elements_text(tags_json::json) as value 17 - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 18 - AND created_at > NOW() - INTERVAL '14 days' 19 - UNION ALL 20 - SELECT value as tag, author_did 21 - FROM highlights, json_array_elements_text(tags_json::json) as value 22 - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 23 - AND created_at > NOW() - INTERVAL '14 days' 24 - UNION ALL 25 - SELECT value as tag, author_did 26 - FROM bookmarks, json_array_elements_text(tags_json::json) as value 27 - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 28 - AND created_at > NOW() - INTERVAL '14 days' 29 - ) combined 30 - GROUP BY tag 31 - HAVING COUNT(DISTINCT author_did) >= 3 32 - ORDER BY count DESC 33 - LIMIT $1 34 - ` 35 - } else { 36 - query = ` 37 - SELECT tag, COUNT(*) as count FROM ( 38 - SELECT json_each.value as tag, author_did 39 - FROM annotations, json_each(annotations.tags_json) 40 - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 41 - AND created_at > datetime('now', '-14 days') 42 - UNION ALL 43 - SELECT json_each.value as tag, author_did 44 - FROM highlights, json_each(highlights.tags_json) 45 - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 46 - AND created_at > datetime('now', '-14 days') 47 - UNION ALL 48 - SELECT json_each.value as tag, author_did 49 - FROM bookmarks, json_each(bookmarks.tags_json) 50 - WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 51 - AND created_at > datetime('now', '-14 days') 52 - ) combined 53 - GROUP BY tag 54 - HAVING COUNT(DISTINCT author_did) >= 3 55 - ORDER BY count DESC 56 - LIMIT ? 57 - ` 58 - } 4 + query := ` 5 + SELECT tag, COUNT(*) as count FROM ( 6 + SELECT value as tag, author_did 7 + FROM annotations, json_array_elements_text(tags_json::json) as value 8 + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 9 + AND created_at > NOW() - INTERVAL '14 days' 10 + UNION ALL 11 + SELECT value as tag, author_did 12 + FROM highlights, json_array_elements_text(tags_json::json) as value 13 + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 14 + AND created_at > NOW() - INTERVAL '14 days' 15 + UNION ALL 16 + SELECT value as tag, author_did 17 + FROM bookmarks, json_array_elements_text(tags_json::json) as value 18 + WHERE tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 19 + AND created_at > NOW() - INTERVAL '14 days' 20 + ) combined 21 + GROUP BY tag 22 + HAVING COUNT(DISTINCT author_did) >= 3 23 + ORDER BY count DESC 24 + LIMIT $1 25 + ` 59 26 60 - var rows *sql.Rows 61 - var err error 62 - if db.driver == "postgres" { 63 - rows, err = db.Query(query, limit) 64 - } else { 65 - rows, err = db.Query(db.Rebind(query), limit) 66 - } 27 + rows, err := db.Query(query, limit) 67 28 if err != nil { 68 29 return nil, err 69 30 } ··· 90 51 } 91 52 92 53 func (db *DB) GetUserTags(did string, limit int) ([]TrendingTag, error) { 93 - var query string 94 - if db.driver == "postgres" { 95 - query = ` 96 - SELECT tag, SUM(cnt) as count FROM ( 97 - SELECT value as tag, COUNT(*) as cnt 98 - FROM annotations, json_array_elements_text(tags_json::json) as value 99 - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 100 - GROUP BY tag 101 - UNION ALL 102 - SELECT value as tag, COUNT(*) as cnt 103 - FROM highlights, json_array_elements_text(tags_json::json) as value 104 - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 105 - GROUP BY tag 106 - UNION ALL 107 - SELECT value as tag, COUNT(*) as cnt 108 - FROM bookmarks, json_array_elements_text(tags_json::json) as value 109 - WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 110 - GROUP BY tag 111 - ) combined 54 + query := ` 55 + SELECT tag, SUM(cnt) as count FROM ( 56 + SELECT value as tag, COUNT(*) as cnt 57 + FROM annotations, json_array_elements_text(tags_json::json) as value 58 + WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 112 59 GROUP BY tag 113 - ORDER BY count DESC 114 - LIMIT $2 115 - ` 116 - } else { 117 - query = ` 118 - SELECT tag, SUM(cnt) as count FROM ( 119 - SELECT json_each.value as tag, COUNT(*) as cnt 120 - FROM annotations, json_each(annotations.tags_json) 121 - WHERE author_did = ? AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 122 - GROUP BY tag 123 - UNION ALL 124 - SELECT json_each.value as tag, COUNT(*) as cnt 125 - FROM highlights, json_each(highlights.tags_json) 126 - WHERE author_did = ? AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 127 - GROUP BY tag 128 - UNION ALL 129 - SELECT json_each.value as tag, COUNT(*) as cnt 130 - FROM bookmarks, json_each(bookmarks.tags_json) 131 - WHERE author_did = ? AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 132 - GROUP BY tag 133 - ) combined 60 + UNION ALL 61 + SELECT value as tag, COUNT(*) as cnt 62 + FROM highlights, json_array_elements_text(tags_json::json) as value 63 + WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 64 + GROUP BY tag 65 + UNION ALL 66 + SELECT value as tag, COUNT(*) as cnt 67 + FROM bookmarks, json_array_elements_text(tags_json::json) as value 68 + WHERE author_did = $1 AND tags_json IS NOT NULL AND tags_json != '' AND tags_json != '[]' 134 69 GROUP BY tag 135 - ORDER BY count DESC 136 - LIMIT ? 137 - ` 138 - } 70 + ) combined 71 + GROUP BY tag 72 + ORDER BY count DESC 73 + LIMIT $2 74 + ` 139 75 140 - var rows *sql.Rows 141 - var err error 142 - if db.driver == "postgres" { 143 - rows, err = db.Query(query, did, limit) 144 - } else { 145 - rows, err = db.Query(db.Rebind(query), did, did, did, limit) 146 - } 76 + rows, err := db.Query(query, did, limit) 147 77 if err != nil { 148 78 return nil, err 149 79 } ··· 168 98 169 99 return tags, nil 170 100 } 101 + 102 + type TrendingTag struct { 103 + Tag string `json:"tag"` 104 + Count int `json:"count"` 105 + }
+27 -6
backend/internal/firehose/ingester.go
··· 57 57 currentRelayIdx int 58 58 onAnnotation AnnotationCallback 59 59 onDocument DocumentCallback 60 + workerPool chan func() 60 61 } 61 62 62 63 type RecordHandler func(event *FirehoseEvent) 63 64 64 65 func NewIngester(database *db.DB, syncService *internal_sync.Service) *Ingester { 66 + pool := make(chan func(), 256) 67 + for range 10 { 68 + go func() { 69 + for fn := range pool { 70 + fn() 71 + } 72 + }() 73 + } 74 + 65 75 i := &Ingester{ 66 - db: database, 67 - sync: syncService, 68 - handlers: make(map[string]RecordHandler), 76 + db: database, 77 + sync: syncService, 78 + handlers: make(map[string]RecordHandler), 79 + workerPool: pool, 69 80 } 70 81 71 82 i.RegisterHandler(CollectionAnnotation, i.handleAnnotation) ··· 237 248 238 249 i.dispatchToHandler(firehoseEvent) 239 250 240 - go i.triggerLazySync(event.Did) 251 + did := event.Did 252 + select { 253 + case i.workerPool <- func() { i.triggerLazySync(did) }: 254 + default: 255 + } 241 256 } 242 257 case "delete": 243 258 i.handleDelete(commit.Collection, uri) ··· 266 281 return 267 282 } 268 283 269 - _, err = i.sync.PerformSync(context.Background(), did, func(ctx context.Context, _ string) (*xrpc.Client, error) { 284 + syncCtx, syncCancel := context.WithTimeout(context.Background(), 15*time.Second) 285 + defer syncCancel() 286 + _, err = i.sync.PerformSync(syncCtx, did, func(ctx context.Context, _ string) (*xrpc.Client, error) { 270 287 return &xrpc.Client{ 271 288 PDS: pds, 272 289 }, nil ··· 434 451 } else { 435 452 logger.Info("Indexed annotation from %s on %s", event.Repo, targetSource) 436 453 if i.onAnnotation != nil { 437 - go i.onAnnotation(uri, event.Repo, targetSource, bodyValuePtr, selectorJSONPtr, targetTitlePtr, tagsJSONPtr) 454 + cb := i.onAnnotation 455 + select { 456 + case i.workerPool <- func() { cb(uri, event.Repo, targetSource, bodyValuePtr, selectorJSONPtr, targetTitlePtr, tagsJSONPtr) }: 457 + default: 458 + } 438 459 } 439 460 } 440 461 }
+6
backend/internal/recommendations/service.go
··· 374 374 if len(docs) < batchSize { 375 375 break 376 376 } 377 + 378 + time.Sleep(2 * time.Second) 377 379 } 378 380 379 381 if total > 0 { ··· 429 431 if len(anns) < batchSize { 430 432 break 431 433 } 434 + 435 + time.Sleep(2 * time.Second) 432 436 } 433 437 434 438 if total > 0 { ··· 484 488 if len(highlights) < batchSize { 485 489 break 486 490 } 491 + 492 + time.Sleep(2 * time.Second) 487 493 } 488 494 489 495 if total > 0 {
+9 -1
backend/internal/verification/verify.go
··· 22 22 }, 23 23 } 24 24 25 + var verifySem = make(chan struct{}, 3) 26 + 25 27 var linkTagPattern = regexp.MustCompile(`<link[^>]+rel=["']site\.standard\.document["'][^>]+href=["']([^"']+)["'][^>]*/?>|<link[^>]+href=["']([^"']+)["'][^>]+rel=["']site\.standard\.document["'][^>]*/?>`) 26 28 27 29 func VerifyPublication(pubURL, expectedURI string) error { ··· 84 86 return fmt.Errorf("document URL returned %d", resp.StatusCode) 85 87 } 86 88 87 - body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024)) 89 + body, err := io.ReadAll(io.LimitReader(resp.Body, 16*1024)) 88 90 if err != nil { 89 91 return fmt.Errorf("failed to read document: %w", err) 90 92 } ··· 107 109 108 110 func VerifyPublicationAsync(pubURL, uri string, onVerified func(string)) { 109 111 go func() { 112 + verifySem <- struct{}{} 113 + defer func() { <-verifySem }() 114 + 110 115 if err := VerifyPublication(pubURL, uri); err != nil { 111 116 return 112 117 } ··· 119 124 120 125 func VerifyDocumentAsync(docURL, uri string, onVerified func(string)) { 121 126 go func() { 127 + verifySem <- struct{}{} 128 + defer func() { <-verifySem }() 129 + 122 130 if err := VerifyDocument(docURL, uri); err != nil { 123 131 return 124 132 }
+11 -10
extension/src/utils/overlay.ts
··· 765 765 const marginLeft = i === 0 ? '0' : '-8px'; 766 766 767 767 if (avatar) { 768 - return `<img src="${avatar}" style="width: 24px; height: 24px; border-radius: 50%; object-fit: cover; border: 2px solid #09090b; margin-left: ${marginLeft};">`; 768 + return `<img src="${escapeHtml(avatar)}" style="width: 24px; height: 24px; border-radius: 50%; object-fit: cover; border: 2px solid #09090b; margin-left: ${marginLeft};">`; 769 769 } else { 770 - return `<div style="width: 24px; height: 24px; border-radius: 50%; background: #3b82f6; color: white; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; font-family: -apple-system, sans-serif; border: 2px solid #09090b; margin-left: ${marginLeft};">${handle[0]?.toUpperCase() || 'U'}</div>`; 770 + return `<div style="width: 24px; height: 24px; border-radius: 50%; background: #3b82f6; color: white; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; font-family: -apple-system, sans-serif; border: 2px solid #09090b; margin-left: ${marginLeft};">${escapeHtml(handle[0]?.toUpperCase() || 'U')}</div>`; 771 771 } 772 772 }) 773 773 .join(''); ··· 916 916 const isOwned = currentUserDid && author.did === currentUserDid; 917 917 const createdAt = item.createdAt ? formatRelativeTime(item.createdAt) : ''; 918 918 919 - let avatarHtml = `<div class="comment-avatar">${handle[0]?.toUpperCase() || 'U'}</div>`; 919 + let avatarHtml = `<div class="comment-avatar">${escapeHtml(handle[0]?.toUpperCase() || 'U')}</div>`; 920 920 if (avatar) { 921 - avatarHtml = `<img src="${avatar}" class="comment-avatar" style="object-fit: cover;">`; 921 + avatarHtml = `<img src="${escapeHtml(avatar)}" class="comment-avatar" style="object-fit: cover;">`; 922 922 } 923 923 924 924 let bodyHtml = ''; ··· 928 928 bodyHtml = `<div class="comment-text">${escapeHtml(text)}</div>`; 929 929 } 930 930 931 + const safeId = escapeHtml(id || ''); 931 932 const addNoteBtn = 932 933 isHighlight && isOwned 933 - ? `<button class="comment-action-btn btn-add-note" data-id="${id}" data-uri="${id}">${Icons.message} Annotate</button>` 934 + ? `<button class="comment-action-btn btn-add-note" data-id="${safeId}" data-uri="${safeId}">${Icons.message} Annotate</button>` 934 935 : ''; 935 936 936 937 return ` 937 - <div class="comment-item" data-item-id="${id}"> 938 + <div class="comment-item" data-item-id="${safeId}"> 938 939 <div class="comment-header"> 939 940 ${avatarHtml} 940 941 <div class="comment-meta"> 941 - <span class="comment-handle">@${handle}</span> 942 - ${createdAt ? `<span class="comment-time">${createdAt}</span>` : ''} 942 + <span class="comment-handle">@${escapeHtml(handle)}</span> 943 + ${createdAt ? `<span class="comment-time">${escapeHtml(createdAt)}</span>` : ''} 943 944 </div> 944 945 </div> 945 946 ${bodyHtml} 946 947 <div class="comment-actions"> 947 948 ${addNoteBtn} 948 - ${!isHighlight ? `<button class="comment-action-btn btn-reply" data-id="${id}">${Icons.reply} Reply</button>` : ''} 949 - <button class="comment-action-btn btn-share" data-id="${id}" data-text="${escapeHtml(text)}">${Icons.share} Share</button> 949 + ${!isHighlight ? `<button class="comment-action-btn btn-reply" data-id="${safeId}">${Icons.reply} Reply</button>` : ''} 950 + <button class="comment-action-btn btn-share" data-id="${safeId}" data-text="${escapeHtml(text)}">${Icons.share} Share</button> 950 951 </div> 951 952 </div> 952 953 `;
+6 -4
web/astro.config.mjs
··· 6 6 7 7 const API_PORT = process.env.API_PORT || 8081; 8 8 9 - const isDev = process.env.NODE_ENV === "development"; 10 - 11 9 // https://astro.build/config 12 10 export default defineConfig({ 11 + output: "server", 13 12 adapter: node({ mode: "standalone" }), 14 13 integrations: [react(), tailwind()], 14 + prefetch: { 15 + prefetchAll: false, 16 + defaultStrategy: "hover", 17 + }, 15 18 security: { 16 - checkOrigin: false, 19 + checkOrigin: true, 17 20 }, 18 21 vite: { 19 22 ssr: { 20 - noExternal: isDev ? /^(?!react|react-dom|react-router-dom|cookie)/ : true, 21 23 external: ["@resvg/resvg-js"], 22 24 }, 23 25 build: {
+71 -125
web/bun.lock
··· 1 1 { 2 2 "lockfileVersion": 1, 3 - "configVersion": 0, 4 3 "workspaces": { 5 4 "": { 6 5 "name": "web", 7 6 "dependencies": { 8 - "@astrojs/node": "^9.5.2", 9 - "@astrojs/react": "^4.4.2", 7 + "@astrojs/node": "^10.0.3", 8 + "@astrojs/react": "^5.0.1", 10 9 "@astrojs/tailwind": "^6.0.2", 11 10 "@nanostores/react": "^1.0.0", 12 11 "@resvg/resvg-js": "^2.6.2", 13 12 "@tailwindcss/vite": "^4.1.18", 14 - "astro": "^5.17.1", 13 + "astro": "^6.0.8", 15 14 "autoprefixer": "^10.4.24", 16 15 "clsx": "^2.1.1", 17 16 "date-fns": "^4.1.0", ··· 21 20 "postcss": "^8.5.6", 22 21 "react": "^19.2.4", 23 22 "react-dom": "^19.2.4", 24 - "react-router-dom": "^7.13.0", 25 23 "satori": "^0.19.2", 26 24 "tailwind-merge": "^3.4.0", 27 25 "tailwindcss": "^3.4.19", ··· 50 48 "packages": { 51 49 "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], 52 50 53 - "@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="], 51 + "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="], 54 52 55 - "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.5", "", {}, "sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA=="], 53 + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="], 56 54 57 - "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.10", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.19.0", "smol-toml": "^1.5.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A=="], 55 + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.0.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-zAfLJmn07u9SlDNNHTpjv0RT4F8D4k54NR7ReRas8CO4OeGoqSvOuKwqCFg2/cqN3wHwdWlK/7Yv/lMXlhVIaw=="], 58 56 59 - "@astrojs/node": ["@astrojs/node@9.5.2", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.5", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^5.14.3" } }, "sha512-85/x+FRwbNGDip1TzSGMiak31/6LvBhA8auqd9lLoHaM5XElk+uIfIr3KjJqucDojE0PtiLk1lMSwD9gd3YlGg=="], 57 + "@astrojs/node": ["@astrojs/node@10.0.3", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "send": "^1.2.1", "server-destroy": "^1.0.1" }, "peerDependencies": { "astro": "^6.0.0" } }, "sha512-yWDPaXTOw34h9qNpxDBz1Xj5HudnyuWW2E8ZSegW6o8n+mKI3Yq/iLAUQfxA3h8wfaIRY/PCh3T2jLAys2SXeQ=="], 60 58 61 - "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], 59 + "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="], 62 60 63 - "@astrojs/react": ["@astrojs/react@4.4.2", "", { "dependencies": { "@vitejs/plugin-react": "^4.7.0", "ultrahtml": "^1.6.0", "vite": "^6.4.1" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ=="], 61 + "@astrojs/react": ["@astrojs/react@5.0.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@vitejs/plugin-react": "^5.1.4", "devalue": "^5.6.3", "ultrahtml": "^1.6.0", "vite": "^7.3.1" }, "peerDependencies": { "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, "sha512-gJgQfDUxyePk+UIzwCEtAq04SGbziwRNwOMYvkxLHEtZScSMvRnvQhDWAEMCjLwwEomoT92Tfm34xpD7XAAzOg=="], 64 62 65 63 "@astrojs/tailwind": ["@astrojs/tailwind@6.0.2", "", { "dependencies": { "autoprefixer": "^10.4.21", "postcss": "^8.5.3", "postcss-load-config": "^4.0.2" }, "peerDependencies": { "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", "tailwindcss": "^3.0.24" } }, "sha512-j3mhLNeugZq6A8dMNXVarUa8K6X9AW+QHU9u3lKNrPLMHhOQ0S7VeWhHwEeJFpEK1BTKEUY1U78VQv2gN6hNGg=="], 66 64 ··· 106 104 107 105 "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], 108 106 107 + "@clack/core": ["@clack/core@1.1.0", "", { "dependencies": { "sisteransi": "^1.0.5" } }, "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA=="], 108 + 109 + "@clack/prompts": ["@clack/prompts@1.1.0", "", { "dependencies": { "@clack/core": "1.1.0", "sisteransi": "^1.0.5" } }, "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g=="], 110 + 109 111 "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], 110 112 111 - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], 113 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], 112 114 113 - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], 115 + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="], 114 116 115 - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], 117 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="], 116 118 117 - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], 119 + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="], 118 120 119 - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], 121 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="], 120 122 121 - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], 123 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="], 122 124 123 - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], 125 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="], 124 126 125 - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], 127 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="], 126 128 127 - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 129 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="], 128 130 129 - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 131 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="], 130 132 131 - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 133 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="], 132 134 133 - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 135 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="], 134 136 135 - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 137 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="], 136 138 137 - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 139 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="], 138 140 139 - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 141 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="], 140 142 141 - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 143 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="], 142 144 143 - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 145 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="], 144 146 145 - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 147 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="], 146 148 147 - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 149 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="], 148 150 149 - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 151 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="], 150 152 151 - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 153 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="], 152 154 153 - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 155 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="], 154 156 155 - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 157 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="], 156 158 157 - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 159 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="], 158 160 159 - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 161 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="], 160 162 161 - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 163 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="], 162 164 163 165 "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], 164 166 ··· 286 288 287 289 "@resvg/resvg-js-win32-x64-msvc": ["@resvg/resvg-js-win32-x64-msvc@2.6.2", "", { "os": "win32", "cpu": "x64" }, "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ=="], 288 290 289 - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], 291 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], 290 292 291 293 "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], 292 294 ··· 340 342 341 343 "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], 342 344 343 - "@shikijs/core": ["@shikijs/core@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA=="], 345 + "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], 344 346 345 - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw=="], 347 + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], 346 348 347 - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA=="], 349 + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], 348 350 349 - "@shikijs/langs": ["@shikijs/langs@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA=="], 351 + "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], 350 352 351 - "@shikijs/themes": ["@shikijs/themes@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0" } }, "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g=="], 353 + "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="], 352 354 353 - "@shikijs/types": ["@shikijs/types@3.22.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg=="], 355 + "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], 356 + 357 + "@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], 354 358 355 359 "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], 356 360 ··· 440 444 441 445 "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], 442 446 443 - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], 447 + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], 444 448 445 449 "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 446 450 ··· 448 452 449 453 "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 450 454 451 - "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], 452 - 453 - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], 454 - 455 - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], 456 - 457 455 "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], 458 456 459 457 "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], ··· 480 478 481 479 "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], 482 480 483 - "astro": ["astro@5.17.1", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.5", "@astrojs/markdown-remark": "6.3.10", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.0", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-oD3tlxTaVWGq/Wfbqk6gxzVRz98xa/rYlpe+gU2jXJMSD01k6sEDL01ZlT8mVSYB/rMgnvIOfiQQ3BbLdN237A=="], 481 + "astro": ["astro@6.0.8", "", { "dependencies": { "@astrojs/compiler": "^3.0.0", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.0.1", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.0.1", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyclip": "^0.1.6", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-DCPeb8GKOoFWh+8whB7Qi/kKWD/6NcQ9nd1QVNzJFxgHkea3WYrNroQRq4whmBdjhkYPTLS/1gmUAl2iA2Es2g=="], 484 482 485 483 "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], 486 484 ··· 494 492 495 493 "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 496 494 497 - "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], 498 - 499 495 "base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="], 500 496 501 497 "baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="], ··· 504 500 505 501 "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], 506 502 507 - "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], 508 - 509 503 "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 510 504 511 505 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], ··· 517 511 "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 518 512 519 513 "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 520 - 521 - "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], 522 514 523 515 "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], 524 516 ··· 528 520 529 521 "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], 530 522 531 - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], 532 - 533 523 "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], 534 524 535 525 "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], ··· 540 530 541 531 "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], 542 532 543 - "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], 544 - 545 533 "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], 546 534 547 535 "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], ··· 550 538 551 539 "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], 552 540 553 - "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], 541 + "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="], 554 542 555 543 "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 556 544 ··· 614 602 615 603 "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 616 604 617 - "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], 618 - 619 - "devalue": ["devalue@5.6.2", "", {}, "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg=="], 605 + "devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="], 620 606 621 607 "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], 622 608 ··· 646 632 647 633 "emoji-picker-react": ["emoji-picker-react@4.18.0", "", { "dependencies": { "flairup": "1.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-vLTrLfApXAIciguGE57pXPWs9lPLBspbEpPMiUq03TIli2dHZBiB+aZ0R9/Wat0xmTfcd4AuEzQgSYxEZ8C88Q=="], 648 634 649 - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], 650 - 651 635 "emoji-regex-xs": ["emoji-regex-xs@2.0.1", "", {}, "sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g=="], 652 636 653 637 "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], ··· 664 648 665 649 "es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="], 666 650 667 - "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], 651 + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], 668 652 669 653 "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 670 654 ··· 674 658 675 659 "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], 676 660 677 - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], 661 + "esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], 678 662 679 663 "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 680 664 ··· 706 690 707 691 "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 708 692 709 - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], 693 + "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 710 694 711 695 "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 712 696 ··· 768 752 769 753 "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 770 754 771 - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], 772 - 773 755 "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 774 756 775 757 "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], ··· 838 820 839 821 "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 840 822 841 - "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], 842 - 843 823 "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 844 824 845 825 "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], ··· 871 851 "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 872 852 873 853 "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], 874 - 875 - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 876 854 877 855 "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], 878 856 ··· 935 913 "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], 936 914 937 915 "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 938 - 939 - "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], 940 916 941 917 "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 942 918 ··· 1124 1100 1125 1101 "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], 1126 1102 1103 + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], 1104 + 1127 1105 "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], 1128 1106 1129 1107 "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], ··· 1138 1116 1139 1117 "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], 1140 1118 1141 - "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], 1119 + "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="], 1142 1120 1143 1121 "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 1144 1122 1145 - "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], 1123 + "p-queue": ["p-queue@9.1.0", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw=="], 1146 1124 1147 - "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], 1125 + "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="], 1148 1126 1149 1127 "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], 1150 1128 ··· 1196 1174 1197 1175 "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], 1198 1176 1199 - "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], 1200 - 1201 1177 "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 1202 1178 1203 1179 "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], ··· 1218 1194 1219 1195 "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], 1220 1196 1221 - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], 1222 - 1223 - "react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="], 1224 - 1225 - "react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="], 1197 + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], 1226 1198 1227 1199 "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], 1228 1200 ··· 1290 1262 1291 1263 "server-destroy": ["server-destroy@1.0.1", "", {}, "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ=="], 1292 1264 1293 - "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], 1294 - 1295 1265 "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], 1296 1266 1297 1267 "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], ··· 1306 1276 1307 1277 "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 1308 1278 1309 - "shiki": ["shiki@3.22.0", "", { "dependencies": { "@shikijs/core": "3.22.0", "@shikijs/engine-javascript": "3.22.0", "@shikijs/engine-oniguruma": "3.22.0", "@shikijs/langs": "3.22.0", "@shikijs/themes": "3.22.0", "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g=="], 1279 + "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], 1310 1280 1311 1281 "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], 1312 1282 ··· 1328 1298 1329 1299 "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], 1330 1300 1331 - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], 1332 - 1333 1301 "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], 1334 1302 1335 1303 "string.prototype.matchall": ["string.prototype.matchall@4.0.12", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA=="], ··· 1343 1311 "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], 1344 1312 1345 1313 "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], 1346 - 1347 - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], 1348 1314 1349 1315 "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], 1350 1316 ··· 1365 1331 "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], 1366 1332 1367 1333 "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], 1334 + 1335 + "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="], 1368 1336 1369 1337 "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], 1370 1338 ··· 1388 1356 1389 1357 "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 1390 1358 1391 - "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], 1392 - 1393 1359 "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], 1394 1360 1395 1361 "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], ··· 1416 1382 1417 1383 "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], 1418 1384 1419 - "unifont": ["unifont@0.7.3", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-b0GtQzKCyuSHGsfj5vyN8st7muZ6VCI4XD4vFlr7Uy1rlWVYxC3npnfk8MyreHxJYrz1ooLDqDzFe9XqQTlAhA=="], 1385 + "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], 1420 1386 1421 1387 "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], 1422 1388 ··· 1450 1416 1451 1417 "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], 1452 1418 1453 - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], 1419 + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], 1454 1420 1455 - "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], 1421 + "vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="], 1456 1422 1457 1423 "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], 1458 1424 ··· 1468 1434 1469 1435 "which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="], 1470 1436 1471 - "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], 1472 - 1473 1437 "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 1474 - 1475 - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], 1476 1438 1477 1439 "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], 1478 1440 ··· 1480 1442 1481 1443 "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], 1482 1444 1483 - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 1445 + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], 1484 1446 1485 1447 "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], 1486 - 1487 - "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], 1488 - 1489 - "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], 1490 1448 1491 1449 "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], 1492 1450 1493 1451 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 1494 1452 1495 - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], 1496 - 1497 - "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], 1498 - 1499 1453 "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], 1500 1454 1501 1455 "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], ··· 1503 1457 "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 1504 1458 1505 1459 "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 1506 - 1507 - "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 1508 1460 1509 1461 "@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 1510 1462 ··· 1530 1482 1531 1483 "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 1532 1484 1533 - "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 1534 - 1535 1485 "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 1536 1486 1537 - "astro/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 1487 + "astro/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], 1488 + 1489 + "astro/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], 1538 1490 1539 1491 "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 1540 1492 ··· 1568 1520 1569 1521 "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 1570 1522 1571 - "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 1572 - 1573 - "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 1574 - 1575 1523 "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], 1576 1524 1577 1525 "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 1578 1526 1579 1527 "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], 1580 - 1581 - "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 1582 1528 } 1583 1529 }
-9674
web/package-lock.json
··· 1 - { 2 - "name": "web", 3 - "version": "0.0.1", 4 - "lockfileVersion": 3, 5 - "requires": true, 6 - "packages": { 7 - "": { 8 - "name": "web", 9 - "version": "0.0.1", 10 - "dependencies": { 11 - "@astrojs/node": "^9.5.2", 12 - "@astrojs/react": "^4.4.2", 13 - "@astrojs/tailwind": "^6.0.2", 14 - "@nanostores/react": "^1.0.0", 15 - "@resvg/resvg-js": "^2.6.2", 16 - "@tailwindcss/vite": "^4.1.18", 17 - "astro": "^5.17.1", 18 - "autoprefixer": "^10.4.24", 19 - "clsx": "^2.1.1", 20 - "date-fns": "^4.1.0", 21 - "emoji-picker-react": "^4.18.0", 22 - "lucide-react": "^0.563.0", 23 - "nanostores": "^1.1.0", 24 - "postcss": "^8.5.6", 25 - "react": "^19.2.4", 26 - "react-dom": "^19.2.4", 27 - "react-router-dom": "^7.13.0", 28 - "satori": "^0.19.2", 29 - "tailwind-merge": "^3.4.0", 30 - "tailwindcss": "^3.4.19" 31 - }, 32 - "devDependencies": { 33 - "@eslint/js": "^10.0.1", 34 - "@types/node": "^25.2.3", 35 - "@types/react": "^19.2.11", 36 - "@types/react-dom": "^19.2.3", 37 - "@typescript-eslint/eslint-plugin": "^8.54.0", 38 - "@typescript-eslint/parser": "^8.54.0", 39 - "eslint": "^10.0.0", 40 - "eslint-config-prettier": "^10.1.8", 41 - "eslint-plugin-prettier": "^5.5.5", 42 - "eslint-plugin-react": "^7.37.5", 43 - "eslint-plugin-react-hooks": "^7.0.1", 44 - "eslint-plugin-react-refresh": "^0.5.0", 45 - "globals": "^17.3.0", 46 - "prettier": "^3.8.1", 47 - "react-icons": "^5.5.0", 48 - "typescript": "^5.9.3", 49 - "typescript-eslint": "^8.54.0" 50 - } 51 - }, 52 - "node_modules/@alloc/quick-lru": { 53 - "version": "5.2.0", 54 - "license": "MIT", 55 - "engines": { 56 - "node": ">=10" 57 - }, 58 - "funding": { 59 - "url": "https://github.com/sponsors/sindresorhus" 60 - } 61 - }, 62 - "node_modules/@astrojs/compiler": { 63 - "version": "2.13.0", 64 - "license": "MIT" 65 - }, 66 - "node_modules/@astrojs/internal-helpers": { 67 - "version": "0.7.5", 68 - "license": "MIT" 69 - }, 70 - "node_modules/@astrojs/markdown-remark": { 71 - "version": "6.3.10", 72 - "license": "MIT", 73 - "dependencies": { 74 - "@astrojs/internal-helpers": "0.7.5", 75 - "@astrojs/prism": "3.3.0", 76 - "github-slugger": "^2.0.0", 77 - "hast-util-from-html": "^2.0.3", 78 - "hast-util-to-text": "^4.0.2", 79 - "import-meta-resolve": "^4.2.0", 80 - "js-yaml": "^4.1.1", 81 - "mdast-util-definitions": "^6.0.0", 82 - "rehype-raw": "^7.0.0", 83 - "rehype-stringify": "^10.0.1", 84 - "remark-gfm": "^4.0.1", 85 - "remark-parse": "^11.0.0", 86 - "remark-rehype": "^11.1.2", 87 - "remark-smartypants": "^3.0.2", 88 - "shiki": "^3.19.0", 89 - "smol-toml": "^1.5.2", 90 - "unified": "^11.0.5", 91 - "unist-util-remove-position": "^5.0.0", 92 - "unist-util-visit": "^5.0.0", 93 - "unist-util-visit-parents": "^6.0.2", 94 - "vfile": "^6.0.3" 95 - } 96 - }, 97 - "node_modules/@astrojs/node": { 98 - "version": "9.5.2", 99 - "license": "MIT", 100 - "dependencies": { 101 - "@astrojs/internal-helpers": "0.7.5", 102 - "send": "^1.2.1", 103 - "server-destroy": "^1.0.1" 104 - }, 105 - "peerDependencies": { 106 - "astro": "^5.14.3" 107 - } 108 - }, 109 - "node_modules/@astrojs/prism": { 110 - "version": "3.3.0", 111 - "license": "MIT", 112 - "dependencies": { 113 - "prismjs": "^1.30.0" 114 - }, 115 - "engines": { 116 - "node": "18.20.8 || ^20.3.0 || >=22.0.0" 117 - } 118 - }, 119 - "node_modules/@astrojs/react": { 120 - "version": "4.4.2", 121 - "license": "MIT", 122 - "dependencies": { 123 - "@vitejs/plugin-react": "^4.7.0", 124 - "ultrahtml": "^1.6.0", 125 - "vite": "^6.4.1" 126 - }, 127 - "engines": { 128 - "node": "18.20.8 || ^20.3.0 || >=22.0.0" 129 - }, 130 - "peerDependencies": { 131 - "@types/react": "^17.0.50 || ^18.0.21 || ^19.0.0", 132 - "@types/react-dom": "^17.0.17 || ^18.0.6 || ^19.0.0", 133 - "react": "^17.0.2 || ^18.0.0 || ^19.0.0", 134 - "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" 135 - } 136 - }, 137 - "node_modules/@astrojs/tailwind": { 138 - "version": "6.0.2", 139 - "license": "MIT", 140 - "dependencies": { 141 - "autoprefixer": "^10.4.21", 142 - "postcss": "^8.5.3", 143 - "postcss-load-config": "^4.0.2" 144 - }, 145 - "peerDependencies": { 146 - "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", 147 - "tailwindcss": "^3.0.24" 148 - } 149 - }, 150 - "node_modules/@astrojs/telemetry": { 151 - "version": "3.3.0", 152 - "license": "MIT", 153 - "dependencies": { 154 - "ci-info": "^4.2.0", 155 - "debug": "^4.4.0", 156 - "dlv": "^1.1.3", 157 - "dset": "^3.1.4", 158 - "is-docker": "^3.0.0", 159 - "is-wsl": "^3.1.0", 160 - "which-pm-runs": "^1.1.0" 161 - }, 162 - "engines": { 163 - "node": "18.20.8 || ^20.3.0 || >=22.0.0" 164 - } 165 - }, 166 - "node_modules/@babel/code-frame": { 167 - "version": "7.29.0", 168 - "license": "MIT", 169 - "dependencies": { 170 - "@babel/helper-validator-identifier": "^7.28.5", 171 - "js-tokens": "^4.0.0", 172 - "picocolors": "^1.1.1" 173 - }, 174 - "engines": { 175 - "node": ">=6.9.0" 176 - } 177 - }, 178 - "node_modules/@babel/compat-data": { 179 - "version": "7.29.0", 180 - "license": "MIT", 181 - "engines": { 182 - "node": ">=6.9.0" 183 - } 184 - }, 185 - "node_modules/@babel/core": { 186 - "version": "7.29.0", 187 - "license": "MIT", 188 - "dependencies": { 189 - "@babel/code-frame": "^7.29.0", 190 - "@babel/generator": "^7.29.0", 191 - "@babel/helper-compilation-targets": "^7.28.6", 192 - "@babel/helper-module-transforms": "^7.28.6", 193 - "@babel/helpers": "^7.28.6", 194 - "@babel/parser": "^7.29.0", 195 - "@babel/template": "^7.28.6", 196 - "@babel/traverse": "^7.29.0", 197 - "@babel/types": "^7.29.0", 198 - "@jridgewell/remapping": "^2.3.5", 199 - "convert-source-map": "^2.0.0", 200 - "debug": "^4.1.0", 201 - "gensync": "^1.0.0-beta.2", 202 - "json5": "^2.2.3", 203 - "semver": "^6.3.1" 204 - }, 205 - "engines": { 206 - "node": ">=6.9.0" 207 - }, 208 - "funding": { 209 - "type": "opencollective", 210 - "url": "https://opencollective.com/babel" 211 - } 212 - }, 213 - "node_modules/@babel/core/node_modules/semver": { 214 - "version": "6.3.1", 215 - "license": "ISC", 216 - "bin": { 217 - "semver": "bin/semver.js" 218 - } 219 - }, 220 - "node_modules/@babel/generator": { 221 - "version": "7.29.1", 222 - "license": "MIT", 223 - "dependencies": { 224 - "@babel/parser": "^7.29.0", 225 - "@babel/types": "^7.29.0", 226 - "@jridgewell/gen-mapping": "^0.3.12", 227 - "@jridgewell/trace-mapping": "^0.3.28", 228 - "jsesc": "^3.0.2" 229 - }, 230 - "engines": { 231 - "node": ">=6.9.0" 232 - } 233 - }, 234 - "node_modules/@babel/helper-compilation-targets": { 235 - "version": "7.28.6", 236 - "license": "MIT", 237 - "dependencies": { 238 - "@babel/compat-data": "^7.28.6", 239 - "@babel/helper-validator-option": "^7.27.1", 240 - "browserslist": "^4.24.0", 241 - "lru-cache": "^5.1.1", 242 - "semver": "^6.3.1" 243 - }, 244 - "engines": { 245 - "node": ">=6.9.0" 246 - } 247 - }, 248 - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { 249 - "version": "5.1.1", 250 - "license": "ISC", 251 - "dependencies": { 252 - "yallist": "^3.0.2" 253 - } 254 - }, 255 - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { 256 - "version": "6.3.1", 257 - "license": "ISC", 258 - "bin": { 259 - "semver": "bin/semver.js" 260 - } 261 - }, 262 - "node_modules/@babel/helper-globals": { 263 - "version": "7.28.0", 264 - "license": "MIT", 265 - "engines": { 266 - "node": ">=6.9.0" 267 - } 268 - }, 269 - "node_modules/@babel/helper-module-imports": { 270 - "version": "7.28.6", 271 - "license": "MIT", 272 - "dependencies": { 273 - "@babel/traverse": "^7.28.6", 274 - "@babel/types": "^7.28.6" 275 - }, 276 - "engines": { 277 - "node": ">=6.9.0" 278 - } 279 - }, 280 - "node_modules/@babel/helper-module-transforms": { 281 - "version": "7.28.6", 282 - "license": "MIT", 283 - "dependencies": { 284 - "@babel/helper-module-imports": "^7.28.6", 285 - "@babel/helper-validator-identifier": "^7.28.5", 286 - "@babel/traverse": "^7.28.6" 287 - }, 288 - "engines": { 289 - "node": ">=6.9.0" 290 - }, 291 - "peerDependencies": { 292 - "@babel/core": "^7.0.0" 293 - } 294 - }, 295 - "node_modules/@babel/helper-plugin-utils": { 296 - "version": "7.28.6", 297 - "license": "MIT", 298 - "engines": { 299 - "node": ">=6.9.0" 300 - } 301 - }, 302 - "node_modules/@babel/helper-string-parser": { 303 - "version": "7.27.1", 304 - "license": "MIT", 305 - "engines": { 306 - "node": ">=6.9.0" 307 - } 308 - }, 309 - "node_modules/@babel/helper-validator-identifier": { 310 - "version": "7.28.5", 311 - "license": "MIT", 312 - "engines": { 313 - "node": ">=6.9.0" 314 - } 315 - }, 316 - "node_modules/@babel/helper-validator-option": { 317 - "version": "7.27.1", 318 - "license": "MIT", 319 - "engines": { 320 - "node": ">=6.9.0" 321 - } 322 - }, 323 - "node_modules/@babel/helpers": { 324 - "version": "7.28.6", 325 - "license": "MIT", 326 - "dependencies": { 327 - "@babel/template": "^7.28.6", 328 - "@babel/types": "^7.28.6" 329 - }, 330 - "engines": { 331 - "node": ">=6.9.0" 332 - } 333 - }, 334 - "node_modules/@babel/parser": { 335 - "version": "7.29.0", 336 - "license": "MIT", 337 - "dependencies": { 338 - "@babel/types": "^7.29.0" 339 - }, 340 - "bin": { 341 - "parser": "bin/babel-parser.js" 342 - }, 343 - "engines": { 344 - "node": ">=6.0.0" 345 - } 346 - }, 347 - "node_modules/@babel/plugin-transform-react-jsx-self": { 348 - "version": "7.27.1", 349 - "license": "MIT", 350 - "dependencies": { 351 - "@babel/helper-plugin-utils": "^7.27.1" 352 - }, 353 - "engines": { 354 - "node": ">=6.9.0" 355 - }, 356 - "peerDependencies": { 357 - "@babel/core": "^7.0.0-0" 358 - } 359 - }, 360 - "node_modules/@babel/plugin-transform-react-jsx-source": { 361 - "version": "7.27.1", 362 - "license": "MIT", 363 - "dependencies": { 364 - "@babel/helper-plugin-utils": "^7.27.1" 365 - }, 366 - "engines": { 367 - "node": ">=6.9.0" 368 - }, 369 - "peerDependencies": { 370 - "@babel/core": "^7.0.0-0" 371 - } 372 - }, 373 - "node_modules/@babel/template": { 374 - "version": "7.28.6", 375 - "license": "MIT", 376 - "dependencies": { 377 - "@babel/code-frame": "^7.28.6", 378 - "@babel/parser": "^7.28.6", 379 - "@babel/types": "^7.28.6" 380 - }, 381 - "engines": { 382 - "node": ">=6.9.0" 383 - } 384 - }, 385 - "node_modules/@babel/traverse": { 386 - "version": "7.29.0", 387 - "license": "MIT", 388 - "dependencies": { 389 - "@babel/code-frame": "^7.29.0", 390 - "@babel/generator": "^7.29.0", 391 - "@babel/helper-globals": "^7.28.0", 392 - "@babel/parser": "^7.29.0", 393 - "@babel/template": "^7.28.6", 394 - "@babel/types": "^7.29.0", 395 - "debug": "^4.3.1" 396 - }, 397 - "engines": { 398 - "node": ">=6.9.0" 399 - } 400 - }, 401 - "node_modules/@babel/types": { 402 - "version": "7.29.0", 403 - "license": "MIT", 404 - "dependencies": { 405 - "@babel/helper-string-parser": "^7.27.1", 406 - "@babel/helper-validator-identifier": "^7.28.5" 407 - }, 408 - "engines": { 409 - "node": ">=6.9.0" 410 - } 411 - }, 412 - "node_modules/@capsizecss/unpack": { 413 - "version": "4.0.0", 414 - "license": "MIT", 415 - "dependencies": { 416 - "fontkitten": "^1.0.0" 417 - }, 418 - "engines": { 419 - "node": ">=18" 420 - } 421 - }, 422 - "node_modules/@emnapi/runtime": { 423 - "version": "1.8.1", 424 - "license": "MIT", 425 - "optional": true, 426 - "dependencies": { 427 - "tslib": "^2.4.0" 428 - } 429 - }, 430 - "node_modules/@esbuild/aix-ppc64": { 431 - "version": "0.25.12", 432 - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", 433 - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", 434 - "cpu": [ 435 - "ppc64" 436 - ], 437 - "license": "MIT", 438 - "optional": true, 439 - "os": [ 440 - "aix" 441 - ], 442 - "engines": { 443 - "node": ">=18" 444 - } 445 - }, 446 - "node_modules/@esbuild/android-arm": { 447 - "version": "0.25.12", 448 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", 449 - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", 450 - "cpu": [ 451 - "arm" 452 - ], 453 - "license": "MIT", 454 - "optional": true, 455 - "os": [ 456 - "android" 457 - ], 458 - "engines": { 459 - "node": ">=18" 460 - } 461 - }, 462 - "node_modules/@esbuild/android-arm64": { 463 - "version": "0.25.12", 464 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", 465 - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", 466 - "cpu": [ 467 - "arm64" 468 - ], 469 - "license": "MIT", 470 - "optional": true, 471 - "os": [ 472 - "android" 473 - ], 474 - "engines": { 475 - "node": ">=18" 476 - } 477 - }, 478 - "node_modules/@esbuild/android-x64": { 479 - "version": "0.25.12", 480 - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", 481 - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", 482 - "cpu": [ 483 - "x64" 484 - ], 485 - "license": "MIT", 486 - "optional": true, 487 - "os": [ 488 - "android" 489 - ], 490 - "engines": { 491 - "node": ">=18" 492 - } 493 - }, 494 - "node_modules/@esbuild/darwin-arm64": { 495 - "version": "0.25.12", 496 - "cpu": [ 497 - "arm64" 498 - ], 499 - "license": "MIT", 500 - "optional": true, 501 - "os": [ 502 - "darwin" 503 - ], 504 - "engines": { 505 - "node": ">=18" 506 - } 507 - }, 508 - "node_modules/@esbuild/darwin-x64": { 509 - "version": "0.25.12", 510 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", 511 - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", 512 - "cpu": [ 513 - "x64" 514 - ], 515 - "license": "MIT", 516 - "optional": true, 517 - "os": [ 518 - "darwin" 519 - ], 520 - "engines": { 521 - "node": ">=18" 522 - } 523 - }, 524 - "node_modules/@esbuild/freebsd-arm64": { 525 - "version": "0.25.12", 526 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", 527 - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", 528 - "cpu": [ 529 - "arm64" 530 - ], 531 - "license": "MIT", 532 - "optional": true, 533 - "os": [ 534 - "freebsd" 535 - ], 536 - "engines": { 537 - "node": ">=18" 538 - } 539 - }, 540 - "node_modules/@esbuild/freebsd-x64": { 541 - "version": "0.25.12", 542 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", 543 - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", 544 - "cpu": [ 545 - "x64" 546 - ], 547 - "license": "MIT", 548 - "optional": true, 549 - "os": [ 550 - "freebsd" 551 - ], 552 - "engines": { 553 - "node": ">=18" 554 - } 555 - }, 556 - "node_modules/@esbuild/linux-arm": { 557 - "version": "0.25.12", 558 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", 559 - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", 560 - "cpu": [ 561 - "arm" 562 - ], 563 - "license": "MIT", 564 - "optional": true, 565 - "os": [ 566 - "linux" 567 - ], 568 - "engines": { 569 - "node": ">=18" 570 - } 571 - }, 572 - "node_modules/@esbuild/linux-arm64": { 573 - "version": "0.25.12", 574 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", 575 - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", 576 - "cpu": [ 577 - "arm64" 578 - ], 579 - "license": "MIT", 580 - "optional": true, 581 - "os": [ 582 - "linux" 583 - ], 584 - "engines": { 585 - "node": ">=18" 586 - } 587 - }, 588 - "node_modules/@esbuild/linux-ia32": { 589 - "version": "0.25.12", 590 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", 591 - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", 592 - "cpu": [ 593 - "ia32" 594 - ], 595 - "license": "MIT", 596 - "optional": true, 597 - "os": [ 598 - "linux" 599 - ], 600 - "engines": { 601 - "node": ">=18" 602 - } 603 - }, 604 - "node_modules/@esbuild/linux-loong64": { 605 - "version": "0.25.12", 606 - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", 607 - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", 608 - "cpu": [ 609 - "loong64" 610 - ], 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.25.12", 622 - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", 623 - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", 624 - "cpu": [ 625 - "mips64el" 626 - ], 627 - "license": "MIT", 628 - "optional": true, 629 - "os": [ 630 - "linux" 631 - ], 632 - "engines": { 633 - "node": ">=18" 634 - } 635 - }, 636 - "node_modules/@esbuild/linux-ppc64": { 637 - "version": "0.25.12", 638 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", 639 - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", 640 - "cpu": [ 641 - "ppc64" 642 - ], 643 - "license": "MIT", 644 - "optional": true, 645 - "os": [ 646 - "linux" 647 - ], 648 - "engines": { 649 - "node": ">=18" 650 - } 651 - }, 652 - "node_modules/@esbuild/linux-riscv64": { 653 - "version": "0.25.12", 654 - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", 655 - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", 656 - "cpu": [ 657 - "riscv64" 658 - ], 659 - "license": "MIT", 660 - "optional": true, 661 - "os": [ 662 - "linux" 663 - ], 664 - "engines": { 665 - "node": ">=18" 666 - } 667 - }, 668 - "node_modules/@esbuild/linux-s390x": { 669 - "version": "0.25.12", 670 - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", 671 - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", 672 - "cpu": [ 673 - "s390x" 674 - ], 675 - "license": "MIT", 676 - "optional": true, 677 - "os": [ 678 - "linux" 679 - ], 680 - "engines": { 681 - "node": ">=18" 682 - } 683 - }, 684 - "node_modules/@esbuild/linux-x64": { 685 - "version": "0.25.12", 686 - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", 687 - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", 688 - "cpu": [ 689 - "x64" 690 - ], 691 - "license": "MIT", 692 - "optional": true, 693 - "os": [ 694 - "linux" 695 - ], 696 - "engines": { 697 - "node": ">=18" 698 - } 699 - }, 700 - "node_modules/@esbuild/netbsd-arm64": { 701 - "version": "0.25.12", 702 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", 703 - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", 704 - "cpu": [ 705 - "arm64" 706 - ], 707 - "license": "MIT", 708 - "optional": true, 709 - "os": [ 710 - "netbsd" 711 - ], 712 - "engines": { 713 - "node": ">=18" 714 - } 715 - }, 716 - "node_modules/@esbuild/netbsd-x64": { 717 - "version": "0.25.12", 718 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", 719 - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", 720 - "cpu": [ 721 - "x64" 722 - ], 723 - "license": "MIT", 724 - "optional": true, 725 - "os": [ 726 - "netbsd" 727 - ], 728 - "engines": { 729 - "node": ">=18" 730 - } 731 - }, 732 - "node_modules/@esbuild/openbsd-arm64": { 733 - "version": "0.25.12", 734 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", 735 - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", 736 - "cpu": [ 737 - "arm64" 738 - ], 739 - "license": "MIT", 740 - "optional": true, 741 - "os": [ 742 - "openbsd" 743 - ], 744 - "engines": { 745 - "node": ">=18" 746 - } 747 - }, 748 - "node_modules/@esbuild/openbsd-x64": { 749 - "version": "0.25.12", 750 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", 751 - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", 752 - "cpu": [ 753 - "x64" 754 - ], 755 - "license": "MIT", 756 - "optional": true, 757 - "os": [ 758 - "openbsd" 759 - ], 760 - "engines": { 761 - "node": ">=18" 762 - } 763 - }, 764 - "node_modules/@esbuild/openharmony-arm64": { 765 - "version": "0.25.12", 766 - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", 767 - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", 768 - "cpu": [ 769 - "arm64" 770 - ], 771 - "license": "MIT", 772 - "optional": true, 773 - "os": [ 774 - "openharmony" 775 - ], 776 - "engines": { 777 - "node": ">=18" 778 - } 779 - }, 780 - "node_modules/@esbuild/sunos-x64": { 781 - "version": "0.25.12", 782 - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", 783 - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", 784 - "cpu": [ 785 - "x64" 786 - ], 787 - "license": "MIT", 788 - "optional": true, 789 - "os": [ 790 - "sunos" 791 - ], 792 - "engines": { 793 - "node": ">=18" 794 - } 795 - }, 796 - "node_modules/@esbuild/win32-arm64": { 797 - "version": "0.25.12", 798 - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", 799 - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", 800 - "cpu": [ 801 - "arm64" 802 - ], 803 - "license": "MIT", 804 - "optional": true, 805 - "os": [ 806 - "win32" 807 - ], 808 - "engines": { 809 - "node": ">=18" 810 - } 811 - }, 812 - "node_modules/@esbuild/win32-ia32": { 813 - "version": "0.25.12", 814 - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", 815 - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", 816 - "cpu": [ 817 - "ia32" 818 - ], 819 - "license": "MIT", 820 - "optional": true, 821 - "os": [ 822 - "win32" 823 - ], 824 - "engines": { 825 - "node": ">=18" 826 - } 827 - }, 828 - "node_modules/@esbuild/win32-x64": { 829 - "version": "0.25.12", 830 - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", 831 - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", 832 - "cpu": [ 833 - "x64" 834 - ], 835 - "license": "MIT", 836 - "optional": true, 837 - "os": [ 838 - "win32" 839 - ], 840 - "engines": { 841 - "node": ">=18" 842 - } 843 - }, 844 - "node_modules/@eslint-community/eslint-utils": { 845 - "version": "4.9.1", 846 - "dev": true, 847 - "license": "MIT", 848 - "dependencies": { 849 - "eslint-visitor-keys": "^3.4.3" 850 - }, 851 - "engines": { 852 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 853 - }, 854 - "funding": { 855 - "url": "https://opencollective.com/eslint" 856 - }, 857 - "peerDependencies": { 858 - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 859 - } 860 - }, 861 - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { 862 - "version": "3.4.3", 863 - "dev": true, 864 - "license": "Apache-2.0", 865 - "engines": { 866 - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 867 - }, 868 - "funding": { 869 - "url": "https://opencollective.com/eslint" 870 - } 871 - }, 872 - "node_modules/@eslint-community/regexpp": { 873 - "version": "4.12.2", 874 - "dev": true, 875 - "license": "MIT", 876 - "engines": { 877 - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 878 - } 879 - }, 880 - "node_modules/@eslint/config-array": { 881 - "version": "0.23.1", 882 - "dev": true, 883 - "license": "Apache-2.0", 884 - "dependencies": { 885 - "@eslint/object-schema": "^3.0.1", 886 - "debug": "^4.3.1", 887 - "minimatch": "^10.1.1" 888 - }, 889 - "engines": { 890 - "node": "^20.19.0 || ^22.13.0 || >=24" 891 - } 892 - }, 893 - "node_modules/@eslint/config-helpers": { 894 - "version": "0.5.2", 895 - "dev": true, 896 - "license": "Apache-2.0", 897 - "dependencies": { 898 - "@eslint/core": "^1.1.0" 899 - }, 900 - "engines": { 901 - "node": "^20.19.0 || ^22.13.0 || >=24" 902 - } 903 - }, 904 - "node_modules/@eslint/core": { 905 - "version": "1.1.0", 906 - "dev": true, 907 - "license": "Apache-2.0", 908 - "dependencies": { 909 - "@types/json-schema": "^7.0.15" 910 - }, 911 - "engines": { 912 - "node": "^20.19.0 || ^22.13.0 || >=24" 913 - } 914 - }, 915 - "node_modules/@eslint/js": { 916 - "version": "10.0.1", 917 - "dev": true, 918 - "license": "MIT", 919 - "engines": { 920 - "node": "^20.19.0 || ^22.13.0 || >=24" 921 - }, 922 - "funding": { 923 - "url": "https://eslint.org/donate" 924 - }, 925 - "peerDependencies": { 926 - "eslint": "^10.0.0" 927 - }, 928 - "peerDependenciesMeta": { 929 - "eslint": { 930 - "optional": true 931 - } 932 - } 933 - }, 934 - "node_modules/@eslint/object-schema": { 935 - "version": "3.0.1", 936 - "dev": true, 937 - "license": "Apache-2.0", 938 - "engines": { 939 - "node": "^20.19.0 || ^22.13.0 || >=24" 940 - } 941 - }, 942 - "node_modules/@eslint/plugin-kit": { 943 - "version": "0.6.0", 944 - "dev": true, 945 - "license": "Apache-2.0", 946 - "dependencies": { 947 - "@eslint/core": "^1.1.0", 948 - "levn": "^0.4.1" 949 - }, 950 - "engines": { 951 - "node": "^20.19.0 || ^22.13.0 || >=24" 952 - } 953 - }, 954 - "node_modules/@humanfs/core": { 955 - "version": "0.19.1", 956 - "dev": true, 957 - "license": "Apache-2.0", 958 - "engines": { 959 - "node": ">=18.18.0" 960 - } 961 - }, 962 - "node_modules/@humanfs/node": { 963 - "version": "0.16.7", 964 - "dev": true, 965 - "license": "Apache-2.0", 966 - "dependencies": { 967 - "@humanfs/core": "^0.19.1", 968 - "@humanwhocodes/retry": "^0.4.0" 969 - }, 970 - "engines": { 971 - "node": ">=18.18.0" 972 - } 973 - }, 974 - "node_modules/@humanwhocodes/module-importer": { 975 - "version": "1.0.1", 976 - "dev": true, 977 - "license": "Apache-2.0", 978 - "engines": { 979 - "node": ">=12.22" 980 - }, 981 - "funding": { 982 - "type": "github", 983 - "url": "https://github.com/sponsors/nzakas" 984 - } 985 - }, 986 - "node_modules/@humanwhocodes/retry": { 987 - "version": "0.4.3", 988 - "dev": true, 989 - "license": "Apache-2.0", 990 - "engines": { 991 - "node": ">=18.18" 992 - }, 993 - "funding": { 994 - "type": "github", 995 - "url": "https://github.com/sponsors/nzakas" 996 - } 997 - }, 998 - "node_modules/@img/colour": { 999 - "version": "1.0.0", 1000 - "license": "MIT", 1001 - "optional": true, 1002 - "engines": { 1003 - "node": ">=18" 1004 - } 1005 - }, 1006 - "node_modules/@img/sharp-darwin-arm64": { 1007 - "version": "0.34.5", 1008 - "cpu": [ 1009 - "arm64" 1010 - ], 1011 - "license": "Apache-2.0", 1012 - "optional": true, 1013 - "os": [ 1014 - "darwin" 1015 - ], 1016 - "engines": { 1017 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1018 - }, 1019 - "funding": { 1020 - "url": "https://opencollective.com/libvips" 1021 - }, 1022 - "optionalDependencies": { 1023 - "@img/sharp-libvips-darwin-arm64": "1.2.4" 1024 - } 1025 - }, 1026 - "node_modules/@img/sharp-darwin-x64": { 1027 - "version": "0.34.5", 1028 - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", 1029 - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", 1030 - "cpu": [ 1031 - "x64" 1032 - ], 1033 - "license": "Apache-2.0", 1034 - "optional": true, 1035 - "os": [ 1036 - "darwin" 1037 - ], 1038 - "engines": { 1039 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1040 - }, 1041 - "funding": { 1042 - "url": "https://opencollective.com/libvips" 1043 - }, 1044 - "optionalDependencies": { 1045 - "@img/sharp-libvips-darwin-x64": "1.2.4" 1046 - } 1047 - }, 1048 - "node_modules/@img/sharp-libvips-darwin-arm64": { 1049 - "version": "1.2.4", 1050 - "cpu": [ 1051 - "arm64" 1052 - ], 1053 - "license": "LGPL-3.0-or-later", 1054 - "optional": true, 1055 - "os": [ 1056 - "darwin" 1057 - ], 1058 - "funding": { 1059 - "url": "https://opencollective.com/libvips" 1060 - } 1061 - }, 1062 - "node_modules/@img/sharp-libvips-darwin-x64": { 1063 - "version": "1.2.4", 1064 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", 1065 - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", 1066 - "cpu": [ 1067 - "x64" 1068 - ], 1069 - "license": "LGPL-3.0-or-later", 1070 - "optional": true, 1071 - "os": [ 1072 - "darwin" 1073 - ], 1074 - "funding": { 1075 - "url": "https://opencollective.com/libvips" 1076 - } 1077 - }, 1078 - "node_modules/@img/sharp-libvips-linux-arm": { 1079 - "version": "1.2.4", 1080 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", 1081 - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", 1082 - "cpu": [ 1083 - "arm" 1084 - ], 1085 - "license": "LGPL-3.0-or-later", 1086 - "optional": true, 1087 - "os": [ 1088 - "linux" 1089 - ], 1090 - "funding": { 1091 - "url": "https://opencollective.com/libvips" 1092 - } 1093 - }, 1094 - "node_modules/@img/sharp-libvips-linux-arm64": { 1095 - "version": "1.2.4", 1096 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", 1097 - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", 1098 - "cpu": [ 1099 - "arm64" 1100 - ], 1101 - "license": "LGPL-3.0-or-later", 1102 - "optional": true, 1103 - "os": [ 1104 - "linux" 1105 - ], 1106 - "funding": { 1107 - "url": "https://opencollective.com/libvips" 1108 - } 1109 - }, 1110 - "node_modules/@img/sharp-libvips-linux-ppc64": { 1111 - "version": "1.2.4", 1112 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", 1113 - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", 1114 - "cpu": [ 1115 - "ppc64" 1116 - ], 1117 - "license": "LGPL-3.0-or-later", 1118 - "optional": true, 1119 - "os": [ 1120 - "linux" 1121 - ], 1122 - "funding": { 1123 - "url": "https://opencollective.com/libvips" 1124 - } 1125 - }, 1126 - "node_modules/@img/sharp-libvips-linux-riscv64": { 1127 - "version": "1.2.4", 1128 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", 1129 - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", 1130 - "cpu": [ 1131 - "riscv64" 1132 - ], 1133 - "license": "LGPL-3.0-or-later", 1134 - "optional": true, 1135 - "os": [ 1136 - "linux" 1137 - ], 1138 - "funding": { 1139 - "url": "https://opencollective.com/libvips" 1140 - } 1141 - }, 1142 - "node_modules/@img/sharp-libvips-linux-s390x": { 1143 - "version": "1.2.4", 1144 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", 1145 - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", 1146 - "cpu": [ 1147 - "s390x" 1148 - ], 1149 - "license": "LGPL-3.0-or-later", 1150 - "optional": true, 1151 - "os": [ 1152 - "linux" 1153 - ], 1154 - "funding": { 1155 - "url": "https://opencollective.com/libvips" 1156 - } 1157 - }, 1158 - "node_modules/@img/sharp-libvips-linux-x64": { 1159 - "version": "1.2.4", 1160 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", 1161 - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", 1162 - "cpu": [ 1163 - "x64" 1164 - ], 1165 - "license": "LGPL-3.0-or-later", 1166 - "optional": true, 1167 - "os": [ 1168 - "linux" 1169 - ], 1170 - "funding": { 1171 - "url": "https://opencollective.com/libvips" 1172 - } 1173 - }, 1174 - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 1175 - "version": "1.2.4", 1176 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", 1177 - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", 1178 - "cpu": [ 1179 - "arm64" 1180 - ], 1181 - "license": "LGPL-3.0-or-later", 1182 - "optional": true, 1183 - "os": [ 1184 - "linux" 1185 - ], 1186 - "funding": { 1187 - "url": "https://opencollective.com/libvips" 1188 - } 1189 - }, 1190 - "node_modules/@img/sharp-libvips-linuxmusl-x64": { 1191 - "version": "1.2.4", 1192 - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", 1193 - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", 1194 - "cpu": [ 1195 - "x64" 1196 - ], 1197 - "license": "LGPL-3.0-or-later", 1198 - "optional": true, 1199 - "os": [ 1200 - "linux" 1201 - ], 1202 - "funding": { 1203 - "url": "https://opencollective.com/libvips" 1204 - } 1205 - }, 1206 - "node_modules/@img/sharp-linux-arm": { 1207 - "version": "0.34.5", 1208 - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", 1209 - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", 1210 - "cpu": [ 1211 - "arm" 1212 - ], 1213 - "license": "Apache-2.0", 1214 - "optional": true, 1215 - "os": [ 1216 - "linux" 1217 - ], 1218 - "engines": { 1219 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1220 - }, 1221 - "funding": { 1222 - "url": "https://opencollective.com/libvips" 1223 - }, 1224 - "optionalDependencies": { 1225 - "@img/sharp-libvips-linux-arm": "1.2.4" 1226 - } 1227 - }, 1228 - "node_modules/@img/sharp-linux-arm64": { 1229 - "version": "0.34.5", 1230 - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", 1231 - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", 1232 - "cpu": [ 1233 - "arm64" 1234 - ], 1235 - "license": "Apache-2.0", 1236 - "optional": true, 1237 - "os": [ 1238 - "linux" 1239 - ], 1240 - "engines": { 1241 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1242 - }, 1243 - "funding": { 1244 - "url": "https://opencollective.com/libvips" 1245 - }, 1246 - "optionalDependencies": { 1247 - "@img/sharp-libvips-linux-arm64": "1.2.4" 1248 - } 1249 - }, 1250 - "node_modules/@img/sharp-linux-ppc64": { 1251 - "version": "0.34.5", 1252 - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", 1253 - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", 1254 - "cpu": [ 1255 - "ppc64" 1256 - ], 1257 - "license": "Apache-2.0", 1258 - "optional": true, 1259 - "os": [ 1260 - "linux" 1261 - ], 1262 - "engines": { 1263 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1264 - }, 1265 - "funding": { 1266 - "url": "https://opencollective.com/libvips" 1267 - }, 1268 - "optionalDependencies": { 1269 - "@img/sharp-libvips-linux-ppc64": "1.2.4" 1270 - } 1271 - }, 1272 - "node_modules/@img/sharp-linux-riscv64": { 1273 - "version": "0.34.5", 1274 - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", 1275 - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", 1276 - "cpu": [ 1277 - "riscv64" 1278 - ], 1279 - "license": "Apache-2.0", 1280 - "optional": true, 1281 - "os": [ 1282 - "linux" 1283 - ], 1284 - "engines": { 1285 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1286 - }, 1287 - "funding": { 1288 - "url": "https://opencollective.com/libvips" 1289 - }, 1290 - "optionalDependencies": { 1291 - "@img/sharp-libvips-linux-riscv64": "1.2.4" 1292 - } 1293 - }, 1294 - "node_modules/@img/sharp-linux-s390x": { 1295 - "version": "0.34.5", 1296 - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", 1297 - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", 1298 - "cpu": [ 1299 - "s390x" 1300 - ], 1301 - "license": "Apache-2.0", 1302 - "optional": true, 1303 - "os": [ 1304 - "linux" 1305 - ], 1306 - "engines": { 1307 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1308 - }, 1309 - "funding": { 1310 - "url": "https://opencollective.com/libvips" 1311 - }, 1312 - "optionalDependencies": { 1313 - "@img/sharp-libvips-linux-s390x": "1.2.4" 1314 - } 1315 - }, 1316 - "node_modules/@img/sharp-linux-x64": { 1317 - "version": "0.34.5", 1318 - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", 1319 - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", 1320 - "cpu": [ 1321 - "x64" 1322 - ], 1323 - "license": "Apache-2.0", 1324 - "optional": true, 1325 - "os": [ 1326 - "linux" 1327 - ], 1328 - "engines": { 1329 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1330 - }, 1331 - "funding": { 1332 - "url": "https://opencollective.com/libvips" 1333 - }, 1334 - "optionalDependencies": { 1335 - "@img/sharp-libvips-linux-x64": "1.2.4" 1336 - } 1337 - }, 1338 - "node_modules/@img/sharp-linuxmusl-arm64": { 1339 - "version": "0.34.5", 1340 - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", 1341 - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", 1342 - "cpu": [ 1343 - "arm64" 1344 - ], 1345 - "license": "Apache-2.0", 1346 - "optional": true, 1347 - "os": [ 1348 - "linux" 1349 - ], 1350 - "engines": { 1351 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1352 - }, 1353 - "funding": { 1354 - "url": "https://opencollective.com/libvips" 1355 - }, 1356 - "optionalDependencies": { 1357 - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" 1358 - } 1359 - }, 1360 - "node_modules/@img/sharp-linuxmusl-x64": { 1361 - "version": "0.34.5", 1362 - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", 1363 - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", 1364 - "cpu": [ 1365 - "x64" 1366 - ], 1367 - "license": "Apache-2.0", 1368 - "optional": true, 1369 - "os": [ 1370 - "linux" 1371 - ], 1372 - "engines": { 1373 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1374 - }, 1375 - "funding": { 1376 - "url": "https://opencollective.com/libvips" 1377 - }, 1378 - "optionalDependencies": { 1379 - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" 1380 - } 1381 - }, 1382 - "node_modules/@img/sharp-wasm32": { 1383 - "version": "0.34.5", 1384 - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", 1385 - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", 1386 - "cpu": [ 1387 - "wasm32" 1388 - ], 1389 - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 1390 - "optional": true, 1391 - "dependencies": { 1392 - "@emnapi/runtime": "^1.7.0" 1393 - }, 1394 - "engines": { 1395 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1396 - }, 1397 - "funding": { 1398 - "url": "https://opencollective.com/libvips" 1399 - } 1400 - }, 1401 - "node_modules/@img/sharp-win32-arm64": { 1402 - "version": "0.34.5", 1403 - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", 1404 - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", 1405 - "cpu": [ 1406 - "arm64" 1407 - ], 1408 - "license": "Apache-2.0 AND LGPL-3.0-or-later", 1409 - "optional": true, 1410 - "os": [ 1411 - "win32" 1412 - ], 1413 - "engines": { 1414 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1415 - }, 1416 - "funding": { 1417 - "url": "https://opencollective.com/libvips" 1418 - } 1419 - }, 1420 - "node_modules/@img/sharp-win32-ia32": { 1421 - "version": "0.34.5", 1422 - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", 1423 - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", 1424 - "cpu": [ 1425 - "ia32" 1426 - ], 1427 - "license": "Apache-2.0 AND LGPL-3.0-or-later", 1428 - "optional": true, 1429 - "os": [ 1430 - "win32" 1431 - ], 1432 - "engines": { 1433 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1434 - }, 1435 - "funding": { 1436 - "url": "https://opencollective.com/libvips" 1437 - } 1438 - }, 1439 - "node_modules/@img/sharp-win32-x64": { 1440 - "version": "0.34.5", 1441 - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", 1442 - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", 1443 - "cpu": [ 1444 - "x64" 1445 - ], 1446 - "license": "Apache-2.0 AND LGPL-3.0-or-later", 1447 - "optional": true, 1448 - "os": [ 1449 - "win32" 1450 - ], 1451 - "engines": { 1452 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 1453 - }, 1454 - "funding": { 1455 - "url": "https://opencollective.com/libvips" 1456 - } 1457 - }, 1458 - "node_modules/@isaacs/balanced-match": { 1459 - "version": "4.0.1", 1460 - "dev": true, 1461 - "license": "MIT", 1462 - "engines": { 1463 - "node": "20 || >=22" 1464 - } 1465 - }, 1466 - "node_modules/@isaacs/brace-expansion": { 1467 - "version": "5.0.1", 1468 - "dev": true, 1469 - "license": "MIT", 1470 - "dependencies": { 1471 - "@isaacs/balanced-match": "^4.0.1" 1472 - }, 1473 - "engines": { 1474 - "node": "20 || >=22" 1475 - } 1476 - }, 1477 - "node_modules/@jridgewell/gen-mapping": { 1478 - "version": "0.3.13", 1479 - "license": "MIT", 1480 - "dependencies": { 1481 - "@jridgewell/sourcemap-codec": "^1.5.0", 1482 - "@jridgewell/trace-mapping": "^0.3.24" 1483 - } 1484 - }, 1485 - "node_modules/@jridgewell/remapping": { 1486 - "version": "2.3.5", 1487 - "license": "MIT", 1488 - "dependencies": { 1489 - "@jridgewell/gen-mapping": "^0.3.5", 1490 - "@jridgewell/trace-mapping": "^0.3.24" 1491 - } 1492 - }, 1493 - "node_modules/@jridgewell/resolve-uri": { 1494 - "version": "3.1.2", 1495 - "license": "MIT", 1496 - "engines": { 1497 - "node": ">=6.0.0" 1498 - } 1499 - }, 1500 - "node_modules/@jridgewell/sourcemap-codec": { 1501 - "version": "1.5.5", 1502 - "license": "MIT" 1503 - }, 1504 - "node_modules/@jridgewell/trace-mapping": { 1505 - "version": "0.3.31", 1506 - "license": "MIT", 1507 - "dependencies": { 1508 - "@jridgewell/resolve-uri": "^3.1.0", 1509 - "@jridgewell/sourcemap-codec": "^1.4.14" 1510 - } 1511 - }, 1512 - "node_modules/@nanostores/react": { 1513 - "version": "1.0.0", 1514 - "funding": [ 1515 - { 1516 - "type": "github", 1517 - "url": "https://github.com/sponsors/ai" 1518 - } 1519 - ], 1520 - "license": "MIT", 1521 - "engines": { 1522 - "node": "^20.0.0 || >=22.0.0" 1523 - }, 1524 - "peerDependencies": { 1525 - "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0", 1526 - "react": ">=18.0.0" 1527 - } 1528 - }, 1529 - "node_modules/@nodelib/fs.scandir": { 1530 - "version": "2.1.5", 1531 - "license": "MIT", 1532 - "dependencies": { 1533 - "@nodelib/fs.stat": "2.0.5", 1534 - "run-parallel": "^1.1.9" 1535 - }, 1536 - "engines": { 1537 - "node": ">= 8" 1538 - } 1539 - }, 1540 - "node_modules/@nodelib/fs.stat": { 1541 - "version": "2.0.5", 1542 - "license": "MIT", 1543 - "engines": { 1544 - "node": ">= 8" 1545 - } 1546 - }, 1547 - "node_modules/@nodelib/fs.walk": { 1548 - "version": "1.2.8", 1549 - "license": "MIT", 1550 - "dependencies": { 1551 - "@nodelib/fs.scandir": "2.1.5", 1552 - "fastq": "^1.6.0" 1553 - }, 1554 - "engines": { 1555 - "node": ">= 8" 1556 - } 1557 - }, 1558 - "node_modules/@oslojs/encoding": { 1559 - "version": "1.1.0", 1560 - "license": "MIT" 1561 - }, 1562 - "node_modules/@pkgr/core": { 1563 - "version": "0.2.9", 1564 - "dev": true, 1565 - "license": "MIT", 1566 - "engines": { 1567 - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" 1568 - }, 1569 - "funding": { 1570 - "url": "https://opencollective.com/pkgr" 1571 - } 1572 - }, 1573 - "node_modules/@resvg/resvg-js": { 1574 - "version": "2.6.2", 1575 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", 1576 - "integrity": "sha512-xBaJish5OeGmniDj9cW5PRa/PtmuVU3ziqrbr5xJj901ZDN4TosrVaNZpEiLZAxdfnhAe7uQ7QFWfjPe9d9K2Q==", 1577 - "license": "MPL-2.0", 1578 - "engines": { 1579 - "node": ">= 10" 1580 - }, 1581 - "optionalDependencies": { 1582 - "@resvg/resvg-js-android-arm-eabi": "2.6.2", 1583 - "@resvg/resvg-js-android-arm64": "2.6.2", 1584 - "@resvg/resvg-js-darwin-arm64": "2.6.2", 1585 - "@resvg/resvg-js-darwin-x64": "2.6.2", 1586 - "@resvg/resvg-js-linux-arm-gnueabihf": "2.6.2", 1587 - "@resvg/resvg-js-linux-arm64-gnu": "2.6.2", 1588 - "@resvg/resvg-js-linux-arm64-musl": "2.6.2", 1589 - "@resvg/resvg-js-linux-x64-gnu": "2.6.2", 1590 - "@resvg/resvg-js-linux-x64-musl": "2.6.2", 1591 - "@resvg/resvg-js-win32-arm64-msvc": "2.6.2", 1592 - "@resvg/resvg-js-win32-ia32-msvc": "2.6.2", 1593 - "@resvg/resvg-js-win32-x64-msvc": "2.6.2" 1594 - } 1595 - }, 1596 - "node_modules/@resvg/resvg-js-android-arm-eabi": { 1597 - "version": "2.6.2", 1598 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz", 1599 - "integrity": "sha512-FrJibrAk6v29eabIPgcTUMPXiEz8ssrAk7TXxsiZzww9UTQ1Z5KAbFJs+Z0Ez+VZTYgnE5IQJqBcoSiMebtPHA==", 1600 - "cpu": [ 1601 - "arm" 1602 - ], 1603 - "license": "MPL-2.0", 1604 - "optional": true, 1605 - "os": [ 1606 - "android" 1607 - ], 1608 - "engines": { 1609 - "node": ">= 10" 1610 - } 1611 - }, 1612 - "node_modules/@resvg/resvg-js-android-arm64": { 1613 - "version": "2.6.2", 1614 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.6.2.tgz", 1615 - "integrity": "sha512-VcOKezEhm2VqzXpcIJoITuvUS/fcjIw5NA/w3tjzWyzmvoCdd+QXIqy3FBGulWdClvp4g+IfUemigrkLThSjAQ==", 1616 - "cpu": [ 1617 - "arm64" 1618 - ], 1619 - "license": "MPL-2.0", 1620 - "optional": true, 1621 - "os": [ 1622 - "android" 1623 - ], 1624 - "engines": { 1625 - "node": ">= 10" 1626 - } 1627 - }, 1628 - "node_modules/@resvg/resvg-js-darwin-arm64": { 1629 - "version": "2.6.2", 1630 - "cpu": [ 1631 - "arm64" 1632 - ], 1633 - "license": "MPL-2.0", 1634 - "optional": true, 1635 - "os": [ 1636 - "darwin" 1637 - ], 1638 - "engines": { 1639 - "node": ">= 10" 1640 - } 1641 - }, 1642 - "node_modules/@resvg/resvg-js-darwin-x64": { 1643 - "version": "2.6.2", 1644 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.6.2.tgz", 1645 - "integrity": "sha512-GInyZLjgWDfsVT6+SHxQVRwNzV0AuA1uqGsOAW+0th56J7Nh6bHHKXHBWzUrihxMetcFDmQMAX1tZ1fZDYSRsw==", 1646 - "cpu": [ 1647 - "x64" 1648 - ], 1649 - "license": "MPL-2.0", 1650 - "optional": true, 1651 - "os": [ 1652 - "darwin" 1653 - ], 1654 - "engines": { 1655 - "node": ">= 10" 1656 - } 1657 - }, 1658 - "node_modules/@resvg/resvg-js-linux-arm-gnueabihf": { 1659 - "version": "2.6.2", 1660 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.6.2.tgz", 1661 - "integrity": "sha512-YIV3u/R9zJbpqTTNwTZM5/ocWetDKGsro0SWp70eGEM9eV2MerWyBRZnQIgzU3YBnSBQ1RcxRZvY/UxwESfZIw==", 1662 - "cpu": [ 1663 - "arm" 1664 - ], 1665 - "license": "MPL-2.0", 1666 - "optional": true, 1667 - "os": [ 1668 - "linux" 1669 - ], 1670 - "engines": { 1671 - "node": ">= 10" 1672 - } 1673 - }, 1674 - "node_modules/@resvg/resvg-js-linux-arm64-gnu": { 1675 - "version": "2.6.2", 1676 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.6.2.tgz", 1677 - "integrity": "sha512-zc2BlJSim7YR4FZDQ8OUoJg5holYzdiYMeobb9pJuGDidGL9KZUv7SbiD4E8oZogtYY42UZEap7dqkkYuA91pg==", 1678 - "cpu": [ 1679 - "arm64" 1680 - ], 1681 - "license": "MPL-2.0", 1682 - "optional": true, 1683 - "os": [ 1684 - "linux" 1685 - ], 1686 - "engines": { 1687 - "node": ">= 10" 1688 - } 1689 - }, 1690 - "node_modules/@resvg/resvg-js-linux-arm64-musl": { 1691 - "version": "2.6.2", 1692 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.6.2.tgz", 1693 - "integrity": "sha512-3h3dLPWNgSsD4lQBJPb4f+kvdOSJHa5PjTYVsWHxLUzH4IFTJUAnmuWpw4KqyQ3NA5QCyhw4TWgxk3jRkQxEKg==", 1694 - "cpu": [ 1695 - "arm64" 1696 - ], 1697 - "license": "MPL-2.0", 1698 - "optional": true, 1699 - "os": [ 1700 - "linux" 1701 - ], 1702 - "engines": { 1703 - "node": ">= 10" 1704 - } 1705 - }, 1706 - "node_modules/@resvg/resvg-js-linux-x64-gnu": { 1707 - "version": "2.6.2", 1708 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.6.2.tgz", 1709 - "integrity": "sha512-IVUe+ckIerA7xMZ50duAZzwf1U7khQe2E0QpUxu5MBJNao5RqC0zwV/Zm965vw6D3gGFUl7j4m+oJjubBVoftw==", 1710 - "cpu": [ 1711 - "x64" 1712 - ], 1713 - "license": "MPL-2.0", 1714 - "optional": true, 1715 - "os": [ 1716 - "linux" 1717 - ], 1718 - "engines": { 1719 - "node": ">= 10" 1720 - } 1721 - }, 1722 - "node_modules/@resvg/resvg-js-linux-x64-musl": { 1723 - "version": "2.6.2", 1724 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.6.2.tgz", 1725 - "integrity": "sha512-UOf83vqTzoYQO9SZ0fPl2ZIFtNIz/Rr/y+7X8XRX1ZnBYsQ/tTb+cj9TE+KHOdmlTFBxhYzVkP2lRByCzqi4jQ==", 1726 - "cpu": [ 1727 - "x64" 1728 - ], 1729 - "license": "MPL-2.0", 1730 - "optional": true, 1731 - "os": [ 1732 - "linux" 1733 - ], 1734 - "engines": { 1735 - "node": ">= 10" 1736 - } 1737 - }, 1738 - "node_modules/@resvg/resvg-js-win32-arm64-msvc": { 1739 - "version": "2.6.2", 1740 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.6.2.tgz", 1741 - "integrity": "sha512-7C/RSgCa+7vqZ7qAbItfiaAWhyRSoD4l4BQAbVDqRRsRgY+S+hgS3in0Rxr7IorKUpGE69X48q6/nOAuTJQxeQ==", 1742 - "cpu": [ 1743 - "arm64" 1744 - ], 1745 - "license": "MPL-2.0", 1746 - "optional": true, 1747 - "os": [ 1748 - "win32" 1749 - ], 1750 - "engines": { 1751 - "node": ">= 10" 1752 - } 1753 - }, 1754 - "node_modules/@resvg/resvg-js-win32-ia32-msvc": { 1755 - "version": "2.6.2", 1756 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.6.2.tgz", 1757 - "integrity": "sha512-har4aPAlvjnLcil40AC77YDIk6loMawuJwFINEM7n0pZviwMkMvjb2W5ZirsNOZY4aDbo5tLx0wNMREp5Brk+w==", 1758 - "cpu": [ 1759 - "ia32" 1760 - ], 1761 - "license": "MPL-2.0", 1762 - "optional": true, 1763 - "os": [ 1764 - "win32" 1765 - ], 1766 - "engines": { 1767 - "node": ">= 10" 1768 - } 1769 - }, 1770 - "node_modules/@resvg/resvg-js-win32-x64-msvc": { 1771 - "version": "2.6.2", 1772 - "resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.6.2.tgz", 1773 - "integrity": "sha512-ZXtYhtUr5SSaBrUDq7DiyjOFJqBVL/dOBN7N/qmi/pO0IgiWW/f/ue3nbvu9joWE5aAKDoIzy/CxsY0suwGosQ==", 1774 - "cpu": [ 1775 - "x64" 1776 - ], 1777 - "license": "MPL-2.0", 1778 - "optional": true, 1779 - "os": [ 1780 - "win32" 1781 - ], 1782 - "engines": { 1783 - "node": ">= 10" 1784 - } 1785 - }, 1786 - "node_modules/@rolldown/pluginutils": { 1787 - "version": "1.0.0-beta.27", 1788 - "license": "MIT" 1789 - }, 1790 - "node_modules/@rollup/pluginutils": { 1791 - "version": "5.3.0", 1792 - "license": "MIT", 1793 - "dependencies": { 1794 - "@types/estree": "^1.0.0", 1795 - "estree-walker": "^2.0.2", 1796 - "picomatch": "^4.0.2" 1797 - }, 1798 - "engines": { 1799 - "node": ">=14.0.0" 1800 - }, 1801 - "peerDependencies": { 1802 - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 1803 - }, 1804 - "peerDependenciesMeta": { 1805 - "rollup": { 1806 - "optional": true 1807 - } 1808 - } 1809 - }, 1810 - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { 1811 - "version": "2.0.2", 1812 - "license": "MIT" 1813 - }, 1814 - "node_modules/@rollup/rollup-android-arm-eabi": { 1815 - "version": "4.57.1", 1816 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", 1817 - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", 1818 - "cpu": [ 1819 - "arm" 1820 - ], 1821 - "license": "MIT", 1822 - "optional": true, 1823 - "os": [ 1824 - "android" 1825 - ] 1826 - }, 1827 - "node_modules/@rollup/rollup-android-arm64": { 1828 - "version": "4.57.1", 1829 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", 1830 - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", 1831 - "cpu": [ 1832 - "arm64" 1833 - ], 1834 - "license": "MIT", 1835 - "optional": true, 1836 - "os": [ 1837 - "android" 1838 - ] 1839 - }, 1840 - "node_modules/@rollup/rollup-darwin-arm64": { 1841 - "version": "4.57.1", 1842 - "cpu": [ 1843 - "arm64" 1844 - ], 1845 - "license": "MIT", 1846 - "optional": true, 1847 - "os": [ 1848 - "darwin" 1849 - ] 1850 - }, 1851 - "node_modules/@rollup/rollup-darwin-x64": { 1852 - "version": "4.57.1", 1853 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", 1854 - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", 1855 - "cpu": [ 1856 - "x64" 1857 - ], 1858 - "license": "MIT", 1859 - "optional": true, 1860 - "os": [ 1861 - "darwin" 1862 - ] 1863 - }, 1864 - "node_modules/@rollup/rollup-freebsd-arm64": { 1865 - "version": "4.57.1", 1866 - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", 1867 - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", 1868 - "cpu": [ 1869 - "arm64" 1870 - ], 1871 - "license": "MIT", 1872 - "optional": true, 1873 - "os": [ 1874 - "freebsd" 1875 - ] 1876 - }, 1877 - "node_modules/@rollup/rollup-freebsd-x64": { 1878 - "version": "4.57.1", 1879 - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", 1880 - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", 1881 - "cpu": [ 1882 - "x64" 1883 - ], 1884 - "license": "MIT", 1885 - "optional": true, 1886 - "os": [ 1887 - "freebsd" 1888 - ] 1889 - }, 1890 - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1891 - "version": "4.57.1", 1892 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", 1893 - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", 1894 - "cpu": [ 1895 - "arm" 1896 - ], 1897 - "license": "MIT", 1898 - "optional": true, 1899 - "os": [ 1900 - "linux" 1901 - ] 1902 - }, 1903 - "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1904 - "version": "4.57.1", 1905 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", 1906 - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", 1907 - "cpu": [ 1908 - "arm" 1909 - ], 1910 - "license": "MIT", 1911 - "optional": true, 1912 - "os": [ 1913 - "linux" 1914 - ] 1915 - }, 1916 - "node_modules/@rollup/rollup-linux-arm64-gnu": { 1917 - "version": "4.57.1", 1918 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", 1919 - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", 1920 - "cpu": [ 1921 - "arm64" 1922 - ], 1923 - "license": "MIT", 1924 - "optional": true, 1925 - "os": [ 1926 - "linux" 1927 - ] 1928 - }, 1929 - "node_modules/@rollup/rollup-linux-arm64-musl": { 1930 - "version": "4.57.1", 1931 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", 1932 - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", 1933 - "cpu": [ 1934 - "arm64" 1935 - ], 1936 - "license": "MIT", 1937 - "optional": true, 1938 - "os": [ 1939 - "linux" 1940 - ] 1941 - }, 1942 - "node_modules/@rollup/rollup-linux-loong64-gnu": { 1943 - "version": "4.57.1", 1944 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", 1945 - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", 1946 - "cpu": [ 1947 - "loong64" 1948 - ], 1949 - "license": "MIT", 1950 - "optional": true, 1951 - "os": [ 1952 - "linux" 1953 - ] 1954 - }, 1955 - "node_modules/@rollup/rollup-linux-loong64-musl": { 1956 - "version": "4.57.1", 1957 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", 1958 - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", 1959 - "cpu": [ 1960 - "loong64" 1961 - ], 1962 - "license": "MIT", 1963 - "optional": true, 1964 - "os": [ 1965 - "linux" 1966 - ] 1967 - }, 1968 - "node_modules/@rollup/rollup-linux-ppc64-gnu": { 1969 - "version": "4.57.1", 1970 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", 1971 - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", 1972 - "cpu": [ 1973 - "ppc64" 1974 - ], 1975 - "license": "MIT", 1976 - "optional": true, 1977 - "os": [ 1978 - "linux" 1979 - ] 1980 - }, 1981 - "node_modules/@rollup/rollup-linux-ppc64-musl": { 1982 - "version": "4.57.1", 1983 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", 1984 - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", 1985 - "cpu": [ 1986 - "ppc64" 1987 - ], 1988 - "license": "MIT", 1989 - "optional": true, 1990 - "os": [ 1991 - "linux" 1992 - ] 1993 - }, 1994 - "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1995 - "version": "4.57.1", 1996 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", 1997 - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", 1998 - "cpu": [ 1999 - "riscv64" 2000 - ], 2001 - "license": "MIT", 2002 - "optional": true, 2003 - "os": [ 2004 - "linux" 2005 - ] 2006 - }, 2007 - "node_modules/@rollup/rollup-linux-riscv64-musl": { 2008 - "version": "4.57.1", 2009 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", 2010 - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", 2011 - "cpu": [ 2012 - "riscv64" 2013 - ], 2014 - "license": "MIT", 2015 - "optional": true, 2016 - "os": [ 2017 - "linux" 2018 - ] 2019 - }, 2020 - "node_modules/@rollup/rollup-linux-s390x-gnu": { 2021 - "version": "4.57.1", 2022 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", 2023 - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", 2024 - "cpu": [ 2025 - "s390x" 2026 - ], 2027 - "license": "MIT", 2028 - "optional": true, 2029 - "os": [ 2030 - "linux" 2031 - ] 2032 - }, 2033 - "node_modules/@rollup/rollup-linux-x64-gnu": { 2034 - "version": "4.57.1", 2035 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", 2036 - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", 2037 - "cpu": [ 2038 - "x64" 2039 - ], 2040 - "license": "MIT", 2041 - "optional": true, 2042 - "os": [ 2043 - "linux" 2044 - ] 2045 - }, 2046 - "node_modules/@rollup/rollup-linux-x64-musl": { 2047 - "version": "4.57.1", 2048 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", 2049 - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", 2050 - "cpu": [ 2051 - "x64" 2052 - ], 2053 - "license": "MIT", 2054 - "optional": true, 2055 - "os": [ 2056 - "linux" 2057 - ] 2058 - }, 2059 - "node_modules/@rollup/rollup-openbsd-x64": { 2060 - "version": "4.57.1", 2061 - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", 2062 - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", 2063 - "cpu": [ 2064 - "x64" 2065 - ], 2066 - "license": "MIT", 2067 - "optional": true, 2068 - "os": [ 2069 - "openbsd" 2070 - ] 2071 - }, 2072 - "node_modules/@rollup/rollup-openharmony-arm64": { 2073 - "version": "4.57.1", 2074 - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", 2075 - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", 2076 - "cpu": [ 2077 - "arm64" 2078 - ], 2079 - "license": "MIT", 2080 - "optional": true, 2081 - "os": [ 2082 - "openharmony" 2083 - ] 2084 - }, 2085 - "node_modules/@rollup/rollup-win32-arm64-msvc": { 2086 - "version": "4.57.1", 2087 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", 2088 - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", 2089 - "cpu": [ 2090 - "arm64" 2091 - ], 2092 - "license": "MIT", 2093 - "optional": true, 2094 - "os": [ 2095 - "win32" 2096 - ] 2097 - }, 2098 - "node_modules/@rollup/rollup-win32-ia32-msvc": { 2099 - "version": "4.57.1", 2100 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", 2101 - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", 2102 - "cpu": [ 2103 - "ia32" 2104 - ], 2105 - "license": "MIT", 2106 - "optional": true, 2107 - "os": [ 2108 - "win32" 2109 - ] 2110 - }, 2111 - "node_modules/@rollup/rollup-win32-x64-gnu": { 2112 - "version": "4.57.1", 2113 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", 2114 - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", 2115 - "cpu": [ 2116 - "x64" 2117 - ], 2118 - "license": "MIT", 2119 - "optional": true, 2120 - "os": [ 2121 - "win32" 2122 - ] 2123 - }, 2124 - "node_modules/@rollup/rollup-win32-x64-msvc": { 2125 - "version": "4.57.1", 2126 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", 2127 - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", 2128 - "cpu": [ 2129 - "x64" 2130 - ], 2131 - "license": "MIT", 2132 - "optional": true, 2133 - "os": [ 2134 - "win32" 2135 - ] 2136 - }, 2137 - "node_modules/@shikijs/core": { 2138 - "version": "3.22.0", 2139 - "license": "MIT", 2140 - "dependencies": { 2141 - "@shikijs/types": "3.22.0", 2142 - "@shikijs/vscode-textmate": "^10.0.2", 2143 - "@types/hast": "^3.0.4", 2144 - "hast-util-to-html": "^9.0.5" 2145 - } 2146 - }, 2147 - "node_modules/@shikijs/engine-javascript": { 2148 - "version": "3.22.0", 2149 - "license": "MIT", 2150 - "dependencies": { 2151 - "@shikijs/types": "3.22.0", 2152 - "@shikijs/vscode-textmate": "^10.0.2", 2153 - "oniguruma-to-es": "^4.3.4" 2154 - } 2155 - }, 2156 - "node_modules/@shikijs/engine-oniguruma": { 2157 - "version": "3.22.0", 2158 - "license": "MIT", 2159 - "dependencies": { 2160 - "@shikijs/types": "3.22.0", 2161 - "@shikijs/vscode-textmate": "^10.0.2" 2162 - } 2163 - }, 2164 - "node_modules/@shikijs/langs": { 2165 - "version": "3.22.0", 2166 - "license": "MIT", 2167 - "dependencies": { 2168 - "@shikijs/types": "3.22.0" 2169 - } 2170 - }, 2171 - "node_modules/@shikijs/themes": { 2172 - "version": "3.22.0", 2173 - "license": "MIT", 2174 - "dependencies": { 2175 - "@shikijs/types": "3.22.0" 2176 - } 2177 - }, 2178 - "node_modules/@shikijs/types": { 2179 - "version": "3.22.0", 2180 - "license": "MIT", 2181 - "dependencies": { 2182 - "@shikijs/vscode-textmate": "^10.0.2", 2183 - "@types/hast": "^3.0.4" 2184 - } 2185 - }, 2186 - "node_modules/@shikijs/vscode-textmate": { 2187 - "version": "10.0.2", 2188 - "license": "MIT" 2189 - }, 2190 - "node_modules/@shuding/opentype.js": { 2191 - "version": "1.4.0-beta.0", 2192 - "license": "MIT", 2193 - "dependencies": { 2194 - "fflate": "^0.7.3", 2195 - "string.prototype.codepointat": "^0.2.1" 2196 - }, 2197 - "bin": { 2198 - "ot": "bin/ot" 2199 - }, 2200 - "engines": { 2201 - "node": ">= 8.0.0" 2202 - } 2203 - }, 2204 - "node_modules/@tailwindcss/node": { 2205 - "version": "4.1.18", 2206 - "license": "MIT", 2207 - "dependencies": { 2208 - "@jridgewell/remapping": "^2.3.4", 2209 - "enhanced-resolve": "^5.18.3", 2210 - "jiti": "^2.6.1", 2211 - "lightningcss": "1.30.2", 2212 - "magic-string": "^0.30.21", 2213 - "source-map-js": "^1.2.1", 2214 - "tailwindcss": "4.1.18" 2215 - } 2216 - }, 2217 - "node_modules/@tailwindcss/node/node_modules/jiti": { 2218 - "version": "2.6.1", 2219 - "license": "MIT", 2220 - "bin": { 2221 - "jiti": "lib/jiti-cli.mjs" 2222 - } 2223 - }, 2224 - "node_modules/@tailwindcss/node/node_modules/tailwindcss": { 2225 - "version": "4.1.18", 2226 - "license": "MIT" 2227 - }, 2228 - "node_modules/@tailwindcss/oxide": { 2229 - "version": "4.1.18", 2230 - "license": "MIT", 2231 - "engines": { 2232 - "node": ">= 10" 2233 - }, 2234 - "optionalDependencies": { 2235 - "@tailwindcss/oxide-android-arm64": "4.1.18", 2236 - "@tailwindcss/oxide-darwin-arm64": "4.1.18", 2237 - "@tailwindcss/oxide-darwin-x64": "4.1.18", 2238 - "@tailwindcss/oxide-freebsd-x64": "4.1.18", 2239 - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", 2240 - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", 2241 - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", 2242 - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", 2243 - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", 2244 - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", 2245 - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", 2246 - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" 2247 - } 2248 - }, 2249 - "node_modules/@tailwindcss/oxide-android-arm64": { 2250 - "version": "4.1.18", 2251 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", 2252 - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", 2253 - "cpu": [ 2254 - "arm64" 2255 - ], 2256 - "license": "MIT", 2257 - "optional": true, 2258 - "os": [ 2259 - "android" 2260 - ], 2261 - "engines": { 2262 - "node": ">= 10" 2263 - } 2264 - }, 2265 - "node_modules/@tailwindcss/oxide-darwin-arm64": { 2266 - "version": "4.1.18", 2267 - "cpu": [ 2268 - "arm64" 2269 - ], 2270 - "license": "MIT", 2271 - "optional": true, 2272 - "os": [ 2273 - "darwin" 2274 - ], 2275 - "engines": { 2276 - "node": ">= 10" 2277 - } 2278 - }, 2279 - "node_modules/@tailwindcss/oxide-darwin-x64": { 2280 - "version": "4.1.18", 2281 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", 2282 - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", 2283 - "cpu": [ 2284 - "x64" 2285 - ], 2286 - "license": "MIT", 2287 - "optional": true, 2288 - "os": [ 2289 - "darwin" 2290 - ], 2291 - "engines": { 2292 - "node": ">= 10" 2293 - } 2294 - }, 2295 - "node_modules/@tailwindcss/oxide-freebsd-x64": { 2296 - "version": "4.1.18", 2297 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", 2298 - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", 2299 - "cpu": [ 2300 - "x64" 2301 - ], 2302 - "license": "MIT", 2303 - "optional": true, 2304 - "os": [ 2305 - "freebsd" 2306 - ], 2307 - "engines": { 2308 - "node": ">= 10" 2309 - } 2310 - }, 2311 - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 2312 - "version": "4.1.18", 2313 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", 2314 - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", 2315 - "cpu": [ 2316 - "arm" 2317 - ], 2318 - "license": "MIT", 2319 - "optional": true, 2320 - "os": [ 2321 - "linux" 2322 - ], 2323 - "engines": { 2324 - "node": ">= 10" 2325 - } 2326 - }, 2327 - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 2328 - "version": "4.1.18", 2329 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", 2330 - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", 2331 - "cpu": [ 2332 - "arm64" 2333 - ], 2334 - "license": "MIT", 2335 - "optional": true, 2336 - "os": [ 2337 - "linux" 2338 - ], 2339 - "engines": { 2340 - "node": ">= 10" 2341 - } 2342 - }, 2343 - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 2344 - "version": "4.1.18", 2345 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", 2346 - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", 2347 - "cpu": [ 2348 - "arm64" 2349 - ], 2350 - "license": "MIT", 2351 - "optional": true, 2352 - "os": [ 2353 - "linux" 2354 - ], 2355 - "engines": { 2356 - "node": ">= 10" 2357 - } 2358 - }, 2359 - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 2360 - "version": "4.1.18", 2361 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", 2362 - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", 2363 - "cpu": [ 2364 - "x64" 2365 - ], 2366 - "license": "MIT", 2367 - "optional": true, 2368 - "os": [ 2369 - "linux" 2370 - ], 2371 - "engines": { 2372 - "node": ">= 10" 2373 - } 2374 - }, 2375 - "node_modules/@tailwindcss/oxide-linux-x64-musl": { 2376 - "version": "4.1.18", 2377 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", 2378 - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", 2379 - "cpu": [ 2380 - "x64" 2381 - ], 2382 - "license": "MIT", 2383 - "optional": true, 2384 - "os": [ 2385 - "linux" 2386 - ], 2387 - "engines": { 2388 - "node": ">= 10" 2389 - } 2390 - }, 2391 - "node_modules/@tailwindcss/oxide-wasm32-wasi": { 2392 - "version": "4.1.18", 2393 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", 2394 - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", 2395 - "bundleDependencies": [ 2396 - "@napi-rs/wasm-runtime", 2397 - "@emnapi/core", 2398 - "@emnapi/runtime", 2399 - "@tybys/wasm-util", 2400 - "@emnapi/wasi-threads", 2401 - "tslib" 2402 - ], 2403 - "cpu": [ 2404 - "wasm32" 2405 - ], 2406 - "license": "MIT", 2407 - "optional": true, 2408 - "dependencies": { 2409 - "@emnapi/core": "^1.7.1", 2410 - "@emnapi/runtime": "^1.7.1", 2411 - "@emnapi/wasi-threads": "^1.1.0", 2412 - "@napi-rs/wasm-runtime": "^1.1.0", 2413 - "@tybys/wasm-util": "^0.10.1", 2414 - "tslib": "^2.4.0" 2415 - }, 2416 - "engines": { 2417 - "node": ">=14.0.0" 2418 - } 2419 - }, 2420 - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 2421 - "version": "4.1.18", 2422 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", 2423 - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", 2424 - "cpu": [ 2425 - "arm64" 2426 - ], 2427 - "license": "MIT", 2428 - "optional": true, 2429 - "os": [ 2430 - "win32" 2431 - ], 2432 - "engines": { 2433 - "node": ">= 10" 2434 - } 2435 - }, 2436 - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 2437 - "version": "4.1.18", 2438 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", 2439 - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", 2440 - "cpu": [ 2441 - "x64" 2442 - ], 2443 - "license": "MIT", 2444 - "optional": true, 2445 - "os": [ 2446 - "win32" 2447 - ], 2448 - "engines": { 2449 - "node": ">= 10" 2450 - } 2451 - }, 2452 - "node_modules/@tailwindcss/vite": { 2453 - "version": "4.1.18", 2454 - "license": "MIT", 2455 - "dependencies": { 2456 - "@tailwindcss/node": "4.1.18", 2457 - "@tailwindcss/oxide": "4.1.18", 2458 - "tailwindcss": "4.1.18" 2459 - }, 2460 - "peerDependencies": { 2461 - "vite": "^5.2.0 || ^6 || ^7" 2462 - } 2463 - }, 2464 - "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { 2465 - "version": "4.1.18", 2466 - "license": "MIT" 2467 - }, 2468 - "node_modules/@types/babel__core": { 2469 - "version": "7.20.5", 2470 - "license": "MIT", 2471 - "dependencies": { 2472 - "@babel/parser": "^7.20.7", 2473 - "@babel/types": "^7.20.7", 2474 - "@types/babel__generator": "*", 2475 - "@types/babel__template": "*", 2476 - "@types/babel__traverse": "*" 2477 - } 2478 - }, 2479 - "node_modules/@types/babel__generator": { 2480 - "version": "7.27.0", 2481 - "license": "MIT", 2482 - "dependencies": { 2483 - "@babel/types": "^7.0.0" 2484 - } 2485 - }, 2486 - "node_modules/@types/babel__template": { 2487 - "version": "7.4.4", 2488 - "license": "MIT", 2489 - "dependencies": { 2490 - "@babel/parser": "^7.1.0", 2491 - "@babel/types": "^7.0.0" 2492 - } 2493 - }, 2494 - "node_modules/@types/babel__traverse": { 2495 - "version": "7.28.0", 2496 - "license": "MIT", 2497 - "dependencies": { 2498 - "@babel/types": "^7.28.2" 2499 - } 2500 - }, 2501 - "node_modules/@types/debug": { 2502 - "version": "4.1.12", 2503 - "license": "MIT", 2504 - "dependencies": { 2505 - "@types/ms": "*" 2506 - } 2507 - }, 2508 - "node_modules/@types/esrecurse": { 2509 - "version": "4.3.1", 2510 - "dev": true, 2511 - "license": "MIT" 2512 - }, 2513 - "node_modules/@types/estree": { 2514 - "version": "1.0.8", 2515 - "license": "MIT" 2516 - }, 2517 - "node_modules/@types/hast": { 2518 - "version": "3.0.4", 2519 - "license": "MIT", 2520 - "dependencies": { 2521 - "@types/unist": "*" 2522 - } 2523 - }, 2524 - "node_modules/@types/json-schema": { 2525 - "version": "7.0.15", 2526 - "dev": true, 2527 - "license": "MIT" 2528 - }, 2529 - "node_modules/@types/mdast": { 2530 - "version": "4.0.4", 2531 - "license": "MIT", 2532 - "dependencies": { 2533 - "@types/unist": "*" 2534 - } 2535 - }, 2536 - "node_modules/@types/ms": { 2537 - "version": "2.1.0", 2538 - "license": "MIT" 2539 - }, 2540 - "node_modules/@types/nlcst": { 2541 - "version": "2.0.3", 2542 - "license": "MIT", 2543 - "dependencies": { 2544 - "@types/unist": "*" 2545 - } 2546 - }, 2547 - "node_modules/@types/node": { 2548 - "version": "25.2.3", 2549 - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", 2550 - "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", 2551 - "dev": true, 2552 - "license": "MIT", 2553 - "dependencies": { 2554 - "undici-types": "~7.16.0" 2555 - } 2556 - }, 2557 - "node_modules/@types/react": { 2558 - "version": "19.2.11", 2559 - "dev": true, 2560 - "license": "MIT", 2561 - "dependencies": { 2562 - "csstype": "^3.2.2" 2563 - } 2564 - }, 2565 - "node_modules/@types/react-dom": { 2566 - "version": "19.2.3", 2567 - "dev": true, 2568 - "license": "MIT", 2569 - "peerDependencies": { 2570 - "@types/react": "^19.2.0" 2571 - } 2572 - }, 2573 - "node_modules/@types/unist": { 2574 - "version": "3.0.3", 2575 - "license": "MIT" 2576 - }, 2577 - "node_modules/@typescript-eslint/eslint-plugin": { 2578 - "version": "8.54.0", 2579 - "dev": true, 2580 - "license": "MIT", 2581 - "dependencies": { 2582 - "@eslint-community/regexpp": "^4.12.2", 2583 - "@typescript-eslint/scope-manager": "8.54.0", 2584 - "@typescript-eslint/type-utils": "8.54.0", 2585 - "@typescript-eslint/utils": "8.54.0", 2586 - "@typescript-eslint/visitor-keys": "8.54.0", 2587 - "ignore": "^7.0.5", 2588 - "natural-compare": "^1.4.0", 2589 - "ts-api-utils": "^2.4.0" 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 - "@typescript-eslint/parser": "^8.54.0", 2600 - "eslint": "^8.57.0 || ^9.0.0", 2601 - "typescript": ">=4.8.4 <6.0.0" 2602 - } 2603 - }, 2604 - "node_modules/@typescript-eslint/parser": { 2605 - "version": "8.54.0", 2606 - "dev": true, 2607 - "license": "MIT", 2608 - "dependencies": { 2609 - "@typescript-eslint/scope-manager": "8.54.0", 2610 - "@typescript-eslint/types": "8.54.0", 2611 - "@typescript-eslint/typescript-estree": "8.54.0", 2612 - "@typescript-eslint/visitor-keys": "8.54.0", 2613 - "debug": "^4.4.3" 2614 - }, 2615 - "engines": { 2616 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2617 - }, 2618 - "funding": { 2619 - "type": "opencollective", 2620 - "url": "https://opencollective.com/typescript-eslint" 2621 - }, 2622 - "peerDependencies": { 2623 - "eslint": "^8.57.0 || ^9.0.0", 2624 - "typescript": ">=4.8.4 <6.0.0" 2625 - } 2626 - }, 2627 - "node_modules/@typescript-eslint/project-service": { 2628 - "version": "8.54.0", 2629 - "dev": true, 2630 - "license": "MIT", 2631 - "dependencies": { 2632 - "@typescript-eslint/tsconfig-utils": "^8.54.0", 2633 - "@typescript-eslint/types": "^8.54.0", 2634 - "debug": "^4.4.3" 2635 - }, 2636 - "engines": { 2637 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2638 - }, 2639 - "funding": { 2640 - "type": "opencollective", 2641 - "url": "https://opencollective.com/typescript-eslint" 2642 - }, 2643 - "peerDependencies": { 2644 - "typescript": ">=4.8.4 <6.0.0" 2645 - } 2646 - }, 2647 - "node_modules/@typescript-eslint/scope-manager": { 2648 - "version": "8.54.0", 2649 - "dev": true, 2650 - "license": "MIT", 2651 - "dependencies": { 2652 - "@typescript-eslint/types": "8.54.0", 2653 - "@typescript-eslint/visitor-keys": "8.54.0" 2654 - }, 2655 - "engines": { 2656 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2657 - }, 2658 - "funding": { 2659 - "type": "opencollective", 2660 - "url": "https://opencollective.com/typescript-eslint" 2661 - } 2662 - }, 2663 - "node_modules/@typescript-eslint/tsconfig-utils": { 2664 - "version": "8.54.0", 2665 - "dev": true, 2666 - "license": "MIT", 2667 - "engines": { 2668 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2669 - }, 2670 - "funding": { 2671 - "type": "opencollective", 2672 - "url": "https://opencollective.com/typescript-eslint" 2673 - }, 2674 - "peerDependencies": { 2675 - "typescript": ">=4.8.4 <6.0.0" 2676 - } 2677 - }, 2678 - "node_modules/@typescript-eslint/type-utils": { 2679 - "version": "8.54.0", 2680 - "dev": true, 2681 - "license": "MIT", 2682 - "dependencies": { 2683 - "@typescript-eslint/types": "8.54.0", 2684 - "@typescript-eslint/typescript-estree": "8.54.0", 2685 - "@typescript-eslint/utils": "8.54.0", 2686 - "debug": "^4.4.3", 2687 - "ts-api-utils": "^2.4.0" 2688 - }, 2689 - "engines": { 2690 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2691 - }, 2692 - "funding": { 2693 - "type": "opencollective", 2694 - "url": "https://opencollective.com/typescript-eslint" 2695 - }, 2696 - "peerDependencies": { 2697 - "eslint": "^8.57.0 || ^9.0.0", 2698 - "typescript": ">=4.8.4 <6.0.0" 2699 - } 2700 - }, 2701 - "node_modules/@typescript-eslint/types": { 2702 - "version": "8.54.0", 2703 - "dev": true, 2704 - "license": "MIT", 2705 - "engines": { 2706 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2707 - }, 2708 - "funding": { 2709 - "type": "opencollective", 2710 - "url": "https://opencollective.com/typescript-eslint" 2711 - } 2712 - }, 2713 - "node_modules/@typescript-eslint/typescript-estree": { 2714 - "version": "8.54.0", 2715 - "dev": true, 2716 - "license": "MIT", 2717 - "dependencies": { 2718 - "@typescript-eslint/project-service": "8.54.0", 2719 - "@typescript-eslint/tsconfig-utils": "8.54.0", 2720 - "@typescript-eslint/types": "8.54.0", 2721 - "@typescript-eslint/visitor-keys": "8.54.0", 2722 - "debug": "^4.4.3", 2723 - "minimatch": "^9.0.5", 2724 - "semver": "^7.7.3", 2725 - "tinyglobby": "^0.2.15", 2726 - "ts-api-utils": "^2.4.0" 2727 - }, 2728 - "engines": { 2729 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2730 - }, 2731 - "funding": { 2732 - "type": "opencollective", 2733 - "url": "https://opencollective.com/typescript-eslint" 2734 - }, 2735 - "peerDependencies": { 2736 - "typescript": ">=4.8.4 <6.0.0" 2737 - } 2738 - }, 2739 - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 2740 - "version": "9.0.5", 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/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { 2754 - "version": "2.0.2", 2755 - "dev": true, 2756 - "license": "MIT", 2757 - "dependencies": { 2758 - "balanced-match": "^1.0.0" 2759 - } 2760 - }, 2761 - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { 2762 - "version": "7.7.3", 2763 - "dev": true, 2764 - "license": "ISC", 2765 - "bin": { 2766 - "semver": "bin/semver.js" 2767 - }, 2768 - "engines": { 2769 - "node": ">=10" 2770 - } 2771 - }, 2772 - "node_modules/@typescript-eslint/utils": { 2773 - "version": "8.54.0", 2774 - "dev": true, 2775 - "license": "MIT", 2776 - "dependencies": { 2777 - "@eslint-community/eslint-utils": "^4.9.1", 2778 - "@typescript-eslint/scope-manager": "8.54.0", 2779 - "@typescript-eslint/types": "8.54.0", 2780 - "@typescript-eslint/typescript-estree": "8.54.0" 2781 - }, 2782 - "engines": { 2783 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2784 - }, 2785 - "funding": { 2786 - "type": "opencollective", 2787 - "url": "https://opencollective.com/typescript-eslint" 2788 - }, 2789 - "peerDependencies": { 2790 - "eslint": "^8.57.0 || ^9.0.0", 2791 - "typescript": ">=4.8.4 <6.0.0" 2792 - } 2793 - }, 2794 - "node_modules/@typescript-eslint/visitor-keys": { 2795 - "version": "8.54.0", 2796 - "dev": true, 2797 - "license": "MIT", 2798 - "dependencies": { 2799 - "@typescript-eslint/types": "8.54.0", 2800 - "eslint-visitor-keys": "^4.2.1" 2801 - }, 2802 - "engines": { 2803 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2804 - }, 2805 - "funding": { 2806 - "type": "opencollective", 2807 - "url": "https://opencollective.com/typescript-eslint" 2808 - } 2809 - }, 2810 - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { 2811 - "version": "4.2.1", 2812 - "dev": true, 2813 - "license": "Apache-2.0", 2814 - "engines": { 2815 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2816 - }, 2817 - "funding": { 2818 - "url": "https://opencollective.com/eslint" 2819 - } 2820 - }, 2821 - "node_modules/@ungap/structured-clone": { 2822 - "version": "1.3.0", 2823 - "license": "ISC" 2824 - }, 2825 - "node_modules/@vitejs/plugin-react": { 2826 - "version": "4.7.0", 2827 - "license": "MIT", 2828 - "dependencies": { 2829 - "@babel/core": "^7.28.0", 2830 - "@babel/plugin-transform-react-jsx-self": "^7.27.1", 2831 - "@babel/plugin-transform-react-jsx-source": "^7.27.1", 2832 - "@rolldown/pluginutils": "1.0.0-beta.27", 2833 - "@types/babel__core": "^7.20.5", 2834 - "react-refresh": "^0.17.0" 2835 - }, 2836 - "engines": { 2837 - "node": "^14.18.0 || >=16.0.0" 2838 - }, 2839 - "peerDependencies": { 2840 - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" 2841 - } 2842 - }, 2843 - "node_modules/acorn": { 2844 - "version": "8.15.0", 2845 - "license": "MIT", 2846 - "bin": { 2847 - "acorn": "bin/acorn" 2848 - }, 2849 - "engines": { 2850 - "node": ">=0.4.0" 2851 - } 2852 - }, 2853 - "node_modules/acorn-jsx": { 2854 - "version": "5.3.2", 2855 - "dev": true, 2856 - "license": "MIT", 2857 - "peerDependencies": { 2858 - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 2859 - } 2860 - }, 2861 - "node_modules/ajv": { 2862 - "version": "6.12.6", 2863 - "dev": true, 2864 - "license": "MIT", 2865 - "dependencies": { 2866 - "fast-deep-equal": "^3.1.1", 2867 - "fast-json-stable-stringify": "^2.0.0", 2868 - "json-schema-traverse": "^0.4.1", 2869 - "uri-js": "^4.2.2" 2870 - }, 2871 - "funding": { 2872 - "type": "github", 2873 - "url": "https://github.com/sponsors/epoberezkin" 2874 - } 2875 - }, 2876 - "node_modules/ansi-align": { 2877 - "version": "3.0.1", 2878 - "license": "ISC", 2879 - "dependencies": { 2880 - "string-width": "^4.1.0" 2881 - } 2882 - }, 2883 - "node_modules/ansi-align/node_modules/string-width": { 2884 - "version": "4.2.3", 2885 - "license": "MIT", 2886 - "dependencies": { 2887 - "emoji-regex": "^8.0.0", 2888 - "is-fullwidth-code-point": "^3.0.0", 2889 - "strip-ansi": "^6.0.1" 2890 - }, 2891 - "engines": { 2892 - "node": ">=8" 2893 - } 2894 - }, 2895 - "node_modules/ansi-align/node_modules/string-width/node_modules/emoji-regex": { 2896 - "version": "8.0.0", 2897 - "license": "MIT" 2898 - }, 2899 - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi": { 2900 - "version": "6.0.1", 2901 - "license": "MIT", 2902 - "dependencies": { 2903 - "ansi-regex": "^5.0.1" 2904 - }, 2905 - "engines": { 2906 - "node": ">=8" 2907 - } 2908 - }, 2909 - "node_modules/ansi-align/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { 2910 - "version": "5.0.1", 2911 - "license": "MIT", 2912 - "engines": { 2913 - "node": ">=8" 2914 - } 2915 - }, 2916 - "node_modules/ansi-regex": { 2917 - "version": "6.2.2", 2918 - "license": "MIT", 2919 - "engines": { 2920 - "node": ">=12" 2921 - }, 2922 - "funding": { 2923 - "url": "https://github.com/chalk/ansi-regex?sponsor=1" 2924 - } 2925 - }, 2926 - "node_modules/ansi-styles": { 2927 - "version": "6.2.3", 2928 - "license": "MIT", 2929 - "engines": { 2930 - "node": ">=12" 2931 - }, 2932 - "funding": { 2933 - "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2934 - } 2935 - }, 2936 - "node_modules/any-promise": { 2937 - "version": "1.3.0", 2938 - "license": "MIT" 2939 - }, 2940 - "node_modules/anymatch": { 2941 - "version": "3.1.3", 2942 - "license": "ISC", 2943 - "dependencies": { 2944 - "normalize-path": "^3.0.0", 2945 - "picomatch": "^2.0.4" 2946 - }, 2947 - "engines": { 2948 - "node": ">= 8" 2949 - } 2950 - }, 2951 - "node_modules/anymatch/node_modules/picomatch": { 2952 - "version": "2.3.1", 2953 - "license": "MIT", 2954 - "engines": { 2955 - "node": ">=8.6" 2956 - }, 2957 - "funding": { 2958 - "url": "https://github.com/sponsors/jonschlinkert" 2959 - } 2960 - }, 2961 - "node_modules/arg": { 2962 - "version": "5.0.2", 2963 - "license": "MIT" 2964 - }, 2965 - "node_modules/argparse": { 2966 - "version": "2.0.1", 2967 - "license": "Python-2.0" 2968 - }, 2969 - "node_modules/aria-query": { 2970 - "version": "5.3.2", 2971 - "license": "Apache-2.0", 2972 - "engines": { 2973 - "node": ">= 0.4" 2974 - } 2975 - }, 2976 - "node_modules/array-buffer-byte-length": { 2977 - "version": "1.0.2", 2978 - "dev": true, 2979 - "license": "MIT", 2980 - "dependencies": { 2981 - "call-bound": "^1.0.3", 2982 - "is-array-buffer": "^3.0.5" 2983 - }, 2984 - "engines": { 2985 - "node": ">= 0.4" 2986 - }, 2987 - "funding": { 2988 - "url": "https://github.com/sponsors/ljharb" 2989 - } 2990 - }, 2991 - "node_modules/array-includes": { 2992 - "version": "3.1.9", 2993 - "dev": true, 2994 - "license": "MIT", 2995 - "dependencies": { 2996 - "call-bind": "^1.0.8", 2997 - "call-bound": "^1.0.4", 2998 - "define-properties": "^1.2.1", 2999 - "es-abstract": "^1.24.0", 3000 - "es-object-atoms": "^1.1.1", 3001 - "get-intrinsic": "^1.3.0", 3002 - "is-string": "^1.1.1", 3003 - "math-intrinsics": "^1.1.0" 3004 - }, 3005 - "engines": { 3006 - "node": ">= 0.4" 3007 - }, 3008 - "funding": { 3009 - "url": "https://github.com/sponsors/ljharb" 3010 - } 3011 - }, 3012 - "node_modules/array-iterate": { 3013 - "version": "2.0.1", 3014 - "license": "MIT", 3015 - "funding": { 3016 - "type": "github", 3017 - "url": "https://github.com/sponsors/wooorm" 3018 - } 3019 - }, 3020 - "node_modules/array.prototype.findlast": { 3021 - "version": "1.2.5", 3022 - "dev": true, 3023 - "license": "MIT", 3024 - "dependencies": { 3025 - "call-bind": "^1.0.7", 3026 - "define-properties": "^1.2.1", 3027 - "es-abstract": "^1.23.2", 3028 - "es-errors": "^1.3.0", 3029 - "es-object-atoms": "^1.0.0", 3030 - "es-shim-unscopables": "^1.0.2" 3031 - }, 3032 - "engines": { 3033 - "node": ">= 0.4" 3034 - }, 3035 - "funding": { 3036 - "url": "https://github.com/sponsors/ljharb" 3037 - } 3038 - }, 3039 - "node_modules/array.prototype.flat": { 3040 - "version": "1.3.3", 3041 - "dev": true, 3042 - "license": "MIT", 3043 - "dependencies": { 3044 - "call-bind": "^1.0.8", 3045 - "define-properties": "^1.2.1", 3046 - "es-abstract": "^1.23.5", 3047 - "es-shim-unscopables": "^1.0.2" 3048 - }, 3049 - "engines": { 3050 - "node": ">= 0.4" 3051 - }, 3052 - "funding": { 3053 - "url": "https://github.com/sponsors/ljharb" 3054 - } 3055 - }, 3056 - "node_modules/array.prototype.flatmap": { 3057 - "version": "1.3.3", 3058 - "dev": true, 3059 - "license": "MIT", 3060 - "dependencies": { 3061 - "call-bind": "^1.0.8", 3062 - "define-properties": "^1.2.1", 3063 - "es-abstract": "^1.23.5", 3064 - "es-shim-unscopables": "^1.0.2" 3065 - }, 3066 - "engines": { 3067 - "node": ">= 0.4" 3068 - }, 3069 - "funding": { 3070 - "url": "https://github.com/sponsors/ljharb" 3071 - } 3072 - }, 3073 - "node_modules/array.prototype.tosorted": { 3074 - "version": "1.1.4", 3075 - "dev": true, 3076 - "license": "MIT", 3077 - "dependencies": { 3078 - "call-bind": "^1.0.7", 3079 - "define-properties": "^1.2.1", 3080 - "es-abstract": "^1.23.3", 3081 - "es-errors": "^1.3.0", 3082 - "es-shim-unscopables": "^1.0.2" 3083 - }, 3084 - "engines": { 3085 - "node": ">= 0.4" 3086 - } 3087 - }, 3088 - "node_modules/arraybuffer.prototype.slice": { 3089 - "version": "1.0.4", 3090 - "dev": true, 3091 - "license": "MIT", 3092 - "dependencies": { 3093 - "array-buffer-byte-length": "^1.0.1", 3094 - "call-bind": "^1.0.8", 3095 - "define-properties": "^1.2.1", 3096 - "es-abstract": "^1.23.5", 3097 - "es-errors": "^1.3.0", 3098 - "get-intrinsic": "^1.2.6", 3099 - "is-array-buffer": "^3.0.4" 3100 - }, 3101 - "engines": { 3102 - "node": ">= 0.4" 3103 - }, 3104 - "funding": { 3105 - "url": "https://github.com/sponsors/ljharb" 3106 - } 3107 - }, 3108 - "node_modules/astro": { 3109 - "version": "5.17.1", 3110 - "license": "MIT", 3111 - "dependencies": { 3112 - "@astrojs/compiler": "^2.13.0", 3113 - "@astrojs/internal-helpers": "0.7.5", 3114 - "@astrojs/markdown-remark": "6.3.10", 3115 - "@astrojs/telemetry": "3.3.0", 3116 - "@capsizecss/unpack": "^4.0.0", 3117 - "@oslojs/encoding": "^1.1.0", 3118 - "@rollup/pluginutils": "^5.3.0", 3119 - "acorn": "^8.15.0", 3120 - "aria-query": "^5.3.2", 3121 - "axobject-query": "^4.1.0", 3122 - "boxen": "8.0.1", 3123 - "ci-info": "^4.3.1", 3124 - "clsx": "^2.1.1", 3125 - "common-ancestor-path": "^1.0.1", 3126 - "cookie": "^1.1.1", 3127 - "cssesc": "^3.0.0", 3128 - "debug": "^4.4.3", 3129 - "deterministic-object-hash": "^2.0.2", 3130 - "devalue": "^5.6.2", 3131 - "diff": "^8.0.3", 3132 - "dlv": "^1.1.3", 3133 - "dset": "^3.1.4", 3134 - "es-module-lexer": "^1.7.0", 3135 - "esbuild": "^0.25.0", 3136 - "estree-walker": "^3.0.3", 3137 - "flattie": "^1.1.1", 3138 - "fontace": "~0.4.0", 3139 - "github-slugger": "^2.0.0", 3140 - "html-escaper": "3.0.3", 3141 - "http-cache-semantics": "^4.2.0", 3142 - "import-meta-resolve": "^4.2.0", 3143 - "js-yaml": "^4.1.1", 3144 - "magic-string": "^0.30.21", 3145 - "magicast": "^0.5.1", 3146 - "mrmime": "^2.0.1", 3147 - "neotraverse": "^0.6.18", 3148 - "p-limit": "^6.2.0", 3149 - "p-queue": "^8.1.1", 3150 - "package-manager-detector": "^1.6.0", 3151 - "piccolore": "^0.1.3", 3152 - "picomatch": "^4.0.3", 3153 - "prompts": "^2.4.2", 3154 - "rehype": "^13.0.2", 3155 - "semver": "^7.7.3", 3156 - "shiki": "^3.21.0", 3157 - "smol-toml": "^1.6.0", 3158 - "svgo": "^4.0.0", 3159 - "tinyexec": "^1.0.2", 3160 - "tinyglobby": "^0.2.15", 3161 - "tsconfck": "^3.1.6", 3162 - "ultrahtml": "^1.6.0", 3163 - "unifont": "~0.7.3", 3164 - "unist-util-visit": "^5.0.0", 3165 - "unstorage": "^1.17.4", 3166 - "vfile": "^6.0.3", 3167 - "vite": "^6.4.1", 3168 - "vitefu": "^1.1.1", 3169 - "xxhash-wasm": "^1.1.0", 3170 - "yargs-parser": "^21.1.1", 3171 - "yocto-spinner": "^0.2.3", 3172 - "zod": "^3.25.76", 3173 - "zod-to-json-schema": "^3.25.1", 3174 - "zod-to-ts": "^1.2.0" 3175 - }, 3176 - "bin": { 3177 - "astro": "astro.js" 3178 - }, 3179 - "engines": { 3180 - "node": "18.20.8 || ^20.3.0 || >=22.0.0", 3181 - "npm": ">=9.6.5", 3182 - "pnpm": ">=7.1.0" 3183 - }, 3184 - "funding": { 3185 - "type": "opencollective", 3186 - "url": "https://opencollective.com/astrodotbuild" 3187 - }, 3188 - "optionalDependencies": { 3189 - "sharp": "^0.34.0" 3190 - } 3191 - }, 3192 - "node_modules/astro/node_modules/semver": { 3193 - "version": "7.7.3", 3194 - "license": "ISC", 3195 - "bin": { 3196 - "semver": "bin/semver.js" 3197 - }, 3198 - "engines": { 3199 - "node": ">=10" 3200 - } 3201 - }, 3202 - "node_modules/async-function": { 3203 - "version": "1.0.0", 3204 - "dev": true, 3205 - "license": "MIT", 3206 - "engines": { 3207 - "node": ">= 0.4" 3208 - } 3209 - }, 3210 - "node_modules/autoprefixer": { 3211 - "version": "10.4.24", 3212 - "funding": [ 3213 - { 3214 - "type": "opencollective", 3215 - "url": "https://opencollective.com/postcss/" 3216 - }, 3217 - { 3218 - "type": "tidelift", 3219 - "url": "https://tidelift.com/funding/github/npm/autoprefixer" 3220 - }, 3221 - { 3222 - "type": "github", 3223 - "url": "https://github.com/sponsors/ai" 3224 - } 3225 - ], 3226 - "license": "MIT", 3227 - "dependencies": { 3228 - "browserslist": "^4.28.1", 3229 - "caniuse-lite": "^1.0.30001766", 3230 - "fraction.js": "^5.3.4", 3231 - "picocolors": "^1.1.1", 3232 - "postcss-value-parser": "^4.2.0" 3233 - }, 3234 - "bin": { 3235 - "autoprefixer": "bin/autoprefixer" 3236 - }, 3237 - "engines": { 3238 - "node": "^10 || ^12 || >=14" 3239 - }, 3240 - "peerDependencies": { 3241 - "postcss": "^8.1.0" 3242 - } 3243 - }, 3244 - "node_modules/available-typed-arrays": { 3245 - "version": "1.0.7", 3246 - "dev": true, 3247 - "license": "MIT", 3248 - "dependencies": { 3249 - "possible-typed-array-names": "^1.0.0" 3250 - }, 3251 - "engines": { 3252 - "node": ">= 0.4" 3253 - }, 3254 - "funding": { 3255 - "url": "https://github.com/sponsors/ljharb" 3256 - } 3257 - }, 3258 - "node_modules/axobject-query": { 3259 - "version": "4.1.0", 3260 - "license": "Apache-2.0", 3261 - "engines": { 3262 - "node": ">= 0.4" 3263 - } 3264 - }, 3265 - "node_modules/bail": { 3266 - "version": "2.0.2", 3267 - "license": "MIT", 3268 - "funding": { 3269 - "type": "github", 3270 - "url": "https://github.com/sponsors/wooorm" 3271 - } 3272 - }, 3273 - "node_modules/balanced-match": { 3274 - "version": "1.0.2", 3275 - "dev": true, 3276 - "license": "MIT" 3277 - }, 3278 - "node_modules/base-64": { 3279 - "version": "1.0.0", 3280 - "license": "MIT" 3281 - }, 3282 - "node_modules/base64-js": { 3283 - "version": "0.0.8", 3284 - "license": "MIT", 3285 - "engines": { 3286 - "node": ">= 0.4" 3287 - } 3288 - }, 3289 - "node_modules/baseline-browser-mapping": { 3290 - "version": "2.9.19", 3291 - "license": "Apache-2.0", 3292 - "bin": { 3293 - "baseline-browser-mapping": "dist/cli.js" 3294 - } 3295 - }, 3296 - "node_modules/binary-extensions": { 3297 - "version": "2.3.0", 3298 - "license": "MIT", 3299 - "engines": { 3300 - "node": ">=8" 3301 - }, 3302 - "funding": { 3303 - "url": "https://github.com/sponsors/sindresorhus" 3304 - } 3305 - }, 3306 - "node_modules/boolbase": { 3307 - "version": "1.0.0", 3308 - "license": "ISC" 3309 - }, 3310 - "node_modules/boxen": { 3311 - "version": "8.0.1", 3312 - "license": "MIT", 3313 - "dependencies": { 3314 - "ansi-align": "^3.0.1", 3315 - "camelcase": "^8.0.0", 3316 - "chalk": "^5.3.0", 3317 - "cli-boxes": "^3.0.0", 3318 - "string-width": "^7.2.0", 3319 - "type-fest": "^4.21.0", 3320 - "widest-line": "^5.0.0", 3321 - "wrap-ansi": "^9.0.0" 3322 - }, 3323 - "engines": { 3324 - "node": ">=18" 3325 - }, 3326 - "funding": { 3327 - "url": "https://github.com/sponsors/sindresorhus" 3328 - } 3329 - }, 3330 - "node_modules/brace-expansion": { 3331 - "version": "1.1.12", 3332 - "dev": true, 3333 - "license": "MIT", 3334 - "dependencies": { 3335 - "balanced-match": "^1.0.0", 3336 - "concat-map": "0.0.1" 3337 - } 3338 - }, 3339 - "node_modules/braces": { 3340 - "version": "3.0.3", 3341 - "license": "MIT", 3342 - "dependencies": { 3343 - "fill-range": "^7.1.1" 3344 - }, 3345 - "engines": { 3346 - "node": ">=8" 3347 - } 3348 - }, 3349 - "node_modules/browserslist": { 3350 - "version": "4.28.1", 3351 - "funding": [ 3352 - { 3353 - "type": "opencollective", 3354 - "url": "https://opencollective.com/browserslist" 3355 - }, 3356 - { 3357 - "type": "tidelift", 3358 - "url": "https://tidelift.com/funding/github/npm/browserslist" 3359 - }, 3360 - { 3361 - "type": "github", 3362 - "url": "https://github.com/sponsors/ai" 3363 - } 3364 - ], 3365 - "license": "MIT", 3366 - "dependencies": { 3367 - "baseline-browser-mapping": "^2.9.0", 3368 - "caniuse-lite": "^1.0.30001759", 3369 - "electron-to-chromium": "^1.5.263", 3370 - "node-releases": "^2.0.27", 3371 - "update-browserslist-db": "^1.2.0" 3372 - }, 3373 - "bin": { 3374 - "browserslist": "cli.js" 3375 - }, 3376 - "engines": { 3377 - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 3378 - } 3379 - }, 3380 - "node_modules/call-bind": { 3381 - "version": "1.0.8", 3382 - "dev": true, 3383 - "license": "MIT", 3384 - "dependencies": { 3385 - "call-bind-apply-helpers": "^1.0.0", 3386 - "es-define-property": "^1.0.0", 3387 - "get-intrinsic": "^1.2.4", 3388 - "set-function-length": "^1.2.2" 3389 - }, 3390 - "engines": { 3391 - "node": ">= 0.4" 3392 - }, 3393 - "funding": { 3394 - "url": "https://github.com/sponsors/ljharb" 3395 - } 3396 - }, 3397 - "node_modules/call-bind-apply-helpers": { 3398 - "version": "1.0.2", 3399 - "dev": true, 3400 - "license": "MIT", 3401 - "dependencies": { 3402 - "es-errors": "^1.3.0", 3403 - "function-bind": "^1.1.2" 3404 - }, 3405 - "engines": { 3406 - "node": ">= 0.4" 3407 - } 3408 - }, 3409 - "node_modules/call-bound": { 3410 - "version": "1.0.4", 3411 - "dev": true, 3412 - "license": "MIT", 3413 - "dependencies": { 3414 - "call-bind-apply-helpers": "^1.0.2", 3415 - "get-intrinsic": "^1.3.0" 3416 - }, 3417 - "engines": { 3418 - "node": ">= 0.4" 3419 - }, 3420 - "funding": { 3421 - "url": "https://github.com/sponsors/ljharb" 3422 - } 3423 - }, 3424 - "node_modules/camelcase": { 3425 - "version": "8.0.0", 3426 - "license": "MIT", 3427 - "engines": { 3428 - "node": ">=16" 3429 - }, 3430 - "funding": { 3431 - "url": "https://github.com/sponsors/sindresorhus" 3432 - } 3433 - }, 3434 - "node_modules/camelcase-css": { 3435 - "version": "2.0.1", 3436 - "license": "MIT", 3437 - "engines": { 3438 - "node": ">= 6" 3439 - } 3440 - }, 3441 - "node_modules/camelize": { 3442 - "version": "1.0.1", 3443 - "license": "MIT", 3444 - "funding": { 3445 - "url": "https://github.com/sponsors/ljharb" 3446 - } 3447 - }, 3448 - "node_modules/caniuse-lite": { 3449 - "version": "1.0.30001768", 3450 - "funding": [ 3451 - { 3452 - "type": "opencollective", 3453 - "url": "https://opencollective.com/browserslist" 3454 - }, 3455 - { 3456 - "type": "tidelift", 3457 - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 3458 - }, 3459 - { 3460 - "type": "github", 3461 - "url": "https://github.com/sponsors/ai" 3462 - } 3463 - ], 3464 - "license": "CC-BY-4.0" 3465 - }, 3466 - "node_modules/ccount": { 3467 - "version": "2.0.1", 3468 - "license": "MIT", 3469 - "funding": { 3470 - "type": "github", 3471 - "url": "https://github.com/sponsors/wooorm" 3472 - } 3473 - }, 3474 - "node_modules/chalk": { 3475 - "version": "5.6.2", 3476 - "license": "MIT", 3477 - "engines": { 3478 - "node": "^12.17.0 || ^14.13 || >=16.0.0" 3479 - }, 3480 - "funding": { 3481 - "url": "https://github.com/chalk/chalk?sponsor=1" 3482 - } 3483 - }, 3484 - "node_modules/character-entities": { 3485 - "version": "2.0.2", 3486 - "license": "MIT", 3487 - "funding": { 3488 - "type": "github", 3489 - "url": "https://github.com/sponsors/wooorm" 3490 - } 3491 - }, 3492 - "node_modules/character-entities-html4": { 3493 - "version": "2.1.0", 3494 - "license": "MIT", 3495 - "funding": { 3496 - "type": "github", 3497 - "url": "https://github.com/sponsors/wooorm" 3498 - } 3499 - }, 3500 - "node_modules/character-entities-legacy": { 3501 - "version": "3.0.0", 3502 - "license": "MIT", 3503 - "funding": { 3504 - "type": "github", 3505 - "url": "https://github.com/sponsors/wooorm" 3506 - } 3507 - }, 3508 - "node_modules/ci-info": { 3509 - "version": "4.4.0", 3510 - "funding": [ 3511 - { 3512 - "type": "github", 3513 - "url": "https://github.com/sponsors/sibiraj-s" 3514 - } 3515 - ], 3516 - "license": "MIT", 3517 - "engines": { 3518 - "node": ">=8" 3519 - } 3520 - }, 3521 - "node_modules/cli-boxes": { 3522 - "version": "3.0.0", 3523 - "license": "MIT", 3524 - "engines": { 3525 - "node": ">=10" 3526 - }, 3527 - "funding": { 3528 - "url": "https://github.com/sponsors/sindresorhus" 3529 - } 3530 - }, 3531 - "node_modules/clsx": { 3532 - "version": "2.1.1", 3533 - "license": "MIT", 3534 - "engines": { 3535 - "node": ">=6" 3536 - } 3537 - }, 3538 - "node_modules/color-name": { 3539 - "version": "1.1.4", 3540 - "license": "MIT" 3541 - }, 3542 - "node_modules/comma-separated-tokens": { 3543 - "version": "2.0.3", 3544 - "license": "MIT", 3545 - "funding": { 3546 - "type": "github", 3547 - "url": "https://github.com/sponsors/wooorm" 3548 - } 3549 - }, 3550 - "node_modules/commander": { 3551 - "version": "11.1.0", 3552 - "license": "MIT", 3553 - "engines": { 3554 - "node": ">=16" 3555 - } 3556 - }, 3557 - "node_modules/common-ancestor-path": { 3558 - "version": "1.0.1", 3559 - "license": "ISC" 3560 - }, 3561 - "node_modules/concat-map": { 3562 - "version": "0.0.1", 3563 - "dev": true, 3564 - "license": "MIT" 3565 - }, 3566 - "node_modules/convert-source-map": { 3567 - "version": "2.0.0", 3568 - "license": "MIT" 3569 - }, 3570 - "node_modules/cookie": { 3571 - "version": "1.1.1", 3572 - "license": "MIT", 3573 - "engines": { 3574 - "node": ">=18" 3575 - }, 3576 - "funding": { 3577 - "type": "opencollective", 3578 - "url": "https://opencollective.com/express" 3579 - } 3580 - }, 3581 - "node_modules/cookie-es": { 3582 - "version": "1.2.2", 3583 - "license": "MIT" 3584 - }, 3585 - "node_modules/cross-spawn": { 3586 - "version": "7.0.6", 3587 - "dev": true, 3588 - "license": "MIT", 3589 - "dependencies": { 3590 - "path-key": "^3.1.0", 3591 - "shebang-command": "^2.0.0", 3592 - "which": "^2.0.1" 3593 - }, 3594 - "engines": { 3595 - "node": ">= 8" 3596 - } 3597 - }, 3598 - "node_modules/crossws": { 3599 - "version": "0.3.5", 3600 - "license": "MIT", 3601 - "dependencies": { 3602 - "uncrypto": "^0.1.3" 3603 - } 3604 - }, 3605 - "node_modules/css-background-parser": { 3606 - "version": "0.1.0", 3607 - "license": "MIT" 3608 - }, 3609 - "node_modules/css-box-shadow": { 3610 - "version": "1.0.0-3", 3611 - "license": "MIT" 3612 - }, 3613 - "node_modules/css-color-keywords": { 3614 - "version": "1.0.0", 3615 - "license": "ISC", 3616 - "engines": { 3617 - "node": ">=4" 3618 - } 3619 - }, 3620 - "node_modules/css-gradient-parser": { 3621 - "version": "0.0.17", 3622 - "license": "MIT", 3623 - "engines": { 3624 - "node": ">=16" 3625 - } 3626 - }, 3627 - "node_modules/css-select": { 3628 - "version": "5.2.2", 3629 - "license": "BSD-2-Clause", 3630 - "dependencies": { 3631 - "boolbase": "^1.0.0", 3632 - "css-what": "^6.1.0", 3633 - "domhandler": "^5.0.2", 3634 - "domutils": "^3.0.1", 3635 - "nth-check": "^2.0.1" 3636 - }, 3637 - "funding": { 3638 - "url": "https://github.com/sponsors/fb55" 3639 - } 3640 - }, 3641 - "node_modules/css-to-react-native": { 3642 - "version": "3.2.0", 3643 - "license": "MIT", 3644 - "dependencies": { 3645 - "camelize": "^1.0.0", 3646 - "css-color-keywords": "^1.0.0", 3647 - "postcss-value-parser": "^4.0.2" 3648 - } 3649 - }, 3650 - "node_modules/css-tree": { 3651 - "version": "3.1.0", 3652 - "license": "MIT", 3653 - "dependencies": { 3654 - "mdn-data": "2.12.2", 3655 - "source-map-js": "^1.0.1" 3656 - }, 3657 - "engines": { 3658 - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" 3659 - } 3660 - }, 3661 - "node_modules/css-what": { 3662 - "version": "6.2.2", 3663 - "license": "BSD-2-Clause", 3664 - "engines": { 3665 - "node": ">= 6" 3666 - }, 3667 - "funding": { 3668 - "url": "https://github.com/sponsors/fb55" 3669 - } 3670 - }, 3671 - "node_modules/cssesc": { 3672 - "version": "3.0.0", 3673 - "license": "MIT", 3674 - "bin": { 3675 - "cssesc": "bin/cssesc" 3676 - }, 3677 - "engines": { 3678 - "node": ">=4" 3679 - } 3680 - }, 3681 - "node_modules/csso": { 3682 - "version": "5.0.5", 3683 - "license": "MIT", 3684 - "dependencies": { 3685 - "css-tree": "~2.2.0" 3686 - }, 3687 - "engines": { 3688 - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", 3689 - "npm": ">=7.0.0" 3690 - } 3691 - }, 3692 - "node_modules/csso/node_modules/css-tree": { 3693 - "version": "2.2.1", 3694 - "license": "MIT", 3695 - "dependencies": { 3696 - "mdn-data": "2.0.28", 3697 - "source-map-js": "^1.0.1" 3698 - }, 3699 - "engines": { 3700 - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", 3701 - "npm": ">=7.0.0" 3702 - } 3703 - }, 3704 - "node_modules/csso/node_modules/css-tree/node_modules/mdn-data": { 3705 - "version": "2.0.28", 3706 - "license": "CC0-1.0" 3707 - }, 3708 - "node_modules/csstype": { 3709 - "version": "3.2.3", 3710 - "dev": true, 3711 - "license": "MIT" 3712 - }, 3713 - "node_modules/data-view-buffer": { 3714 - "version": "1.0.2", 3715 - "dev": true, 3716 - "license": "MIT", 3717 - "dependencies": { 3718 - "call-bound": "^1.0.3", 3719 - "es-errors": "^1.3.0", 3720 - "is-data-view": "^1.0.2" 3721 - }, 3722 - "engines": { 3723 - "node": ">= 0.4" 3724 - }, 3725 - "funding": { 3726 - "url": "https://github.com/sponsors/ljharb" 3727 - } 3728 - }, 3729 - "node_modules/data-view-byte-length": { 3730 - "version": "1.0.2", 3731 - "dev": true, 3732 - "license": "MIT", 3733 - "dependencies": { 3734 - "call-bound": "^1.0.3", 3735 - "es-errors": "^1.3.0", 3736 - "is-data-view": "^1.0.2" 3737 - }, 3738 - "engines": { 3739 - "node": ">= 0.4" 3740 - }, 3741 - "funding": { 3742 - "url": "https://github.com/sponsors/inspect-js" 3743 - } 3744 - }, 3745 - "node_modules/data-view-byte-offset": { 3746 - "version": "1.0.1", 3747 - "dev": true, 3748 - "license": "MIT", 3749 - "dependencies": { 3750 - "call-bound": "^1.0.2", 3751 - "es-errors": "^1.3.0", 3752 - "is-data-view": "^1.0.1" 3753 - }, 3754 - "engines": { 3755 - "node": ">= 0.4" 3756 - }, 3757 - "funding": { 3758 - "url": "https://github.com/sponsors/ljharb" 3759 - } 3760 - }, 3761 - "node_modules/date-fns": { 3762 - "version": "4.1.0", 3763 - "license": "MIT", 3764 - "funding": { 3765 - "type": "github", 3766 - "url": "https://github.com/sponsors/kossnocorp" 3767 - } 3768 - }, 3769 - "node_modules/debug": { 3770 - "version": "4.4.3", 3771 - "license": "MIT", 3772 - "dependencies": { 3773 - "ms": "^2.1.3" 3774 - }, 3775 - "engines": { 3776 - "node": ">=6.0" 3777 - }, 3778 - "peerDependenciesMeta": { 3779 - "supports-color": { 3780 - "optional": true 3781 - } 3782 - } 3783 - }, 3784 - "node_modules/decode-named-character-reference": { 3785 - "version": "1.3.0", 3786 - "license": "MIT", 3787 - "dependencies": { 3788 - "character-entities": "^2.0.0" 3789 - }, 3790 - "funding": { 3791 - "type": "github", 3792 - "url": "https://github.com/sponsors/wooorm" 3793 - } 3794 - }, 3795 - "node_modules/deep-is": { 3796 - "version": "0.1.4", 3797 - "dev": true, 3798 - "license": "MIT" 3799 - }, 3800 - "node_modules/define-data-property": { 3801 - "version": "1.1.4", 3802 - "dev": true, 3803 - "license": "MIT", 3804 - "dependencies": { 3805 - "es-define-property": "^1.0.0", 3806 - "es-errors": "^1.3.0", 3807 - "gopd": "^1.0.1" 3808 - }, 3809 - "engines": { 3810 - "node": ">= 0.4" 3811 - }, 3812 - "funding": { 3813 - "url": "https://github.com/sponsors/ljharb" 3814 - } 3815 - }, 3816 - "node_modules/define-properties": { 3817 - "version": "1.2.1", 3818 - "dev": true, 3819 - "license": "MIT", 3820 - "dependencies": { 3821 - "define-data-property": "^1.0.1", 3822 - "has-property-descriptors": "^1.0.0", 3823 - "object-keys": "^1.1.1" 3824 - }, 3825 - "engines": { 3826 - "node": ">= 0.4" 3827 - }, 3828 - "funding": { 3829 - "url": "https://github.com/sponsors/ljharb" 3830 - } 3831 - }, 3832 - "node_modules/defu": { 3833 - "version": "6.1.4", 3834 - "license": "MIT" 3835 - }, 3836 - "node_modules/depd": { 3837 - "version": "2.0.0", 3838 - "license": "MIT", 3839 - "engines": { 3840 - "node": ">= 0.8" 3841 - } 3842 - }, 3843 - "node_modules/dequal": { 3844 - "version": "2.0.3", 3845 - "license": "MIT", 3846 - "engines": { 3847 - "node": ">=6" 3848 - } 3849 - }, 3850 - "node_modules/destr": { 3851 - "version": "2.0.5", 3852 - "license": "MIT" 3853 - }, 3854 - "node_modules/detect-libc": { 3855 - "version": "2.1.2", 3856 - "license": "Apache-2.0", 3857 - "engines": { 3858 - "node": ">=8" 3859 - } 3860 - }, 3861 - "node_modules/deterministic-object-hash": { 3862 - "version": "2.0.2", 3863 - "license": "MIT", 3864 - "dependencies": { 3865 - "base-64": "^1.0.0" 3866 - }, 3867 - "engines": { 3868 - "node": ">=18" 3869 - } 3870 - }, 3871 - "node_modules/devalue": { 3872 - "version": "5.6.2", 3873 - "license": "MIT" 3874 - }, 3875 - "node_modules/devlop": { 3876 - "version": "1.1.0", 3877 - "license": "MIT", 3878 - "dependencies": { 3879 - "dequal": "^2.0.0" 3880 - }, 3881 - "funding": { 3882 - "type": "github", 3883 - "url": "https://github.com/sponsors/wooorm" 3884 - } 3885 - }, 3886 - "node_modules/didyoumean": { 3887 - "version": "1.2.2", 3888 - "license": "Apache-2.0" 3889 - }, 3890 - "node_modules/diff": { 3891 - "version": "8.0.3", 3892 - "license": "BSD-3-Clause", 3893 - "engines": { 3894 - "node": ">=0.3.1" 3895 - } 3896 - }, 3897 - "node_modules/dlv": { 3898 - "version": "1.1.3", 3899 - "license": "MIT" 3900 - }, 3901 - "node_modules/doctrine": { 3902 - "version": "2.1.0", 3903 - "dev": true, 3904 - "license": "Apache-2.0", 3905 - "dependencies": { 3906 - "esutils": "^2.0.2" 3907 - }, 3908 - "engines": { 3909 - "node": ">=0.10.0" 3910 - } 3911 - }, 3912 - "node_modules/dom-serializer": { 3913 - "version": "2.0.0", 3914 - "license": "MIT", 3915 - "dependencies": { 3916 - "domelementtype": "^2.3.0", 3917 - "domhandler": "^5.0.2", 3918 - "entities": "^4.2.0" 3919 - }, 3920 - "funding": { 3921 - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 3922 - } 3923 - }, 3924 - "node_modules/dom-serializer/node_modules/entities": { 3925 - "version": "4.5.0", 3926 - "license": "BSD-2-Clause", 3927 - "engines": { 3928 - "node": ">=0.12" 3929 - }, 3930 - "funding": { 3931 - "url": "https://github.com/fb55/entities?sponsor=1" 3932 - } 3933 - }, 3934 - "node_modules/domelementtype": { 3935 - "version": "2.3.0", 3936 - "funding": [ 3937 - { 3938 - "type": "github", 3939 - "url": "https://github.com/sponsors/fb55" 3940 - } 3941 - ], 3942 - "license": "BSD-2-Clause" 3943 - }, 3944 - "node_modules/domhandler": { 3945 - "version": "5.0.3", 3946 - "license": "BSD-2-Clause", 3947 - "dependencies": { 3948 - "domelementtype": "^2.3.0" 3949 - }, 3950 - "engines": { 3951 - "node": ">= 4" 3952 - }, 3953 - "funding": { 3954 - "url": "https://github.com/fb55/domhandler?sponsor=1" 3955 - } 3956 - }, 3957 - "node_modules/domutils": { 3958 - "version": "3.2.2", 3959 - "license": "BSD-2-Clause", 3960 - "dependencies": { 3961 - "dom-serializer": "^2.0.0", 3962 - "domelementtype": "^2.3.0", 3963 - "domhandler": "^5.0.3" 3964 - }, 3965 - "funding": { 3966 - "url": "https://github.com/fb55/domutils?sponsor=1" 3967 - } 3968 - }, 3969 - "node_modules/dset": { 3970 - "version": "3.1.4", 3971 - "license": "MIT", 3972 - "engines": { 3973 - "node": ">=4" 3974 - } 3975 - }, 3976 - "node_modules/dunder-proto": { 3977 - "version": "1.0.1", 3978 - "dev": true, 3979 - "license": "MIT", 3980 - "dependencies": { 3981 - "call-bind-apply-helpers": "^1.0.1", 3982 - "es-errors": "^1.3.0", 3983 - "gopd": "^1.2.0" 3984 - }, 3985 - "engines": { 3986 - "node": ">= 0.4" 3987 - } 3988 - }, 3989 - "node_modules/ee-first": { 3990 - "version": "1.1.1", 3991 - "license": "MIT" 3992 - }, 3993 - "node_modules/electron-to-chromium": { 3994 - "version": "1.5.286", 3995 - "license": "ISC" 3996 - }, 3997 - "node_modules/emoji-picker-react": { 3998 - "version": "4.18.0", 3999 - "license": "MIT", 4000 - "dependencies": { 4001 - "flairup": "1.0.0" 4002 - }, 4003 - "engines": { 4004 - "node": ">=10" 4005 - }, 4006 - "peerDependencies": { 4007 - "react": ">=16" 4008 - } 4009 - }, 4010 - "node_modules/emoji-regex": { 4011 - "version": "10.6.0", 4012 - "license": "MIT" 4013 - }, 4014 - "node_modules/emoji-regex-xs": { 4015 - "version": "2.0.1", 4016 - "license": "MIT", 4017 - "engines": { 4018 - "node": ">=10.0.0" 4019 - } 4020 - }, 4021 - "node_modules/encodeurl": { 4022 - "version": "2.0.0", 4023 - "license": "MIT", 4024 - "engines": { 4025 - "node": ">= 0.8" 4026 - } 4027 - }, 4028 - "node_modules/enhanced-resolve": { 4029 - "version": "5.19.0", 4030 - "license": "MIT", 4031 - "dependencies": { 4032 - "graceful-fs": "^4.2.4", 4033 - "tapable": "^2.3.0" 4034 - }, 4035 - "engines": { 4036 - "node": ">=10.13.0" 4037 - } 4038 - }, 4039 - "node_modules/entities": { 4040 - "version": "6.0.1", 4041 - "license": "BSD-2-Clause", 4042 - "engines": { 4043 - "node": ">=0.12" 4044 - }, 4045 - "funding": { 4046 - "url": "https://github.com/fb55/entities?sponsor=1" 4047 - } 4048 - }, 4049 - "node_modules/es-abstract": { 4050 - "version": "1.24.1", 4051 - "dev": true, 4052 - "license": "MIT", 4053 - "dependencies": { 4054 - "array-buffer-byte-length": "^1.0.2", 4055 - "arraybuffer.prototype.slice": "^1.0.4", 4056 - "available-typed-arrays": "^1.0.7", 4057 - "call-bind": "^1.0.8", 4058 - "call-bound": "^1.0.4", 4059 - "data-view-buffer": "^1.0.2", 4060 - "data-view-byte-length": "^1.0.2", 4061 - "data-view-byte-offset": "^1.0.1", 4062 - "es-define-property": "^1.0.1", 4063 - "es-errors": "^1.3.0", 4064 - "es-object-atoms": "^1.1.1", 4065 - "es-set-tostringtag": "^2.1.0", 4066 - "es-to-primitive": "^1.3.0", 4067 - "function.prototype.name": "^1.1.8", 4068 - "get-intrinsic": "^1.3.0", 4069 - "get-proto": "^1.0.1", 4070 - "get-symbol-description": "^1.1.0", 4071 - "globalthis": "^1.0.4", 4072 - "gopd": "^1.2.0", 4073 - "has-property-descriptors": "^1.0.2", 4074 - "has-proto": "^1.2.0", 4075 - "has-symbols": "^1.1.0", 4076 - "hasown": "^2.0.2", 4077 - "internal-slot": "^1.1.0", 4078 - "is-array-buffer": "^3.0.5", 4079 - "is-callable": "^1.2.7", 4080 - "is-data-view": "^1.0.2", 4081 - "is-negative-zero": "^2.0.3", 4082 - "is-regex": "^1.2.1", 4083 - "is-set": "^2.0.3", 4084 - "is-shared-array-buffer": "^1.0.4", 4085 - "is-string": "^1.1.1", 4086 - "is-typed-array": "^1.1.15", 4087 - "is-weakref": "^1.1.1", 4088 - "math-intrinsics": "^1.1.0", 4089 - "object-inspect": "^1.13.4", 4090 - "object-keys": "^1.1.1", 4091 - "object.assign": "^4.1.7", 4092 - "own-keys": "^1.0.1", 4093 - "regexp.prototype.flags": "^1.5.4", 4094 - "safe-array-concat": "^1.1.3", 4095 - "safe-push-apply": "^1.0.0", 4096 - "safe-regex-test": "^1.1.0", 4097 - "set-proto": "^1.0.0", 4098 - "stop-iteration-iterator": "^1.1.0", 4099 - "string.prototype.trim": "^1.2.10", 4100 - "string.prototype.trimend": "^1.0.9", 4101 - "string.prototype.trimstart": "^1.0.8", 4102 - "typed-array-buffer": "^1.0.3", 4103 - "typed-array-byte-length": "^1.0.3", 4104 - "typed-array-byte-offset": "^1.0.4", 4105 - "typed-array-length": "^1.0.7", 4106 - "unbox-primitive": "^1.1.0", 4107 - "which-typed-array": "^1.1.19" 4108 - }, 4109 - "engines": { 4110 - "node": ">= 0.4" 4111 - }, 4112 - "funding": { 4113 - "url": "https://github.com/sponsors/ljharb" 4114 - } 4115 - }, 4116 - "node_modules/es-define-property": { 4117 - "version": "1.0.1", 4118 - "dev": true, 4119 - "license": "MIT", 4120 - "engines": { 4121 - "node": ">= 0.4" 4122 - } 4123 - }, 4124 - "node_modules/es-errors": { 4125 - "version": "1.3.0", 4126 - "dev": true, 4127 - "license": "MIT", 4128 - "engines": { 4129 - "node": ">= 0.4" 4130 - } 4131 - }, 4132 - "node_modules/es-iterator-helpers": { 4133 - "version": "1.2.2", 4134 - "dev": true, 4135 - "license": "MIT", 4136 - "dependencies": { 4137 - "call-bind": "^1.0.8", 4138 - "call-bound": "^1.0.4", 4139 - "define-properties": "^1.2.1", 4140 - "es-abstract": "^1.24.1", 4141 - "es-errors": "^1.3.0", 4142 - "es-set-tostringtag": "^2.1.0", 4143 - "function-bind": "^1.1.2", 4144 - "get-intrinsic": "^1.3.0", 4145 - "globalthis": "^1.0.4", 4146 - "gopd": "^1.2.0", 4147 - "has-property-descriptors": "^1.0.2", 4148 - "has-proto": "^1.2.0", 4149 - "has-symbols": "^1.1.0", 4150 - "internal-slot": "^1.1.0", 4151 - "iterator.prototype": "^1.1.5", 4152 - "safe-array-concat": "^1.1.3" 4153 - }, 4154 - "engines": { 4155 - "node": ">= 0.4" 4156 - } 4157 - }, 4158 - "node_modules/es-module-lexer": { 4159 - "version": "1.7.0", 4160 - "license": "MIT" 4161 - }, 4162 - "node_modules/es-object-atoms": { 4163 - "version": "1.1.1", 4164 - "dev": true, 4165 - "license": "MIT", 4166 - "dependencies": { 4167 - "es-errors": "^1.3.0" 4168 - }, 4169 - "engines": { 4170 - "node": ">= 0.4" 4171 - } 4172 - }, 4173 - "node_modules/es-set-tostringtag": { 4174 - "version": "2.1.0", 4175 - "dev": true, 4176 - "license": "MIT", 4177 - "dependencies": { 4178 - "es-errors": "^1.3.0", 4179 - "get-intrinsic": "^1.2.6", 4180 - "has-tostringtag": "^1.0.2", 4181 - "hasown": "^2.0.2" 4182 - }, 4183 - "engines": { 4184 - "node": ">= 0.4" 4185 - } 4186 - }, 4187 - "node_modules/es-shim-unscopables": { 4188 - "version": "1.1.0", 4189 - "dev": true, 4190 - "license": "MIT", 4191 - "dependencies": { 4192 - "hasown": "^2.0.2" 4193 - }, 4194 - "engines": { 4195 - "node": ">= 0.4" 4196 - } 4197 - }, 4198 - "node_modules/es-to-primitive": { 4199 - "version": "1.3.0", 4200 - "dev": true, 4201 - "license": "MIT", 4202 - "dependencies": { 4203 - "is-callable": "^1.2.7", 4204 - "is-date-object": "^1.0.5", 4205 - "is-symbol": "^1.0.4" 4206 - }, 4207 - "engines": { 4208 - "node": ">= 0.4" 4209 - }, 4210 - "funding": { 4211 - "url": "https://github.com/sponsors/ljharb" 4212 - } 4213 - }, 4214 - "node_modules/esbuild": { 4215 - "version": "0.25.12", 4216 - "hasInstallScript": true, 4217 - "license": "MIT", 4218 - "bin": { 4219 - "esbuild": "bin/esbuild" 4220 - }, 4221 - "engines": { 4222 - "node": ">=18" 4223 - }, 4224 - "optionalDependencies": { 4225 - "@esbuild/aix-ppc64": "0.25.12", 4226 - "@esbuild/android-arm": "0.25.12", 4227 - "@esbuild/android-arm64": "0.25.12", 4228 - "@esbuild/android-x64": "0.25.12", 4229 - "@esbuild/darwin-arm64": "0.25.12", 4230 - "@esbuild/darwin-x64": "0.25.12", 4231 - "@esbuild/freebsd-arm64": "0.25.12", 4232 - "@esbuild/freebsd-x64": "0.25.12", 4233 - "@esbuild/linux-arm": "0.25.12", 4234 - "@esbuild/linux-arm64": "0.25.12", 4235 - "@esbuild/linux-ia32": "0.25.12", 4236 - "@esbuild/linux-loong64": "0.25.12", 4237 - "@esbuild/linux-mips64el": "0.25.12", 4238 - "@esbuild/linux-ppc64": "0.25.12", 4239 - "@esbuild/linux-riscv64": "0.25.12", 4240 - "@esbuild/linux-s390x": "0.25.12", 4241 - "@esbuild/linux-x64": "0.25.12", 4242 - "@esbuild/netbsd-arm64": "0.25.12", 4243 - "@esbuild/netbsd-x64": "0.25.12", 4244 - "@esbuild/openbsd-arm64": "0.25.12", 4245 - "@esbuild/openbsd-x64": "0.25.12", 4246 - "@esbuild/openharmony-arm64": "0.25.12", 4247 - "@esbuild/sunos-x64": "0.25.12", 4248 - "@esbuild/win32-arm64": "0.25.12", 4249 - "@esbuild/win32-ia32": "0.25.12", 4250 - "@esbuild/win32-x64": "0.25.12" 4251 - } 4252 - }, 4253 - "node_modules/escalade": { 4254 - "version": "3.2.0", 4255 - "license": "MIT", 4256 - "engines": { 4257 - "node": ">=6" 4258 - } 4259 - }, 4260 - "node_modules/escape-html": { 4261 - "version": "1.0.3", 4262 - "license": "MIT" 4263 - }, 4264 - "node_modules/escape-string-regexp": { 4265 - "version": "4.0.0", 4266 - "dev": true, 4267 - "license": "MIT", 4268 - "engines": { 4269 - "node": ">=10" 4270 - }, 4271 - "funding": { 4272 - "url": "https://github.com/sponsors/sindresorhus" 4273 - } 4274 - }, 4275 - "node_modules/eslint": { 4276 - "version": "10.0.0", 4277 - "dev": true, 4278 - "license": "MIT", 4279 - "dependencies": { 4280 - "@eslint-community/eslint-utils": "^4.8.0", 4281 - "@eslint-community/regexpp": "^4.12.2", 4282 - "@eslint/config-array": "^0.23.0", 4283 - "@eslint/config-helpers": "^0.5.2", 4284 - "@eslint/core": "^1.1.0", 4285 - "@eslint/plugin-kit": "^0.6.0", 4286 - "@humanfs/node": "^0.16.6", 4287 - "@humanwhocodes/module-importer": "^1.0.1", 4288 - "@humanwhocodes/retry": "^0.4.2", 4289 - "@types/estree": "^1.0.6", 4290 - "ajv": "^6.12.4", 4291 - "cross-spawn": "^7.0.6", 4292 - "debug": "^4.3.2", 4293 - "escape-string-regexp": "^4.0.0", 4294 - "eslint-scope": "^9.1.0", 4295 - "eslint-visitor-keys": "^5.0.0", 4296 - "espree": "^11.1.0", 4297 - "esquery": "^1.7.0", 4298 - "esutils": "^2.0.2", 4299 - "fast-deep-equal": "^3.1.3", 4300 - "file-entry-cache": "^8.0.0", 4301 - "find-up": "^5.0.0", 4302 - "glob-parent": "^6.0.2", 4303 - "ignore": "^5.2.0", 4304 - "imurmurhash": "^0.1.4", 4305 - "is-glob": "^4.0.0", 4306 - "json-stable-stringify-without-jsonify": "^1.0.1", 4307 - "minimatch": "^10.1.1", 4308 - "natural-compare": "^1.4.0", 4309 - "optionator": "^0.9.3" 4310 - }, 4311 - "bin": { 4312 - "eslint": "bin/eslint.js" 4313 - }, 4314 - "engines": { 4315 - "node": "^20.19.0 || ^22.13.0 || >=24" 4316 - }, 4317 - "funding": { 4318 - "url": "https://eslint.org/donate" 4319 - }, 4320 - "peerDependencies": { 4321 - "jiti": "*" 4322 - }, 4323 - "peerDependenciesMeta": { 4324 - "jiti": { 4325 - "optional": true 4326 - } 4327 - } 4328 - }, 4329 - "node_modules/eslint-config-prettier": { 4330 - "version": "10.1.8", 4331 - "dev": true, 4332 - "license": "MIT", 4333 - "bin": { 4334 - "eslint-config-prettier": "bin/cli.js" 4335 - }, 4336 - "funding": { 4337 - "url": "https://opencollective.com/eslint-config-prettier" 4338 - }, 4339 - "peerDependencies": { 4340 - "eslint": ">=7.0.0" 4341 - } 4342 - }, 4343 - "node_modules/eslint-plugin-prettier": { 4344 - "version": "5.5.5", 4345 - "dev": true, 4346 - "license": "MIT", 4347 - "dependencies": { 4348 - "prettier-linter-helpers": "^1.0.1", 4349 - "synckit": "^0.11.12" 4350 - }, 4351 - "engines": { 4352 - "node": "^14.18.0 || >=16.0.0" 4353 - }, 4354 - "funding": { 4355 - "url": "https://opencollective.com/eslint-plugin-prettier" 4356 - }, 4357 - "peerDependencies": { 4358 - "@types/eslint": ">=8.0.0", 4359 - "eslint": ">=8.0.0", 4360 - "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", 4361 - "prettier": ">=3.0.0" 4362 - }, 4363 - "peerDependenciesMeta": { 4364 - "@types/eslint": { 4365 - "optional": true 4366 - }, 4367 - "eslint-config-prettier": { 4368 - "optional": true 4369 - } 4370 - } 4371 - }, 4372 - "node_modules/eslint-plugin-react": { 4373 - "version": "7.37.5", 4374 - "dev": true, 4375 - "license": "MIT", 4376 - "dependencies": { 4377 - "array-includes": "^3.1.8", 4378 - "array.prototype.findlast": "^1.2.5", 4379 - "array.prototype.flatmap": "^1.3.3", 4380 - "array.prototype.tosorted": "^1.1.4", 4381 - "doctrine": "^2.1.0", 4382 - "es-iterator-helpers": "^1.2.1", 4383 - "estraverse": "^5.3.0", 4384 - "hasown": "^2.0.2", 4385 - "jsx-ast-utils": "^2.4.1 || ^3.0.0", 4386 - "minimatch": "^3.1.2", 4387 - "object.entries": "^1.1.9", 4388 - "object.fromentries": "^2.0.8", 4389 - "object.values": "^1.2.1", 4390 - "prop-types": "^15.8.1", 4391 - "resolve": "^2.0.0-next.5", 4392 - "semver": "^6.3.1", 4393 - "string.prototype.matchall": "^4.0.12", 4394 - "string.prototype.repeat": "^1.0.0" 4395 - }, 4396 - "engines": { 4397 - "node": ">=4" 4398 - }, 4399 - "peerDependencies": { 4400 - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" 4401 - } 4402 - }, 4403 - "node_modules/eslint-plugin-react-hooks": { 4404 - "version": "7.0.1", 4405 - "dev": true, 4406 - "license": "MIT", 4407 - "dependencies": { 4408 - "@babel/core": "^7.24.4", 4409 - "@babel/parser": "^7.24.4", 4410 - "hermes-parser": "^0.25.1", 4411 - "zod": "^3.25.0 || ^4.0.0", 4412 - "zod-validation-error": "^3.5.0 || ^4.0.0" 4413 - }, 4414 - "engines": { 4415 - "node": ">=18" 4416 - }, 4417 - "peerDependencies": { 4418 - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" 4419 - } 4420 - }, 4421 - "node_modules/eslint-plugin-react-refresh": { 4422 - "version": "0.5.0", 4423 - "dev": true, 4424 - "license": "MIT", 4425 - "peerDependencies": { 4426 - "eslint": ">=9" 4427 - } 4428 - }, 4429 - "node_modules/eslint-plugin-react/node_modules/minimatch": { 4430 - "version": "3.1.2", 4431 - "dev": true, 4432 - "license": "ISC", 4433 - "dependencies": { 4434 - "brace-expansion": "^1.1.7" 4435 - }, 4436 - "engines": { 4437 - "node": "*" 4438 - } 4439 - }, 4440 - "node_modules/eslint-scope": { 4441 - "version": "9.1.0", 4442 - "dev": true, 4443 - "license": "BSD-2-Clause", 4444 - "dependencies": { 4445 - "@types/esrecurse": "^4.3.1", 4446 - "@types/estree": "^1.0.8", 4447 - "esrecurse": "^4.3.0", 4448 - "estraverse": "^5.2.0" 4449 - }, 4450 - "engines": { 4451 - "node": "^20.19.0 || ^22.13.0 || >=24" 4452 - }, 4453 - "funding": { 4454 - "url": "https://opencollective.com/eslint" 4455 - } 4456 - }, 4457 - "node_modules/eslint-visitor-keys": { 4458 - "version": "5.0.0", 4459 - "dev": true, 4460 - "license": "Apache-2.0", 4461 - "engines": { 4462 - "node": "^20.19.0 || ^22.13.0 || >=24" 4463 - }, 4464 - "funding": { 4465 - "url": "https://opencollective.com/eslint" 4466 - } 4467 - }, 4468 - "node_modules/eslint/node_modules/ignore": { 4469 - "version": "5.3.2", 4470 - "dev": true, 4471 - "license": "MIT", 4472 - "engines": { 4473 - "node": ">= 4" 4474 - } 4475 - }, 4476 - "node_modules/espree": { 4477 - "version": "11.1.0", 4478 - "dev": true, 4479 - "license": "BSD-2-Clause", 4480 - "dependencies": { 4481 - "acorn": "^8.15.0", 4482 - "acorn-jsx": "^5.3.2", 4483 - "eslint-visitor-keys": "^5.0.0" 4484 - }, 4485 - "engines": { 4486 - "node": "^20.19.0 || ^22.13.0 || >=24" 4487 - }, 4488 - "funding": { 4489 - "url": "https://opencollective.com/eslint" 4490 - } 4491 - }, 4492 - "node_modules/esquery": { 4493 - "version": "1.7.0", 4494 - "dev": true, 4495 - "license": "BSD-3-Clause", 4496 - "dependencies": { 4497 - "estraverse": "^5.1.0" 4498 - }, 4499 - "engines": { 4500 - "node": ">=0.10" 4501 - } 4502 - }, 4503 - "node_modules/esrecurse": { 4504 - "version": "4.3.0", 4505 - "dev": true, 4506 - "license": "BSD-2-Clause", 4507 - "dependencies": { 4508 - "estraverse": "^5.2.0" 4509 - }, 4510 - "engines": { 4511 - "node": ">=4.0" 4512 - } 4513 - }, 4514 - "node_modules/estraverse": { 4515 - "version": "5.3.0", 4516 - "dev": true, 4517 - "license": "BSD-2-Clause", 4518 - "engines": { 4519 - "node": ">=4.0" 4520 - } 4521 - }, 4522 - "node_modules/estree-walker": { 4523 - "version": "3.0.3", 4524 - "license": "MIT", 4525 - "dependencies": { 4526 - "@types/estree": "^1.0.0" 4527 - } 4528 - }, 4529 - "node_modules/esutils": { 4530 - "version": "2.0.3", 4531 - "dev": true, 4532 - "license": "BSD-2-Clause", 4533 - "engines": { 4534 - "node": ">=0.10.0" 4535 - } 4536 - }, 4537 - "node_modules/etag": { 4538 - "version": "1.8.1", 4539 - "license": "MIT", 4540 - "engines": { 4541 - "node": ">= 0.6" 4542 - } 4543 - }, 4544 - "node_modules/eventemitter3": { 4545 - "version": "5.0.4", 4546 - "license": "MIT" 4547 - }, 4548 - "node_modules/extend": { 4549 - "version": "3.0.2", 4550 - "license": "MIT" 4551 - }, 4552 - "node_modules/fast-deep-equal": { 4553 - "version": "3.1.3", 4554 - "dev": true, 4555 - "license": "MIT" 4556 - }, 4557 - "node_modules/fast-diff": { 4558 - "version": "1.3.0", 4559 - "dev": true, 4560 - "license": "Apache-2.0" 4561 - }, 4562 - "node_modules/fast-glob": { 4563 - "version": "3.3.3", 4564 - "license": "MIT", 4565 - "dependencies": { 4566 - "@nodelib/fs.stat": "^2.0.2", 4567 - "@nodelib/fs.walk": "^1.2.3", 4568 - "glob-parent": "^5.1.2", 4569 - "merge2": "^1.3.0", 4570 - "micromatch": "^4.0.8" 4571 - }, 4572 - "engines": { 4573 - "node": ">=8.6.0" 4574 - } 4575 - }, 4576 - "node_modules/fast-glob/node_modules/glob-parent": { 4577 - "version": "5.1.2", 4578 - "license": "ISC", 4579 - "dependencies": { 4580 - "is-glob": "^4.0.1" 4581 - }, 4582 - "engines": { 4583 - "node": ">= 6" 4584 - } 4585 - }, 4586 - "node_modules/fast-json-stable-stringify": { 4587 - "version": "2.1.0", 4588 - "dev": true, 4589 - "license": "MIT" 4590 - }, 4591 - "node_modules/fast-levenshtein": { 4592 - "version": "2.0.6", 4593 - "dev": true, 4594 - "license": "MIT" 4595 - }, 4596 - "node_modules/fastq": { 4597 - "version": "1.20.1", 4598 - "license": "ISC", 4599 - "dependencies": { 4600 - "reusify": "^1.0.4" 4601 - } 4602 - }, 4603 - "node_modules/fdir": { 4604 - "version": "6.5.0", 4605 - "license": "MIT", 4606 - "engines": { 4607 - "node": ">=12.0.0" 4608 - }, 4609 - "peerDependencies": { 4610 - "picomatch": "^3 || ^4" 4611 - }, 4612 - "peerDependenciesMeta": { 4613 - "picomatch": { 4614 - "optional": true 4615 - } 4616 - } 4617 - }, 4618 - "node_modules/fflate": { 4619 - "version": "0.7.4", 4620 - "license": "MIT" 4621 - }, 4622 - "node_modules/file-entry-cache": { 4623 - "version": "8.0.0", 4624 - "dev": true, 4625 - "license": "MIT", 4626 - "dependencies": { 4627 - "flat-cache": "^4.0.0" 4628 - }, 4629 - "engines": { 4630 - "node": ">=16.0.0" 4631 - } 4632 - }, 4633 - "node_modules/fill-range": { 4634 - "version": "7.1.1", 4635 - "license": "MIT", 4636 - "dependencies": { 4637 - "to-regex-range": "^5.0.1" 4638 - }, 4639 - "engines": { 4640 - "node": ">=8" 4641 - } 4642 - }, 4643 - "node_modules/find-up": { 4644 - "version": "5.0.0", 4645 - "dev": true, 4646 - "license": "MIT", 4647 - "dependencies": { 4648 - "locate-path": "^6.0.0", 4649 - "path-exists": "^4.0.0" 4650 - }, 4651 - "engines": { 4652 - "node": ">=10" 4653 - }, 4654 - "funding": { 4655 - "url": "https://github.com/sponsors/sindresorhus" 4656 - } 4657 - }, 4658 - "node_modules/flairup": { 4659 - "version": "1.0.0", 4660 - "license": "MIT" 4661 - }, 4662 - "node_modules/flat-cache": { 4663 - "version": "4.0.1", 4664 - "dev": true, 4665 - "license": "MIT", 4666 - "dependencies": { 4667 - "flatted": "^3.2.9", 4668 - "keyv": "^4.5.4" 4669 - }, 4670 - "engines": { 4671 - "node": ">=16" 4672 - } 4673 - }, 4674 - "node_modules/flatted": { 4675 - "version": "3.3.3", 4676 - "dev": true, 4677 - "license": "ISC" 4678 - }, 4679 - "node_modules/flattie": { 4680 - "version": "1.1.1", 4681 - "license": "MIT", 4682 - "engines": { 4683 - "node": ">=8" 4684 - } 4685 - }, 4686 - "node_modules/fontace": { 4687 - "version": "0.4.1", 4688 - "license": "MIT", 4689 - "dependencies": { 4690 - "fontkitten": "^1.0.2" 4691 - } 4692 - }, 4693 - "node_modules/fontkitten": { 4694 - "version": "1.0.2", 4695 - "license": "MIT", 4696 - "dependencies": { 4697 - "tiny-inflate": "^1.0.3" 4698 - }, 4699 - "engines": { 4700 - "node": ">=20" 4701 - } 4702 - }, 4703 - "node_modules/for-each": { 4704 - "version": "0.3.5", 4705 - "dev": true, 4706 - "license": "MIT", 4707 - "dependencies": { 4708 - "is-callable": "^1.2.7" 4709 - }, 4710 - "engines": { 4711 - "node": ">= 0.4" 4712 - }, 4713 - "funding": { 4714 - "url": "https://github.com/sponsors/ljharb" 4715 - } 4716 - }, 4717 - "node_modules/fraction.js": { 4718 - "version": "5.3.4", 4719 - "license": "MIT", 4720 - "engines": { 4721 - "node": "*" 4722 - }, 4723 - "funding": { 4724 - "type": "github", 4725 - "url": "https://github.com/sponsors/rawify" 4726 - } 4727 - }, 4728 - "node_modules/fresh": { 4729 - "version": "2.0.0", 4730 - "license": "MIT", 4731 - "engines": { 4732 - "node": ">= 0.8" 4733 - } 4734 - }, 4735 - "node_modules/fsevents": { 4736 - "version": "2.3.3", 4737 - "license": "MIT", 4738 - "optional": true, 4739 - "os": [ 4740 - "darwin" 4741 - ], 4742 - "engines": { 4743 - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 4744 - } 4745 - }, 4746 - "node_modules/function-bind": { 4747 - "version": "1.1.2", 4748 - "license": "MIT", 4749 - "funding": { 4750 - "url": "https://github.com/sponsors/ljharb" 4751 - } 4752 - }, 4753 - "node_modules/function.prototype.name": { 4754 - "version": "1.1.8", 4755 - "dev": true, 4756 - "license": "MIT", 4757 - "dependencies": { 4758 - "call-bind": "^1.0.8", 4759 - "call-bound": "^1.0.3", 4760 - "define-properties": "^1.2.1", 4761 - "functions-have-names": "^1.2.3", 4762 - "hasown": "^2.0.2", 4763 - "is-callable": "^1.2.7" 4764 - }, 4765 - "engines": { 4766 - "node": ">= 0.4" 4767 - }, 4768 - "funding": { 4769 - "url": "https://github.com/sponsors/ljharb" 4770 - } 4771 - }, 4772 - "node_modules/functions-have-names": { 4773 - "version": "1.2.3", 4774 - "dev": true, 4775 - "license": "MIT", 4776 - "funding": { 4777 - "url": "https://github.com/sponsors/ljharb" 4778 - } 4779 - }, 4780 - "node_modules/generator-function": { 4781 - "version": "2.0.1", 4782 - "dev": true, 4783 - "license": "MIT", 4784 - "engines": { 4785 - "node": ">= 0.4" 4786 - } 4787 - }, 4788 - "node_modules/gensync": { 4789 - "version": "1.0.0-beta.2", 4790 - "license": "MIT", 4791 - "engines": { 4792 - "node": ">=6.9.0" 4793 - } 4794 - }, 4795 - "node_modules/get-east-asian-width": { 4796 - "version": "1.4.0", 4797 - "license": "MIT", 4798 - "engines": { 4799 - "node": ">=18" 4800 - }, 4801 - "funding": { 4802 - "url": "https://github.com/sponsors/sindresorhus" 4803 - } 4804 - }, 4805 - "node_modules/get-intrinsic": { 4806 - "version": "1.3.0", 4807 - "dev": true, 4808 - "license": "MIT", 4809 - "dependencies": { 4810 - "call-bind-apply-helpers": "^1.0.2", 4811 - "es-define-property": "^1.0.1", 4812 - "es-errors": "^1.3.0", 4813 - "es-object-atoms": "^1.1.1", 4814 - "function-bind": "^1.1.2", 4815 - "get-proto": "^1.0.1", 4816 - "gopd": "^1.2.0", 4817 - "has-symbols": "^1.1.0", 4818 - "hasown": "^2.0.2", 4819 - "math-intrinsics": "^1.1.0" 4820 - }, 4821 - "engines": { 4822 - "node": ">= 0.4" 4823 - }, 4824 - "funding": { 4825 - "url": "https://github.com/sponsors/ljharb" 4826 - } 4827 - }, 4828 - "node_modules/get-proto": { 4829 - "version": "1.0.1", 4830 - "dev": true, 4831 - "license": "MIT", 4832 - "dependencies": { 4833 - "dunder-proto": "^1.0.1", 4834 - "es-object-atoms": "^1.0.0" 4835 - }, 4836 - "engines": { 4837 - "node": ">= 0.4" 4838 - } 4839 - }, 4840 - "node_modules/get-symbol-description": { 4841 - "version": "1.1.0", 4842 - "dev": true, 4843 - "license": "MIT", 4844 - "dependencies": { 4845 - "call-bound": "^1.0.3", 4846 - "es-errors": "^1.3.0", 4847 - "get-intrinsic": "^1.2.6" 4848 - }, 4849 - "engines": { 4850 - "node": ">= 0.4" 4851 - }, 4852 - "funding": { 4853 - "url": "https://github.com/sponsors/ljharb" 4854 - } 4855 - }, 4856 - "node_modules/github-slugger": { 4857 - "version": "2.0.0", 4858 - "license": "ISC" 4859 - }, 4860 - "node_modules/glob-parent": { 4861 - "version": "6.0.2", 4862 - "license": "ISC", 4863 - "dependencies": { 4864 - "is-glob": "^4.0.3" 4865 - }, 4866 - "engines": { 4867 - "node": ">=10.13.0" 4868 - } 4869 - }, 4870 - "node_modules/globals": { 4871 - "version": "17.3.0", 4872 - "dev": true, 4873 - "license": "MIT", 4874 - "engines": { 4875 - "node": ">=18" 4876 - }, 4877 - "funding": { 4878 - "url": "https://github.com/sponsors/sindresorhus" 4879 - } 4880 - }, 4881 - "node_modules/globalthis": { 4882 - "version": "1.0.4", 4883 - "dev": true, 4884 - "license": "MIT", 4885 - "dependencies": { 4886 - "define-properties": "^1.2.1", 4887 - "gopd": "^1.0.1" 4888 - }, 4889 - "engines": { 4890 - "node": ">= 0.4" 4891 - }, 4892 - "funding": { 4893 - "url": "https://github.com/sponsors/ljharb" 4894 - } 4895 - }, 4896 - "node_modules/gopd": { 4897 - "version": "1.2.0", 4898 - "dev": true, 4899 - "license": "MIT", 4900 - "engines": { 4901 - "node": ">= 0.4" 4902 - }, 4903 - "funding": { 4904 - "url": "https://github.com/sponsors/ljharb" 4905 - } 4906 - }, 4907 - "node_modules/graceful-fs": { 4908 - "version": "4.2.11", 4909 - "license": "ISC" 4910 - }, 4911 - "node_modules/h3": { 4912 - "version": "1.15.5", 4913 - "license": "MIT", 4914 - "dependencies": { 4915 - "cookie-es": "^1.2.2", 4916 - "crossws": "^0.3.5", 4917 - "defu": "^6.1.4", 4918 - "destr": "^2.0.5", 4919 - "iron-webcrypto": "^1.2.1", 4920 - "node-mock-http": "^1.0.4", 4921 - "radix3": "^1.1.2", 4922 - "ufo": "^1.6.3", 4923 - "uncrypto": "^0.1.3" 4924 - } 4925 - }, 4926 - "node_modules/has-bigints": { 4927 - "version": "1.1.0", 4928 - "dev": true, 4929 - "license": "MIT", 4930 - "engines": { 4931 - "node": ">= 0.4" 4932 - }, 4933 - "funding": { 4934 - "url": "https://github.com/sponsors/ljharb" 4935 - } 4936 - }, 4937 - "node_modules/has-property-descriptors": { 4938 - "version": "1.0.2", 4939 - "dev": true, 4940 - "license": "MIT", 4941 - "dependencies": { 4942 - "es-define-property": "^1.0.0" 4943 - }, 4944 - "funding": { 4945 - "url": "https://github.com/sponsors/ljharb" 4946 - } 4947 - }, 4948 - "node_modules/has-proto": { 4949 - "version": "1.2.0", 4950 - "dev": true, 4951 - "license": "MIT", 4952 - "dependencies": { 4953 - "dunder-proto": "^1.0.0" 4954 - }, 4955 - "engines": { 4956 - "node": ">= 0.4" 4957 - }, 4958 - "funding": { 4959 - "url": "https://github.com/sponsors/ljharb" 4960 - } 4961 - }, 4962 - "node_modules/has-symbols": { 4963 - "version": "1.1.0", 4964 - "dev": true, 4965 - "license": "MIT", 4966 - "engines": { 4967 - "node": ">= 0.4" 4968 - }, 4969 - "funding": { 4970 - "url": "https://github.com/sponsors/ljharb" 4971 - } 4972 - }, 4973 - "node_modules/has-tostringtag": { 4974 - "version": "1.0.2", 4975 - "dev": true, 4976 - "license": "MIT", 4977 - "dependencies": { 4978 - "has-symbols": "^1.0.3" 4979 - }, 4980 - "engines": { 4981 - "node": ">= 0.4" 4982 - }, 4983 - "funding": { 4984 - "url": "https://github.com/sponsors/ljharb" 4985 - } 4986 - }, 4987 - "node_modules/hasown": { 4988 - "version": "2.0.2", 4989 - "license": "MIT", 4990 - "dependencies": { 4991 - "function-bind": "^1.1.2" 4992 - }, 4993 - "engines": { 4994 - "node": ">= 0.4" 4995 - } 4996 - }, 4997 - "node_modules/hast-util-from-html": { 4998 - "version": "2.0.3", 4999 - "license": "MIT", 5000 - "dependencies": { 5001 - "@types/hast": "^3.0.0", 5002 - "devlop": "^1.1.0", 5003 - "hast-util-from-parse5": "^8.0.0", 5004 - "parse5": "^7.0.0", 5005 - "vfile": "^6.0.0", 5006 - "vfile-message": "^4.0.0" 5007 - }, 5008 - "funding": { 5009 - "type": "opencollective", 5010 - "url": "https://opencollective.com/unified" 5011 - } 5012 - }, 5013 - "node_modules/hast-util-from-parse5": { 5014 - "version": "8.0.3", 5015 - "license": "MIT", 5016 - "dependencies": { 5017 - "@types/hast": "^3.0.0", 5018 - "@types/unist": "^3.0.0", 5019 - "devlop": "^1.0.0", 5020 - "hastscript": "^9.0.0", 5021 - "property-information": "^7.0.0", 5022 - "vfile": "^6.0.0", 5023 - "vfile-location": "^5.0.0", 5024 - "web-namespaces": "^2.0.0" 5025 - }, 5026 - "funding": { 5027 - "type": "opencollective", 5028 - "url": "https://opencollective.com/unified" 5029 - } 5030 - }, 5031 - "node_modules/hast-util-is-element": { 5032 - "version": "3.0.0", 5033 - "license": "MIT", 5034 - "dependencies": { 5035 - "@types/hast": "^3.0.0" 5036 - }, 5037 - "funding": { 5038 - "type": "opencollective", 5039 - "url": "https://opencollective.com/unified" 5040 - } 5041 - }, 5042 - "node_modules/hast-util-parse-selector": { 5043 - "version": "4.0.0", 5044 - "license": "MIT", 5045 - "dependencies": { 5046 - "@types/hast": "^3.0.0" 5047 - }, 5048 - "funding": { 5049 - "type": "opencollective", 5050 - "url": "https://opencollective.com/unified" 5051 - } 5052 - }, 5053 - "node_modules/hast-util-raw": { 5054 - "version": "9.1.0", 5055 - "license": "MIT", 5056 - "dependencies": { 5057 - "@types/hast": "^3.0.0", 5058 - "@types/unist": "^3.0.0", 5059 - "@ungap/structured-clone": "^1.0.0", 5060 - "hast-util-from-parse5": "^8.0.0", 5061 - "hast-util-to-parse5": "^8.0.0", 5062 - "html-void-elements": "^3.0.0", 5063 - "mdast-util-to-hast": "^13.0.0", 5064 - "parse5": "^7.0.0", 5065 - "unist-util-position": "^5.0.0", 5066 - "unist-util-visit": "^5.0.0", 5067 - "vfile": "^6.0.0", 5068 - "web-namespaces": "^2.0.0", 5069 - "zwitch": "^2.0.0" 5070 - }, 5071 - "funding": { 5072 - "type": "opencollective", 5073 - "url": "https://opencollective.com/unified" 5074 - } 5075 - }, 5076 - "node_modules/hast-util-to-html": { 5077 - "version": "9.0.5", 5078 - "license": "MIT", 5079 - "dependencies": { 5080 - "@types/hast": "^3.0.0", 5081 - "@types/unist": "^3.0.0", 5082 - "ccount": "^2.0.0", 5083 - "comma-separated-tokens": "^2.0.0", 5084 - "hast-util-whitespace": "^3.0.0", 5085 - "html-void-elements": "^3.0.0", 5086 - "mdast-util-to-hast": "^13.0.0", 5087 - "property-information": "^7.0.0", 5088 - "space-separated-tokens": "^2.0.0", 5089 - "stringify-entities": "^4.0.0", 5090 - "zwitch": "^2.0.4" 5091 - }, 5092 - "funding": { 5093 - "type": "opencollective", 5094 - "url": "https://opencollective.com/unified" 5095 - } 5096 - }, 5097 - "node_modules/hast-util-to-parse5": { 5098 - "version": "8.0.1", 5099 - "license": "MIT", 5100 - "dependencies": { 5101 - "@types/hast": "^3.0.0", 5102 - "comma-separated-tokens": "^2.0.0", 5103 - "devlop": "^1.0.0", 5104 - "property-information": "^7.0.0", 5105 - "space-separated-tokens": "^2.0.0", 5106 - "web-namespaces": "^2.0.0", 5107 - "zwitch": "^2.0.0" 5108 - }, 5109 - "funding": { 5110 - "type": "opencollective", 5111 - "url": "https://opencollective.com/unified" 5112 - } 5113 - }, 5114 - "node_modules/hast-util-to-text": { 5115 - "version": "4.0.2", 5116 - "license": "MIT", 5117 - "dependencies": { 5118 - "@types/hast": "^3.0.0", 5119 - "@types/unist": "^3.0.0", 5120 - "hast-util-is-element": "^3.0.0", 5121 - "unist-util-find-after": "^5.0.0" 5122 - }, 5123 - "funding": { 5124 - "type": "opencollective", 5125 - "url": "https://opencollective.com/unified" 5126 - } 5127 - }, 5128 - "node_modules/hast-util-whitespace": { 5129 - "version": "3.0.0", 5130 - "license": "MIT", 5131 - "dependencies": { 5132 - "@types/hast": "^3.0.0" 5133 - }, 5134 - "funding": { 5135 - "type": "opencollective", 5136 - "url": "https://opencollective.com/unified" 5137 - } 5138 - }, 5139 - "node_modules/hastscript": { 5140 - "version": "9.0.1", 5141 - "license": "MIT", 5142 - "dependencies": { 5143 - "@types/hast": "^3.0.0", 5144 - "comma-separated-tokens": "^2.0.0", 5145 - "hast-util-parse-selector": "^4.0.0", 5146 - "property-information": "^7.0.0", 5147 - "space-separated-tokens": "^2.0.0" 5148 - }, 5149 - "funding": { 5150 - "type": "opencollective", 5151 - "url": "https://opencollective.com/unified" 5152 - } 5153 - }, 5154 - "node_modules/hermes-estree": { 5155 - "version": "0.25.1", 5156 - "dev": true, 5157 - "license": "MIT" 5158 - }, 5159 - "node_modules/hermes-parser": { 5160 - "version": "0.25.1", 5161 - "dev": true, 5162 - "license": "MIT", 5163 - "dependencies": { 5164 - "hermes-estree": "0.25.1" 5165 - } 5166 - }, 5167 - "node_modules/hex-rgb": { 5168 - "version": "4.3.0", 5169 - "license": "MIT", 5170 - "engines": { 5171 - "node": ">=6" 5172 - }, 5173 - "funding": { 5174 - "url": "https://github.com/sponsors/sindresorhus" 5175 - } 5176 - }, 5177 - "node_modules/html-escaper": { 5178 - "version": "3.0.3", 5179 - "license": "MIT" 5180 - }, 5181 - "node_modules/html-void-elements": { 5182 - "version": "3.0.0", 5183 - "license": "MIT", 5184 - "funding": { 5185 - "type": "github", 5186 - "url": "https://github.com/sponsors/wooorm" 5187 - } 5188 - }, 5189 - "node_modules/http-cache-semantics": { 5190 - "version": "4.2.0", 5191 - "license": "BSD-2-Clause" 5192 - }, 5193 - "node_modules/http-errors": { 5194 - "version": "2.0.1", 5195 - "license": "MIT", 5196 - "dependencies": { 5197 - "depd": "~2.0.0", 5198 - "inherits": "~2.0.4", 5199 - "setprototypeof": "~1.2.0", 5200 - "statuses": "~2.0.2", 5201 - "toidentifier": "~1.0.1" 5202 - }, 5203 - "engines": { 5204 - "node": ">= 0.8" 5205 - }, 5206 - "funding": { 5207 - "type": "opencollective", 5208 - "url": "https://opencollective.com/express" 5209 - } 5210 - }, 5211 - "node_modules/ignore": { 5212 - "version": "7.0.5", 5213 - "dev": true, 5214 - "license": "MIT", 5215 - "engines": { 5216 - "node": ">= 4" 5217 - } 5218 - }, 5219 - "node_modules/import-meta-resolve": { 5220 - "version": "4.2.0", 5221 - "license": "MIT", 5222 - "funding": { 5223 - "type": "github", 5224 - "url": "https://github.com/sponsors/wooorm" 5225 - } 5226 - }, 5227 - "node_modules/imurmurhash": { 5228 - "version": "0.1.4", 5229 - "dev": true, 5230 - "license": "MIT", 5231 - "engines": { 5232 - "node": ">=0.8.19" 5233 - } 5234 - }, 5235 - "node_modules/inherits": { 5236 - "version": "2.0.4", 5237 - "license": "ISC" 5238 - }, 5239 - "node_modules/internal-slot": { 5240 - "version": "1.1.0", 5241 - "dev": true, 5242 - "license": "MIT", 5243 - "dependencies": { 5244 - "es-errors": "^1.3.0", 5245 - "hasown": "^2.0.2", 5246 - "side-channel": "^1.1.0" 5247 - }, 5248 - "engines": { 5249 - "node": ">= 0.4" 5250 - } 5251 - }, 5252 - "node_modules/iron-webcrypto": { 5253 - "version": "1.2.1", 5254 - "license": "MIT", 5255 - "funding": { 5256 - "url": "https://github.com/sponsors/brc-dd" 5257 - } 5258 - }, 5259 - "node_modules/is-array-buffer": { 5260 - "version": "3.0.5", 5261 - "dev": true, 5262 - "license": "MIT", 5263 - "dependencies": { 5264 - "call-bind": "^1.0.8", 5265 - "call-bound": "^1.0.3", 5266 - "get-intrinsic": "^1.2.6" 5267 - }, 5268 - "engines": { 5269 - "node": ">= 0.4" 5270 - }, 5271 - "funding": { 5272 - "url": "https://github.com/sponsors/ljharb" 5273 - } 5274 - }, 5275 - "node_modules/is-async-function": { 5276 - "version": "2.1.1", 5277 - "dev": true, 5278 - "license": "MIT", 5279 - "dependencies": { 5280 - "async-function": "^1.0.0", 5281 - "call-bound": "^1.0.3", 5282 - "get-proto": "^1.0.1", 5283 - "has-tostringtag": "^1.0.2", 5284 - "safe-regex-test": "^1.1.0" 5285 - }, 5286 - "engines": { 5287 - "node": ">= 0.4" 5288 - }, 5289 - "funding": { 5290 - "url": "https://github.com/sponsors/ljharb" 5291 - } 5292 - }, 5293 - "node_modules/is-bigint": { 5294 - "version": "1.1.0", 5295 - "dev": true, 5296 - "license": "MIT", 5297 - "dependencies": { 5298 - "has-bigints": "^1.0.2" 5299 - }, 5300 - "engines": { 5301 - "node": ">= 0.4" 5302 - }, 5303 - "funding": { 5304 - "url": "https://github.com/sponsors/ljharb" 5305 - } 5306 - }, 5307 - "node_modules/is-binary-path": { 5308 - "version": "2.1.0", 5309 - "license": "MIT", 5310 - "dependencies": { 5311 - "binary-extensions": "^2.0.0" 5312 - }, 5313 - "engines": { 5314 - "node": ">=8" 5315 - } 5316 - }, 5317 - "node_modules/is-boolean-object": { 5318 - "version": "1.2.2", 5319 - "dev": true, 5320 - "license": "MIT", 5321 - "dependencies": { 5322 - "call-bound": "^1.0.3", 5323 - "has-tostringtag": "^1.0.2" 5324 - }, 5325 - "engines": { 5326 - "node": ">= 0.4" 5327 - }, 5328 - "funding": { 5329 - "url": "https://github.com/sponsors/ljharb" 5330 - } 5331 - }, 5332 - "node_modules/is-callable": { 5333 - "version": "1.2.7", 5334 - "dev": true, 5335 - "license": "MIT", 5336 - "engines": { 5337 - "node": ">= 0.4" 5338 - }, 5339 - "funding": { 5340 - "url": "https://github.com/sponsors/ljharb" 5341 - } 5342 - }, 5343 - "node_modules/is-core-module": { 5344 - "version": "2.16.1", 5345 - "license": "MIT", 5346 - "dependencies": { 5347 - "hasown": "^2.0.2" 5348 - }, 5349 - "engines": { 5350 - "node": ">= 0.4" 5351 - }, 5352 - "funding": { 5353 - "url": "https://github.com/sponsors/ljharb" 5354 - } 5355 - }, 5356 - "node_modules/is-data-view": { 5357 - "version": "1.0.2", 5358 - "dev": true, 5359 - "license": "MIT", 5360 - "dependencies": { 5361 - "call-bound": "^1.0.2", 5362 - "get-intrinsic": "^1.2.6", 5363 - "is-typed-array": "^1.1.13" 5364 - }, 5365 - "engines": { 5366 - "node": ">= 0.4" 5367 - }, 5368 - "funding": { 5369 - "url": "https://github.com/sponsors/ljharb" 5370 - } 5371 - }, 5372 - "node_modules/is-date-object": { 5373 - "version": "1.1.0", 5374 - "dev": true, 5375 - "license": "MIT", 5376 - "dependencies": { 5377 - "call-bound": "^1.0.2", 5378 - "has-tostringtag": "^1.0.2" 5379 - }, 5380 - "engines": { 5381 - "node": ">= 0.4" 5382 - }, 5383 - "funding": { 5384 - "url": "https://github.com/sponsors/ljharb" 5385 - } 5386 - }, 5387 - "node_modules/is-docker": { 5388 - "version": "3.0.0", 5389 - "license": "MIT", 5390 - "bin": { 5391 - "is-docker": "cli.js" 5392 - }, 5393 - "engines": { 5394 - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 5395 - }, 5396 - "funding": { 5397 - "url": "https://github.com/sponsors/sindresorhus" 5398 - } 5399 - }, 5400 - "node_modules/is-extglob": { 5401 - "version": "2.1.1", 5402 - "license": "MIT", 5403 - "engines": { 5404 - "node": ">=0.10.0" 5405 - } 5406 - }, 5407 - "node_modules/is-finalizationregistry": { 5408 - "version": "1.1.1", 5409 - "dev": true, 5410 - "license": "MIT", 5411 - "dependencies": { 5412 - "call-bound": "^1.0.3" 5413 - }, 5414 - "engines": { 5415 - "node": ">= 0.4" 5416 - }, 5417 - "funding": { 5418 - "url": "https://github.com/sponsors/ljharb" 5419 - } 5420 - }, 5421 - "node_modules/is-fullwidth-code-point": { 5422 - "version": "3.0.0", 5423 - "license": "MIT", 5424 - "engines": { 5425 - "node": ">=8" 5426 - } 5427 - }, 5428 - "node_modules/is-generator-function": { 5429 - "version": "1.1.2", 5430 - "dev": true, 5431 - "license": "MIT", 5432 - "dependencies": { 5433 - "call-bound": "^1.0.4", 5434 - "generator-function": "^2.0.0", 5435 - "get-proto": "^1.0.1", 5436 - "has-tostringtag": "^1.0.2", 5437 - "safe-regex-test": "^1.1.0" 5438 - }, 5439 - "engines": { 5440 - "node": ">= 0.4" 5441 - }, 5442 - "funding": { 5443 - "url": "https://github.com/sponsors/ljharb" 5444 - } 5445 - }, 5446 - "node_modules/is-glob": { 5447 - "version": "4.0.3", 5448 - "license": "MIT", 5449 - "dependencies": { 5450 - "is-extglob": "^2.1.1" 5451 - }, 5452 - "engines": { 5453 - "node": ">=0.10.0" 5454 - } 5455 - }, 5456 - "node_modules/is-inside-container": { 5457 - "version": "1.0.0", 5458 - "license": "MIT", 5459 - "dependencies": { 5460 - "is-docker": "^3.0.0" 5461 - }, 5462 - "bin": { 5463 - "is-inside-container": "cli.js" 5464 - }, 5465 - "engines": { 5466 - "node": ">=14.16" 5467 - }, 5468 - "funding": { 5469 - "url": "https://github.com/sponsors/sindresorhus" 5470 - } 5471 - }, 5472 - "node_modules/is-map": { 5473 - "version": "2.0.3", 5474 - "dev": true, 5475 - "license": "MIT", 5476 - "engines": { 5477 - "node": ">= 0.4" 5478 - }, 5479 - "funding": { 5480 - "url": "https://github.com/sponsors/ljharb" 5481 - } 5482 - }, 5483 - "node_modules/is-negative-zero": { 5484 - "version": "2.0.3", 5485 - "dev": true, 5486 - "license": "MIT", 5487 - "engines": { 5488 - "node": ">= 0.4" 5489 - }, 5490 - "funding": { 5491 - "url": "https://github.com/sponsors/ljharb" 5492 - } 5493 - }, 5494 - "node_modules/is-number": { 5495 - "version": "7.0.0", 5496 - "license": "MIT", 5497 - "engines": { 5498 - "node": ">=0.12.0" 5499 - } 5500 - }, 5501 - "node_modules/is-number-object": { 5502 - "version": "1.1.1", 5503 - "dev": true, 5504 - "license": "MIT", 5505 - "dependencies": { 5506 - "call-bound": "^1.0.3", 5507 - "has-tostringtag": "^1.0.2" 5508 - }, 5509 - "engines": { 5510 - "node": ">= 0.4" 5511 - }, 5512 - "funding": { 5513 - "url": "https://github.com/sponsors/ljharb" 5514 - } 5515 - }, 5516 - "node_modules/is-plain-obj": { 5517 - "version": "4.1.0", 5518 - "license": "MIT", 5519 - "engines": { 5520 - "node": ">=12" 5521 - }, 5522 - "funding": { 5523 - "url": "https://github.com/sponsors/sindresorhus" 5524 - } 5525 - }, 5526 - "node_modules/is-regex": { 5527 - "version": "1.2.1", 5528 - "dev": true, 5529 - "license": "MIT", 5530 - "dependencies": { 5531 - "call-bound": "^1.0.2", 5532 - "gopd": "^1.2.0", 5533 - "has-tostringtag": "^1.0.2", 5534 - "hasown": "^2.0.2" 5535 - }, 5536 - "engines": { 5537 - "node": ">= 0.4" 5538 - }, 5539 - "funding": { 5540 - "url": "https://github.com/sponsors/ljharb" 5541 - } 5542 - }, 5543 - "node_modules/is-set": { 5544 - "version": "2.0.3", 5545 - "dev": true, 5546 - "license": "MIT", 5547 - "engines": { 5548 - "node": ">= 0.4" 5549 - }, 5550 - "funding": { 5551 - "url": "https://github.com/sponsors/ljharb" 5552 - } 5553 - }, 5554 - "node_modules/is-shared-array-buffer": { 5555 - "version": "1.0.4", 5556 - "dev": true, 5557 - "license": "MIT", 5558 - "dependencies": { 5559 - "call-bound": "^1.0.3" 5560 - }, 5561 - "engines": { 5562 - "node": ">= 0.4" 5563 - }, 5564 - "funding": { 5565 - "url": "https://github.com/sponsors/ljharb" 5566 - } 5567 - }, 5568 - "node_modules/is-string": { 5569 - "version": "1.1.1", 5570 - "dev": true, 5571 - "license": "MIT", 5572 - "dependencies": { 5573 - "call-bound": "^1.0.3", 5574 - "has-tostringtag": "^1.0.2" 5575 - }, 5576 - "engines": { 5577 - "node": ">= 0.4" 5578 - }, 5579 - "funding": { 5580 - "url": "https://github.com/sponsors/ljharb" 5581 - } 5582 - }, 5583 - "node_modules/is-symbol": { 5584 - "version": "1.1.1", 5585 - "dev": true, 5586 - "license": "MIT", 5587 - "dependencies": { 5588 - "call-bound": "^1.0.2", 5589 - "has-symbols": "^1.1.0", 5590 - "safe-regex-test": "^1.1.0" 5591 - }, 5592 - "engines": { 5593 - "node": ">= 0.4" 5594 - }, 5595 - "funding": { 5596 - "url": "https://github.com/sponsors/ljharb" 5597 - } 5598 - }, 5599 - "node_modules/is-typed-array": { 5600 - "version": "1.1.15", 5601 - "dev": true, 5602 - "license": "MIT", 5603 - "dependencies": { 5604 - "which-typed-array": "^1.1.16" 5605 - }, 5606 - "engines": { 5607 - "node": ">= 0.4" 5608 - }, 5609 - "funding": { 5610 - "url": "https://github.com/sponsors/ljharb" 5611 - } 5612 - }, 5613 - "node_modules/is-weakmap": { 5614 - "version": "2.0.2", 5615 - "dev": true, 5616 - "license": "MIT", 5617 - "engines": { 5618 - "node": ">= 0.4" 5619 - }, 5620 - "funding": { 5621 - "url": "https://github.com/sponsors/ljharb" 5622 - } 5623 - }, 5624 - "node_modules/is-weakref": { 5625 - "version": "1.1.1", 5626 - "dev": true, 5627 - "license": "MIT", 5628 - "dependencies": { 5629 - "call-bound": "^1.0.3" 5630 - }, 5631 - "engines": { 5632 - "node": ">= 0.4" 5633 - }, 5634 - "funding": { 5635 - "url": "https://github.com/sponsors/ljharb" 5636 - } 5637 - }, 5638 - "node_modules/is-weakset": { 5639 - "version": "2.0.4", 5640 - "dev": true, 5641 - "license": "MIT", 5642 - "dependencies": { 5643 - "call-bound": "^1.0.3", 5644 - "get-intrinsic": "^1.2.6" 5645 - }, 5646 - "engines": { 5647 - "node": ">= 0.4" 5648 - }, 5649 - "funding": { 5650 - "url": "https://github.com/sponsors/ljharb" 5651 - } 5652 - }, 5653 - "node_modules/is-wsl": { 5654 - "version": "3.1.0", 5655 - "license": "MIT", 5656 - "dependencies": { 5657 - "is-inside-container": "^1.0.0" 5658 - }, 5659 - "engines": { 5660 - "node": ">=16" 5661 - }, 5662 - "funding": { 5663 - "url": "https://github.com/sponsors/sindresorhus" 5664 - } 5665 - }, 5666 - "node_modules/isarray": { 5667 - "version": "2.0.5", 5668 - "dev": true, 5669 - "license": "MIT" 5670 - }, 5671 - "node_modules/isexe": { 5672 - "version": "2.0.0", 5673 - "dev": true, 5674 - "license": "ISC" 5675 - }, 5676 - "node_modules/iterator.prototype": { 5677 - "version": "1.1.5", 5678 - "dev": true, 5679 - "license": "MIT", 5680 - "dependencies": { 5681 - "define-data-property": "^1.1.4", 5682 - "es-object-atoms": "^1.0.0", 5683 - "get-intrinsic": "^1.2.6", 5684 - "get-proto": "^1.0.0", 5685 - "has-symbols": "^1.1.0", 5686 - "set-function-name": "^2.0.2" 5687 - }, 5688 - "engines": { 5689 - "node": ">= 0.4" 5690 - } 5691 - }, 5692 - "node_modules/js-tokens": { 5693 - "version": "4.0.0", 5694 - "license": "MIT" 5695 - }, 5696 - "node_modules/js-yaml": { 5697 - "version": "4.1.1", 5698 - "license": "MIT", 5699 - "dependencies": { 5700 - "argparse": "^2.0.1" 5701 - }, 5702 - "bin": { 5703 - "js-yaml": "bin/js-yaml.js" 5704 - } 5705 - }, 5706 - "node_modules/jsesc": { 5707 - "version": "3.1.0", 5708 - "license": "MIT", 5709 - "bin": { 5710 - "jsesc": "bin/jsesc" 5711 - }, 5712 - "engines": { 5713 - "node": ">=6" 5714 - } 5715 - }, 5716 - "node_modules/json-buffer": { 5717 - "version": "3.0.1", 5718 - "dev": true, 5719 - "license": "MIT" 5720 - }, 5721 - "node_modules/json-schema-traverse": { 5722 - "version": "0.4.1", 5723 - "dev": true, 5724 - "license": "MIT" 5725 - }, 5726 - "node_modules/json-stable-stringify-without-jsonify": { 5727 - "version": "1.0.1", 5728 - "dev": true, 5729 - "license": "MIT" 5730 - }, 5731 - "node_modules/json5": { 5732 - "version": "2.2.3", 5733 - "license": "MIT", 5734 - "bin": { 5735 - "json5": "lib/cli.js" 5736 - }, 5737 - "engines": { 5738 - "node": ">=6" 5739 - } 5740 - }, 5741 - "node_modules/jsx-ast-utils": { 5742 - "version": "3.3.5", 5743 - "dev": true, 5744 - "license": "MIT", 5745 - "dependencies": { 5746 - "array-includes": "^3.1.6", 5747 - "array.prototype.flat": "^1.3.1", 5748 - "object.assign": "^4.1.4", 5749 - "object.values": "^1.1.6" 5750 - }, 5751 - "engines": { 5752 - "node": ">=4.0" 5753 - } 5754 - }, 5755 - "node_modules/keyv": { 5756 - "version": "4.5.4", 5757 - "dev": true, 5758 - "license": "MIT", 5759 - "dependencies": { 5760 - "json-buffer": "3.0.1" 5761 - } 5762 - }, 5763 - "node_modules/kleur": { 5764 - "version": "3.0.3", 5765 - "license": "MIT", 5766 - "engines": { 5767 - "node": ">=6" 5768 - } 5769 - }, 5770 - "node_modules/levn": { 5771 - "version": "0.4.1", 5772 - "dev": true, 5773 - "license": "MIT", 5774 - "dependencies": { 5775 - "prelude-ls": "^1.2.1", 5776 - "type-check": "~0.4.0" 5777 - }, 5778 - "engines": { 5779 - "node": ">= 0.8.0" 5780 - } 5781 - }, 5782 - "node_modules/lightningcss": { 5783 - "version": "1.30.2", 5784 - "license": "MPL-2.0", 5785 - "dependencies": { 5786 - "detect-libc": "^2.0.3" 5787 - }, 5788 - "engines": { 5789 - "node": ">= 12.0.0" 5790 - }, 5791 - "funding": { 5792 - "type": "opencollective", 5793 - "url": "https://opencollective.com/parcel" 5794 - }, 5795 - "optionalDependencies": { 5796 - "lightningcss-android-arm64": "1.30.2", 5797 - "lightningcss-darwin-arm64": "1.30.2", 5798 - "lightningcss-darwin-x64": "1.30.2", 5799 - "lightningcss-freebsd-x64": "1.30.2", 5800 - "lightningcss-linux-arm-gnueabihf": "1.30.2", 5801 - "lightningcss-linux-arm64-gnu": "1.30.2", 5802 - "lightningcss-linux-arm64-musl": "1.30.2", 5803 - "lightningcss-linux-x64-gnu": "1.30.2", 5804 - "lightningcss-linux-x64-musl": "1.30.2", 5805 - "lightningcss-win32-arm64-msvc": "1.30.2", 5806 - "lightningcss-win32-x64-msvc": "1.30.2" 5807 - } 5808 - }, 5809 - "node_modules/lightningcss-android-arm64": { 5810 - "version": "1.30.2", 5811 - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", 5812 - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", 5813 - "cpu": [ 5814 - "arm64" 5815 - ], 5816 - "license": "MPL-2.0", 5817 - "optional": true, 5818 - "os": [ 5819 - "android" 5820 - ], 5821 - "engines": { 5822 - "node": ">= 12.0.0" 5823 - }, 5824 - "funding": { 5825 - "type": "opencollective", 5826 - "url": "https://opencollective.com/parcel" 5827 - } 5828 - }, 5829 - "node_modules/lightningcss-darwin-arm64": { 5830 - "version": "1.30.2", 5831 - "cpu": [ 5832 - "arm64" 5833 - ], 5834 - "license": "MPL-2.0", 5835 - "optional": true, 5836 - "os": [ 5837 - "darwin" 5838 - ], 5839 - "engines": { 5840 - "node": ">= 12.0.0" 5841 - }, 5842 - "funding": { 5843 - "type": "opencollective", 5844 - "url": "https://opencollective.com/parcel" 5845 - } 5846 - }, 5847 - "node_modules/lightningcss-darwin-x64": { 5848 - "version": "1.30.2", 5849 - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", 5850 - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", 5851 - "cpu": [ 5852 - "x64" 5853 - ], 5854 - "license": "MPL-2.0", 5855 - "optional": true, 5856 - "os": [ 5857 - "darwin" 5858 - ], 5859 - "engines": { 5860 - "node": ">= 12.0.0" 5861 - }, 5862 - "funding": { 5863 - "type": "opencollective", 5864 - "url": "https://opencollective.com/parcel" 5865 - } 5866 - }, 5867 - "node_modules/lightningcss-freebsd-x64": { 5868 - "version": "1.30.2", 5869 - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", 5870 - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", 5871 - "cpu": [ 5872 - "x64" 5873 - ], 5874 - "license": "MPL-2.0", 5875 - "optional": true, 5876 - "os": [ 5877 - "freebsd" 5878 - ], 5879 - "engines": { 5880 - "node": ">= 12.0.0" 5881 - }, 5882 - "funding": { 5883 - "type": "opencollective", 5884 - "url": "https://opencollective.com/parcel" 5885 - } 5886 - }, 5887 - "node_modules/lightningcss-linux-arm-gnueabihf": { 5888 - "version": "1.30.2", 5889 - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", 5890 - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", 5891 - "cpu": [ 5892 - "arm" 5893 - ], 5894 - "license": "MPL-2.0", 5895 - "optional": true, 5896 - "os": [ 5897 - "linux" 5898 - ], 5899 - "engines": { 5900 - "node": ">= 12.0.0" 5901 - }, 5902 - "funding": { 5903 - "type": "opencollective", 5904 - "url": "https://opencollective.com/parcel" 5905 - } 5906 - }, 5907 - "node_modules/lightningcss-linux-arm64-gnu": { 5908 - "version": "1.30.2", 5909 - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", 5910 - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", 5911 - "cpu": [ 5912 - "arm64" 5913 - ], 5914 - "license": "MPL-2.0", 5915 - "optional": true, 5916 - "os": [ 5917 - "linux" 5918 - ], 5919 - "engines": { 5920 - "node": ">= 12.0.0" 5921 - }, 5922 - "funding": { 5923 - "type": "opencollective", 5924 - "url": "https://opencollective.com/parcel" 5925 - } 5926 - }, 5927 - "node_modules/lightningcss-linux-arm64-musl": { 5928 - "version": "1.30.2", 5929 - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", 5930 - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", 5931 - "cpu": [ 5932 - "arm64" 5933 - ], 5934 - "license": "MPL-2.0", 5935 - "optional": true, 5936 - "os": [ 5937 - "linux" 5938 - ], 5939 - "engines": { 5940 - "node": ">= 12.0.0" 5941 - }, 5942 - "funding": { 5943 - "type": "opencollective", 5944 - "url": "https://opencollective.com/parcel" 5945 - } 5946 - }, 5947 - "node_modules/lightningcss-linux-x64-gnu": { 5948 - "version": "1.30.2", 5949 - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", 5950 - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", 5951 - "cpu": [ 5952 - "x64" 5953 - ], 5954 - "license": "MPL-2.0", 5955 - "optional": true, 5956 - "os": [ 5957 - "linux" 5958 - ], 5959 - "engines": { 5960 - "node": ">= 12.0.0" 5961 - }, 5962 - "funding": { 5963 - "type": "opencollective", 5964 - "url": "https://opencollective.com/parcel" 5965 - } 5966 - }, 5967 - "node_modules/lightningcss-linux-x64-musl": { 5968 - "version": "1.30.2", 5969 - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", 5970 - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", 5971 - "cpu": [ 5972 - "x64" 5973 - ], 5974 - "license": "MPL-2.0", 5975 - "optional": true, 5976 - "os": [ 5977 - "linux" 5978 - ], 5979 - "engines": { 5980 - "node": ">= 12.0.0" 5981 - }, 5982 - "funding": { 5983 - "type": "opencollective", 5984 - "url": "https://opencollective.com/parcel" 5985 - } 5986 - }, 5987 - "node_modules/lightningcss-win32-arm64-msvc": { 5988 - "version": "1.30.2", 5989 - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", 5990 - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", 5991 - "cpu": [ 5992 - "arm64" 5993 - ], 5994 - "license": "MPL-2.0", 5995 - "optional": true, 5996 - "os": [ 5997 - "win32" 5998 - ], 5999 - "engines": { 6000 - "node": ">= 12.0.0" 6001 - }, 6002 - "funding": { 6003 - "type": "opencollective", 6004 - "url": "https://opencollective.com/parcel" 6005 - } 6006 - }, 6007 - "node_modules/lightningcss-win32-x64-msvc": { 6008 - "version": "1.30.2", 6009 - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", 6010 - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", 6011 - "cpu": [ 6012 - "x64" 6013 - ], 6014 - "license": "MPL-2.0", 6015 - "optional": true, 6016 - "os": [ 6017 - "win32" 6018 - ], 6019 - "engines": { 6020 - "node": ">= 12.0.0" 6021 - }, 6022 - "funding": { 6023 - "type": "opencollective", 6024 - "url": "https://opencollective.com/parcel" 6025 - } 6026 - }, 6027 - "node_modules/lilconfig": { 6028 - "version": "3.1.3", 6029 - "license": "MIT", 6030 - "engines": { 6031 - "node": ">=14" 6032 - }, 6033 - "funding": { 6034 - "url": "https://github.com/sponsors/antonk52" 6035 - } 6036 - }, 6037 - "node_modules/linebreak": { 6038 - "version": "1.1.0", 6039 - "license": "MIT", 6040 - "dependencies": { 6041 - "base64-js": "0.0.8", 6042 - "unicode-trie": "^2.0.0" 6043 - } 6044 - }, 6045 - "node_modules/lines-and-columns": { 6046 - "version": "1.2.4", 6047 - "license": "MIT" 6048 - }, 6049 - "node_modules/locate-path": { 6050 - "version": "6.0.0", 6051 - "dev": true, 6052 - "license": "MIT", 6053 - "dependencies": { 6054 - "p-locate": "^5.0.0" 6055 - }, 6056 - "engines": { 6057 - "node": ">=10" 6058 - }, 6059 - "funding": { 6060 - "url": "https://github.com/sponsors/sindresorhus" 6061 - } 6062 - }, 6063 - "node_modules/longest-streak": { 6064 - "version": "3.1.0", 6065 - "license": "MIT", 6066 - "funding": { 6067 - "type": "github", 6068 - "url": "https://github.com/sponsors/wooorm" 6069 - } 6070 - }, 6071 - "node_modules/loose-envify": { 6072 - "version": "1.4.0", 6073 - "dev": true, 6074 - "license": "MIT", 6075 - "dependencies": { 6076 - "js-tokens": "^3.0.0 || ^4.0.0" 6077 - }, 6078 - "bin": { 6079 - "loose-envify": "cli.js" 6080 - } 6081 - }, 6082 - "node_modules/lru-cache": { 6083 - "version": "11.2.5", 6084 - "license": "BlueOak-1.0.0", 6085 - "engines": { 6086 - "node": "20 || >=22" 6087 - } 6088 - }, 6089 - "node_modules/lucide-react": { 6090 - "version": "0.563.0", 6091 - "license": "ISC", 6092 - "peerDependencies": { 6093 - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" 6094 - } 6095 - }, 6096 - "node_modules/magic-string": { 6097 - "version": "0.30.21", 6098 - "license": "MIT", 6099 - "dependencies": { 6100 - "@jridgewell/sourcemap-codec": "^1.5.5" 6101 - } 6102 - }, 6103 - "node_modules/magicast": { 6104 - "version": "0.5.2", 6105 - "license": "MIT", 6106 - "dependencies": { 6107 - "@babel/parser": "^7.29.0", 6108 - "@babel/types": "^7.29.0", 6109 - "source-map-js": "^1.2.1" 6110 - } 6111 - }, 6112 - "node_modules/markdown-table": { 6113 - "version": "3.0.4", 6114 - "license": "MIT", 6115 - "funding": { 6116 - "type": "github", 6117 - "url": "https://github.com/sponsors/wooorm" 6118 - } 6119 - }, 6120 - "node_modules/math-intrinsics": { 6121 - "version": "1.1.0", 6122 - "dev": true, 6123 - "license": "MIT", 6124 - "engines": { 6125 - "node": ">= 0.4" 6126 - } 6127 - }, 6128 - "node_modules/mdast-util-definitions": { 6129 - "version": "6.0.0", 6130 - "license": "MIT", 6131 - "dependencies": { 6132 - "@types/mdast": "^4.0.0", 6133 - "@types/unist": "^3.0.0", 6134 - "unist-util-visit": "^5.0.0" 6135 - }, 6136 - "funding": { 6137 - "type": "opencollective", 6138 - "url": "https://opencollective.com/unified" 6139 - } 6140 - }, 6141 - "node_modules/mdast-util-find-and-replace": { 6142 - "version": "3.0.2", 6143 - "license": "MIT", 6144 - "dependencies": { 6145 - "@types/mdast": "^4.0.0", 6146 - "escape-string-regexp": "^5.0.0", 6147 - "unist-util-is": "^6.0.0", 6148 - "unist-util-visit-parents": "^6.0.0" 6149 - }, 6150 - "funding": { 6151 - "type": "opencollective", 6152 - "url": "https://opencollective.com/unified" 6153 - } 6154 - }, 6155 - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { 6156 - "version": "5.0.0", 6157 - "license": "MIT", 6158 - "engines": { 6159 - "node": ">=12" 6160 - }, 6161 - "funding": { 6162 - "url": "https://github.com/sponsors/sindresorhus" 6163 - } 6164 - }, 6165 - "node_modules/mdast-util-from-markdown": { 6166 - "version": "2.0.2", 6167 - "license": "MIT", 6168 - "dependencies": { 6169 - "@types/mdast": "^4.0.0", 6170 - "@types/unist": "^3.0.0", 6171 - "decode-named-character-reference": "^1.0.0", 6172 - "devlop": "^1.0.0", 6173 - "mdast-util-to-string": "^4.0.0", 6174 - "micromark": "^4.0.0", 6175 - "micromark-util-decode-numeric-character-reference": "^2.0.0", 6176 - "micromark-util-decode-string": "^2.0.0", 6177 - "micromark-util-normalize-identifier": "^2.0.0", 6178 - "micromark-util-symbol": "^2.0.0", 6179 - "micromark-util-types": "^2.0.0", 6180 - "unist-util-stringify-position": "^4.0.0" 6181 - }, 6182 - "funding": { 6183 - "type": "opencollective", 6184 - "url": "https://opencollective.com/unified" 6185 - } 6186 - }, 6187 - "node_modules/mdast-util-gfm": { 6188 - "version": "3.1.0", 6189 - "license": "MIT", 6190 - "dependencies": { 6191 - "mdast-util-from-markdown": "^2.0.0", 6192 - "mdast-util-gfm-autolink-literal": "^2.0.0", 6193 - "mdast-util-gfm-footnote": "^2.0.0", 6194 - "mdast-util-gfm-strikethrough": "^2.0.0", 6195 - "mdast-util-gfm-table": "^2.0.0", 6196 - "mdast-util-gfm-task-list-item": "^2.0.0", 6197 - "mdast-util-to-markdown": "^2.0.0" 6198 - }, 6199 - "funding": { 6200 - "type": "opencollective", 6201 - "url": "https://opencollective.com/unified" 6202 - } 6203 - }, 6204 - "node_modules/mdast-util-gfm-autolink-literal": { 6205 - "version": "2.0.1", 6206 - "license": "MIT", 6207 - "dependencies": { 6208 - "@types/mdast": "^4.0.0", 6209 - "ccount": "^2.0.0", 6210 - "devlop": "^1.0.0", 6211 - "mdast-util-find-and-replace": "^3.0.0", 6212 - "micromark-util-character": "^2.0.0" 6213 - }, 6214 - "funding": { 6215 - "type": "opencollective", 6216 - "url": "https://opencollective.com/unified" 6217 - } 6218 - }, 6219 - "node_modules/mdast-util-gfm-footnote": { 6220 - "version": "2.1.0", 6221 - "license": "MIT", 6222 - "dependencies": { 6223 - "@types/mdast": "^4.0.0", 6224 - "devlop": "^1.1.0", 6225 - "mdast-util-from-markdown": "^2.0.0", 6226 - "mdast-util-to-markdown": "^2.0.0", 6227 - "micromark-util-normalize-identifier": "^2.0.0" 6228 - }, 6229 - "funding": { 6230 - "type": "opencollective", 6231 - "url": "https://opencollective.com/unified" 6232 - } 6233 - }, 6234 - "node_modules/mdast-util-gfm-strikethrough": { 6235 - "version": "2.0.0", 6236 - "license": "MIT", 6237 - "dependencies": { 6238 - "@types/mdast": "^4.0.0", 6239 - "mdast-util-from-markdown": "^2.0.0", 6240 - "mdast-util-to-markdown": "^2.0.0" 6241 - }, 6242 - "funding": { 6243 - "type": "opencollective", 6244 - "url": "https://opencollective.com/unified" 6245 - } 6246 - }, 6247 - "node_modules/mdast-util-gfm-table": { 6248 - "version": "2.0.0", 6249 - "license": "MIT", 6250 - "dependencies": { 6251 - "@types/mdast": "^4.0.0", 6252 - "devlop": "^1.0.0", 6253 - "markdown-table": "^3.0.0", 6254 - "mdast-util-from-markdown": "^2.0.0", 6255 - "mdast-util-to-markdown": "^2.0.0" 6256 - }, 6257 - "funding": { 6258 - "type": "opencollective", 6259 - "url": "https://opencollective.com/unified" 6260 - } 6261 - }, 6262 - "node_modules/mdast-util-gfm-task-list-item": { 6263 - "version": "2.0.0", 6264 - "license": "MIT", 6265 - "dependencies": { 6266 - "@types/mdast": "^4.0.0", 6267 - "devlop": "^1.0.0", 6268 - "mdast-util-from-markdown": "^2.0.0", 6269 - "mdast-util-to-markdown": "^2.0.0" 6270 - }, 6271 - "funding": { 6272 - "type": "opencollective", 6273 - "url": "https://opencollective.com/unified" 6274 - } 6275 - }, 6276 - "node_modules/mdast-util-phrasing": { 6277 - "version": "4.1.0", 6278 - "license": "MIT", 6279 - "dependencies": { 6280 - "@types/mdast": "^4.0.0", 6281 - "unist-util-is": "^6.0.0" 6282 - }, 6283 - "funding": { 6284 - "type": "opencollective", 6285 - "url": "https://opencollective.com/unified" 6286 - } 6287 - }, 6288 - "node_modules/mdast-util-to-hast": { 6289 - "version": "13.2.1", 6290 - "license": "MIT", 6291 - "dependencies": { 6292 - "@types/hast": "^3.0.0", 6293 - "@types/mdast": "^4.0.0", 6294 - "@ungap/structured-clone": "^1.0.0", 6295 - "devlop": "^1.0.0", 6296 - "micromark-util-sanitize-uri": "^2.0.0", 6297 - "trim-lines": "^3.0.0", 6298 - "unist-util-position": "^5.0.0", 6299 - "unist-util-visit": "^5.0.0", 6300 - "vfile": "^6.0.0" 6301 - }, 6302 - "funding": { 6303 - "type": "opencollective", 6304 - "url": "https://opencollective.com/unified" 6305 - } 6306 - }, 6307 - "node_modules/mdast-util-to-markdown": { 6308 - "version": "2.1.2", 6309 - "license": "MIT", 6310 - "dependencies": { 6311 - "@types/mdast": "^4.0.0", 6312 - "@types/unist": "^3.0.0", 6313 - "longest-streak": "^3.0.0", 6314 - "mdast-util-phrasing": "^4.0.0", 6315 - "mdast-util-to-string": "^4.0.0", 6316 - "micromark-util-classify-character": "^2.0.0", 6317 - "micromark-util-decode-string": "^2.0.0", 6318 - "unist-util-visit": "^5.0.0", 6319 - "zwitch": "^2.0.0" 6320 - }, 6321 - "funding": { 6322 - "type": "opencollective", 6323 - "url": "https://opencollective.com/unified" 6324 - } 6325 - }, 6326 - "node_modules/mdast-util-to-string": { 6327 - "version": "4.0.0", 6328 - "license": "MIT", 6329 - "dependencies": { 6330 - "@types/mdast": "^4.0.0" 6331 - }, 6332 - "funding": { 6333 - "type": "opencollective", 6334 - "url": "https://opencollective.com/unified" 6335 - } 6336 - }, 6337 - "node_modules/mdn-data": { 6338 - "version": "2.12.2", 6339 - "license": "CC0-1.0" 6340 - }, 6341 - "node_modules/merge2": { 6342 - "version": "1.4.1", 6343 - "license": "MIT", 6344 - "engines": { 6345 - "node": ">= 8" 6346 - } 6347 - }, 6348 - "node_modules/micromark": { 6349 - "version": "4.0.2", 6350 - "funding": [ 6351 - { 6352 - "type": "GitHub Sponsors", 6353 - "url": "https://github.com/sponsors/unifiedjs" 6354 - }, 6355 - { 6356 - "type": "OpenCollective", 6357 - "url": "https://opencollective.com/unified" 6358 - } 6359 - ], 6360 - "license": "MIT", 6361 - "dependencies": { 6362 - "@types/debug": "^4.0.0", 6363 - "debug": "^4.0.0", 6364 - "decode-named-character-reference": "^1.0.0", 6365 - "devlop": "^1.0.0", 6366 - "micromark-core-commonmark": "^2.0.0", 6367 - "micromark-factory-space": "^2.0.0", 6368 - "micromark-util-character": "^2.0.0", 6369 - "micromark-util-chunked": "^2.0.0", 6370 - "micromark-util-combine-extensions": "^2.0.0", 6371 - "micromark-util-decode-numeric-character-reference": "^2.0.0", 6372 - "micromark-util-encode": "^2.0.0", 6373 - "micromark-util-normalize-identifier": "^2.0.0", 6374 - "micromark-util-resolve-all": "^2.0.0", 6375 - "micromark-util-sanitize-uri": "^2.0.0", 6376 - "micromark-util-subtokenize": "^2.0.0", 6377 - "micromark-util-symbol": "^2.0.0", 6378 - "micromark-util-types": "^2.0.0" 6379 - } 6380 - }, 6381 - "node_modules/micromark-core-commonmark": { 6382 - "version": "2.0.3", 6383 - "funding": [ 6384 - { 6385 - "type": "GitHub Sponsors", 6386 - "url": "https://github.com/sponsors/unifiedjs" 6387 - }, 6388 - { 6389 - "type": "OpenCollective", 6390 - "url": "https://opencollective.com/unified" 6391 - } 6392 - ], 6393 - "license": "MIT", 6394 - "dependencies": { 6395 - "decode-named-character-reference": "^1.0.0", 6396 - "devlop": "^1.0.0", 6397 - "micromark-factory-destination": "^2.0.0", 6398 - "micromark-factory-label": "^2.0.0", 6399 - "micromark-factory-space": "^2.0.0", 6400 - "micromark-factory-title": "^2.0.0", 6401 - "micromark-factory-whitespace": "^2.0.0", 6402 - "micromark-util-character": "^2.0.0", 6403 - "micromark-util-chunked": "^2.0.0", 6404 - "micromark-util-classify-character": "^2.0.0", 6405 - "micromark-util-html-tag-name": "^2.0.0", 6406 - "micromark-util-normalize-identifier": "^2.0.0", 6407 - "micromark-util-resolve-all": "^2.0.0", 6408 - "micromark-util-subtokenize": "^2.0.0", 6409 - "micromark-util-symbol": "^2.0.0", 6410 - "micromark-util-types": "^2.0.0" 6411 - } 6412 - }, 6413 - "node_modules/micromark-extension-gfm": { 6414 - "version": "3.0.0", 6415 - "license": "MIT", 6416 - "dependencies": { 6417 - "micromark-extension-gfm-autolink-literal": "^2.0.0", 6418 - "micromark-extension-gfm-footnote": "^2.0.0", 6419 - "micromark-extension-gfm-strikethrough": "^2.0.0", 6420 - "micromark-extension-gfm-table": "^2.0.0", 6421 - "micromark-extension-gfm-tagfilter": "^2.0.0", 6422 - "micromark-extension-gfm-task-list-item": "^2.0.0", 6423 - "micromark-util-combine-extensions": "^2.0.0", 6424 - "micromark-util-types": "^2.0.0" 6425 - }, 6426 - "funding": { 6427 - "type": "opencollective", 6428 - "url": "https://opencollective.com/unified" 6429 - } 6430 - }, 6431 - "node_modules/micromark-extension-gfm-autolink-literal": { 6432 - "version": "2.1.0", 6433 - "license": "MIT", 6434 - "dependencies": { 6435 - "micromark-util-character": "^2.0.0", 6436 - "micromark-util-sanitize-uri": "^2.0.0", 6437 - "micromark-util-symbol": "^2.0.0", 6438 - "micromark-util-types": "^2.0.0" 6439 - }, 6440 - "funding": { 6441 - "type": "opencollective", 6442 - "url": "https://opencollective.com/unified" 6443 - } 6444 - }, 6445 - "node_modules/micromark-extension-gfm-footnote": { 6446 - "version": "2.1.0", 6447 - "license": "MIT", 6448 - "dependencies": { 6449 - "devlop": "^1.0.0", 6450 - "micromark-core-commonmark": "^2.0.0", 6451 - "micromark-factory-space": "^2.0.0", 6452 - "micromark-util-character": "^2.0.0", 6453 - "micromark-util-normalize-identifier": "^2.0.0", 6454 - "micromark-util-sanitize-uri": "^2.0.0", 6455 - "micromark-util-symbol": "^2.0.0", 6456 - "micromark-util-types": "^2.0.0" 6457 - }, 6458 - "funding": { 6459 - "type": "opencollective", 6460 - "url": "https://opencollective.com/unified" 6461 - } 6462 - }, 6463 - "node_modules/micromark-extension-gfm-strikethrough": { 6464 - "version": "2.1.0", 6465 - "license": "MIT", 6466 - "dependencies": { 6467 - "devlop": "^1.0.0", 6468 - "micromark-util-chunked": "^2.0.0", 6469 - "micromark-util-classify-character": "^2.0.0", 6470 - "micromark-util-resolve-all": "^2.0.0", 6471 - "micromark-util-symbol": "^2.0.0", 6472 - "micromark-util-types": "^2.0.0" 6473 - }, 6474 - "funding": { 6475 - "type": "opencollective", 6476 - "url": "https://opencollective.com/unified" 6477 - } 6478 - }, 6479 - "node_modules/micromark-extension-gfm-table": { 6480 - "version": "2.1.1", 6481 - "license": "MIT", 6482 - "dependencies": { 6483 - "devlop": "^1.0.0", 6484 - "micromark-factory-space": "^2.0.0", 6485 - "micromark-util-character": "^2.0.0", 6486 - "micromark-util-symbol": "^2.0.0", 6487 - "micromark-util-types": "^2.0.0" 6488 - }, 6489 - "funding": { 6490 - "type": "opencollective", 6491 - "url": "https://opencollective.com/unified" 6492 - } 6493 - }, 6494 - "node_modules/micromark-extension-gfm-tagfilter": { 6495 - "version": "2.0.0", 6496 - "license": "MIT", 6497 - "dependencies": { 6498 - "micromark-util-types": "^2.0.0" 6499 - }, 6500 - "funding": { 6501 - "type": "opencollective", 6502 - "url": "https://opencollective.com/unified" 6503 - } 6504 - }, 6505 - "node_modules/micromark-extension-gfm-task-list-item": { 6506 - "version": "2.1.0", 6507 - "license": "MIT", 6508 - "dependencies": { 6509 - "devlop": "^1.0.0", 6510 - "micromark-factory-space": "^2.0.0", 6511 - "micromark-util-character": "^2.0.0", 6512 - "micromark-util-symbol": "^2.0.0", 6513 - "micromark-util-types": "^2.0.0" 6514 - }, 6515 - "funding": { 6516 - "type": "opencollective", 6517 - "url": "https://opencollective.com/unified" 6518 - } 6519 - }, 6520 - "node_modules/micromark-factory-destination": { 6521 - "version": "2.0.1", 6522 - "funding": [ 6523 - { 6524 - "type": "GitHub Sponsors", 6525 - "url": "https://github.com/sponsors/unifiedjs" 6526 - }, 6527 - { 6528 - "type": "OpenCollective", 6529 - "url": "https://opencollective.com/unified" 6530 - } 6531 - ], 6532 - "license": "MIT", 6533 - "dependencies": { 6534 - "micromark-util-character": "^2.0.0", 6535 - "micromark-util-symbol": "^2.0.0", 6536 - "micromark-util-types": "^2.0.0" 6537 - } 6538 - }, 6539 - "node_modules/micromark-factory-label": { 6540 - "version": "2.0.1", 6541 - "funding": [ 6542 - { 6543 - "type": "GitHub Sponsors", 6544 - "url": "https://github.com/sponsors/unifiedjs" 6545 - }, 6546 - { 6547 - "type": "OpenCollective", 6548 - "url": "https://opencollective.com/unified" 6549 - } 6550 - ], 6551 - "license": "MIT", 6552 - "dependencies": { 6553 - "devlop": "^1.0.0", 6554 - "micromark-util-character": "^2.0.0", 6555 - "micromark-util-symbol": "^2.0.0", 6556 - "micromark-util-types": "^2.0.0" 6557 - } 6558 - }, 6559 - "node_modules/micromark-factory-space": { 6560 - "version": "2.0.1", 6561 - "funding": [ 6562 - { 6563 - "type": "GitHub Sponsors", 6564 - "url": "https://github.com/sponsors/unifiedjs" 6565 - }, 6566 - { 6567 - "type": "OpenCollective", 6568 - "url": "https://opencollective.com/unified" 6569 - } 6570 - ], 6571 - "license": "MIT", 6572 - "dependencies": { 6573 - "micromark-util-character": "^2.0.0", 6574 - "micromark-util-types": "^2.0.0" 6575 - } 6576 - }, 6577 - "node_modules/micromark-factory-title": { 6578 - "version": "2.0.1", 6579 - "funding": [ 6580 - { 6581 - "type": "GitHub Sponsors", 6582 - "url": "https://github.com/sponsors/unifiedjs" 6583 - }, 6584 - { 6585 - "type": "OpenCollective", 6586 - "url": "https://opencollective.com/unified" 6587 - } 6588 - ], 6589 - "license": "MIT", 6590 - "dependencies": { 6591 - "micromark-factory-space": "^2.0.0", 6592 - "micromark-util-character": "^2.0.0", 6593 - "micromark-util-symbol": "^2.0.0", 6594 - "micromark-util-types": "^2.0.0" 6595 - } 6596 - }, 6597 - "node_modules/micromark-factory-whitespace": { 6598 - "version": "2.0.1", 6599 - "funding": [ 6600 - { 6601 - "type": "GitHub Sponsors", 6602 - "url": "https://github.com/sponsors/unifiedjs" 6603 - }, 6604 - { 6605 - "type": "OpenCollective", 6606 - "url": "https://opencollective.com/unified" 6607 - } 6608 - ], 6609 - "license": "MIT", 6610 - "dependencies": { 6611 - "micromark-factory-space": "^2.0.0", 6612 - "micromark-util-character": "^2.0.0", 6613 - "micromark-util-symbol": "^2.0.0", 6614 - "micromark-util-types": "^2.0.0" 6615 - } 6616 - }, 6617 - "node_modules/micromark-util-character": { 6618 - "version": "2.1.1", 6619 - "funding": [ 6620 - { 6621 - "type": "GitHub Sponsors", 6622 - "url": "https://github.com/sponsors/unifiedjs" 6623 - }, 6624 - { 6625 - "type": "OpenCollective", 6626 - "url": "https://opencollective.com/unified" 6627 - } 6628 - ], 6629 - "license": "MIT", 6630 - "dependencies": { 6631 - "micromark-util-symbol": "^2.0.0", 6632 - "micromark-util-types": "^2.0.0" 6633 - } 6634 - }, 6635 - "node_modules/micromark-util-chunked": { 6636 - "version": "2.0.1", 6637 - "funding": [ 6638 - { 6639 - "type": "GitHub Sponsors", 6640 - "url": "https://github.com/sponsors/unifiedjs" 6641 - }, 6642 - { 6643 - "type": "OpenCollective", 6644 - "url": "https://opencollective.com/unified" 6645 - } 6646 - ], 6647 - "license": "MIT", 6648 - "dependencies": { 6649 - "micromark-util-symbol": "^2.0.0" 6650 - } 6651 - }, 6652 - "node_modules/micromark-util-classify-character": { 6653 - "version": "2.0.1", 6654 - "funding": [ 6655 - { 6656 - "type": "GitHub Sponsors", 6657 - "url": "https://github.com/sponsors/unifiedjs" 6658 - }, 6659 - { 6660 - "type": "OpenCollective", 6661 - "url": "https://opencollective.com/unified" 6662 - } 6663 - ], 6664 - "license": "MIT", 6665 - "dependencies": { 6666 - "micromark-util-character": "^2.0.0", 6667 - "micromark-util-symbol": "^2.0.0", 6668 - "micromark-util-types": "^2.0.0" 6669 - } 6670 - }, 6671 - "node_modules/micromark-util-combine-extensions": { 6672 - "version": "2.0.1", 6673 - "funding": [ 6674 - { 6675 - "type": "GitHub Sponsors", 6676 - "url": "https://github.com/sponsors/unifiedjs" 6677 - }, 6678 - { 6679 - "type": "OpenCollective", 6680 - "url": "https://opencollective.com/unified" 6681 - } 6682 - ], 6683 - "license": "MIT", 6684 - "dependencies": { 6685 - "micromark-util-chunked": "^2.0.0", 6686 - "micromark-util-types": "^2.0.0" 6687 - } 6688 - }, 6689 - "node_modules/micromark-util-decode-numeric-character-reference": { 6690 - "version": "2.0.2", 6691 - "funding": [ 6692 - { 6693 - "type": "GitHub Sponsors", 6694 - "url": "https://github.com/sponsors/unifiedjs" 6695 - }, 6696 - { 6697 - "type": "OpenCollective", 6698 - "url": "https://opencollective.com/unified" 6699 - } 6700 - ], 6701 - "license": "MIT", 6702 - "dependencies": { 6703 - "micromark-util-symbol": "^2.0.0" 6704 - } 6705 - }, 6706 - "node_modules/micromark-util-decode-string": { 6707 - "version": "2.0.1", 6708 - "funding": [ 6709 - { 6710 - "type": "GitHub Sponsors", 6711 - "url": "https://github.com/sponsors/unifiedjs" 6712 - }, 6713 - { 6714 - "type": "OpenCollective", 6715 - "url": "https://opencollective.com/unified" 6716 - } 6717 - ], 6718 - "license": "MIT", 6719 - "dependencies": { 6720 - "decode-named-character-reference": "^1.0.0", 6721 - "micromark-util-character": "^2.0.0", 6722 - "micromark-util-decode-numeric-character-reference": "^2.0.0", 6723 - "micromark-util-symbol": "^2.0.0" 6724 - } 6725 - }, 6726 - "node_modules/micromark-util-encode": { 6727 - "version": "2.0.1", 6728 - "funding": [ 6729 - { 6730 - "type": "GitHub Sponsors", 6731 - "url": "https://github.com/sponsors/unifiedjs" 6732 - }, 6733 - { 6734 - "type": "OpenCollective", 6735 - "url": "https://opencollective.com/unified" 6736 - } 6737 - ], 6738 - "license": "MIT" 6739 - }, 6740 - "node_modules/micromark-util-html-tag-name": { 6741 - "version": "2.0.1", 6742 - "funding": [ 6743 - { 6744 - "type": "GitHub Sponsors", 6745 - "url": "https://github.com/sponsors/unifiedjs" 6746 - }, 6747 - { 6748 - "type": "OpenCollective", 6749 - "url": "https://opencollective.com/unified" 6750 - } 6751 - ], 6752 - "license": "MIT" 6753 - }, 6754 - "node_modules/micromark-util-normalize-identifier": { 6755 - "version": "2.0.1", 6756 - "funding": [ 6757 - { 6758 - "type": "GitHub Sponsors", 6759 - "url": "https://github.com/sponsors/unifiedjs" 6760 - }, 6761 - { 6762 - "type": "OpenCollective", 6763 - "url": "https://opencollective.com/unified" 6764 - } 6765 - ], 6766 - "license": "MIT", 6767 - "dependencies": { 6768 - "micromark-util-symbol": "^2.0.0" 6769 - } 6770 - }, 6771 - "node_modules/micromark-util-resolve-all": { 6772 - "version": "2.0.1", 6773 - "funding": [ 6774 - { 6775 - "type": "GitHub Sponsors", 6776 - "url": "https://github.com/sponsors/unifiedjs" 6777 - }, 6778 - { 6779 - "type": "OpenCollective", 6780 - "url": "https://opencollective.com/unified" 6781 - } 6782 - ], 6783 - "license": "MIT", 6784 - "dependencies": { 6785 - "micromark-util-types": "^2.0.0" 6786 - } 6787 - }, 6788 - "node_modules/micromark-util-sanitize-uri": { 6789 - "version": "2.0.1", 6790 - "funding": [ 6791 - { 6792 - "type": "GitHub Sponsors", 6793 - "url": "https://github.com/sponsors/unifiedjs" 6794 - }, 6795 - { 6796 - "type": "OpenCollective", 6797 - "url": "https://opencollective.com/unified" 6798 - } 6799 - ], 6800 - "license": "MIT", 6801 - "dependencies": { 6802 - "micromark-util-character": "^2.0.0", 6803 - "micromark-util-encode": "^2.0.0", 6804 - "micromark-util-symbol": "^2.0.0" 6805 - } 6806 - }, 6807 - "node_modules/micromark-util-subtokenize": { 6808 - "version": "2.1.0", 6809 - "funding": [ 6810 - { 6811 - "type": "GitHub Sponsors", 6812 - "url": "https://github.com/sponsors/unifiedjs" 6813 - }, 6814 - { 6815 - "type": "OpenCollective", 6816 - "url": "https://opencollective.com/unified" 6817 - } 6818 - ], 6819 - "license": "MIT", 6820 - "dependencies": { 6821 - "devlop": "^1.0.0", 6822 - "micromark-util-chunked": "^2.0.0", 6823 - "micromark-util-symbol": "^2.0.0", 6824 - "micromark-util-types": "^2.0.0" 6825 - } 6826 - }, 6827 - "node_modules/micromark-util-symbol": { 6828 - "version": "2.0.1", 6829 - "funding": [ 6830 - { 6831 - "type": "GitHub Sponsors", 6832 - "url": "https://github.com/sponsors/unifiedjs" 6833 - }, 6834 - { 6835 - "type": "OpenCollective", 6836 - "url": "https://opencollective.com/unified" 6837 - } 6838 - ], 6839 - "license": "MIT" 6840 - }, 6841 - "node_modules/micromark-util-types": { 6842 - "version": "2.0.2", 6843 - "funding": [ 6844 - { 6845 - "type": "GitHub Sponsors", 6846 - "url": "https://github.com/sponsors/unifiedjs" 6847 - }, 6848 - { 6849 - "type": "OpenCollective", 6850 - "url": "https://opencollective.com/unified" 6851 - } 6852 - ], 6853 - "license": "MIT" 6854 - }, 6855 - "node_modules/micromatch": { 6856 - "version": "4.0.8", 6857 - "license": "MIT", 6858 - "dependencies": { 6859 - "braces": "^3.0.3", 6860 - "picomatch": "^2.3.1" 6861 - }, 6862 - "engines": { 6863 - "node": ">=8.6" 6864 - } 6865 - }, 6866 - "node_modules/micromatch/node_modules/picomatch": { 6867 - "version": "2.3.1", 6868 - "license": "MIT", 6869 - "engines": { 6870 - "node": ">=8.6" 6871 - }, 6872 - "funding": { 6873 - "url": "https://github.com/sponsors/jonschlinkert" 6874 - } 6875 - }, 6876 - "node_modules/mime-db": { 6877 - "version": "1.54.0", 6878 - "license": "MIT", 6879 - "engines": { 6880 - "node": ">= 0.6" 6881 - } 6882 - }, 6883 - "node_modules/mime-types": { 6884 - "version": "3.0.2", 6885 - "license": "MIT", 6886 - "dependencies": { 6887 - "mime-db": "^1.54.0" 6888 - }, 6889 - "engines": { 6890 - "node": ">=18" 6891 - }, 6892 - "funding": { 6893 - "type": "opencollective", 6894 - "url": "https://opencollective.com/express" 6895 - } 6896 - }, 6897 - "node_modules/minimatch": { 6898 - "version": "10.1.2", 6899 - "dev": true, 6900 - "license": "BlueOak-1.0.0", 6901 - "dependencies": { 6902 - "@isaacs/brace-expansion": "^5.0.1" 6903 - }, 6904 - "engines": { 6905 - "node": "20 || >=22" 6906 - }, 6907 - "funding": { 6908 - "url": "https://github.com/sponsors/isaacs" 6909 - } 6910 - }, 6911 - "node_modules/mrmime": { 6912 - "version": "2.0.1", 6913 - "license": "MIT", 6914 - "engines": { 6915 - "node": ">=10" 6916 - } 6917 - }, 6918 - "node_modules/ms": { 6919 - "version": "2.1.3", 6920 - "license": "MIT" 6921 - }, 6922 - "node_modules/mz": { 6923 - "version": "2.7.0", 6924 - "license": "MIT", 6925 - "dependencies": { 6926 - "any-promise": "^1.0.0", 6927 - "object-assign": "^4.0.1", 6928 - "thenify-all": "^1.0.0" 6929 - } 6930 - }, 6931 - "node_modules/nanoid": { 6932 - "version": "3.3.11", 6933 - "funding": [ 6934 - { 6935 - "type": "github", 6936 - "url": "https://github.com/sponsors/ai" 6937 - } 6938 - ], 6939 - "license": "MIT", 6940 - "bin": { 6941 - "nanoid": "bin/nanoid.cjs" 6942 - }, 6943 - "engines": { 6944 - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 6945 - } 6946 - }, 6947 - "node_modules/nanostores": { 6948 - "version": "1.1.0", 6949 - "funding": [ 6950 - { 6951 - "type": "github", 6952 - "url": "https://github.com/sponsors/ai" 6953 - } 6954 - ], 6955 - "license": "MIT", 6956 - "engines": { 6957 - "node": "^20.0.0 || >=22.0.0" 6958 - } 6959 - }, 6960 - "node_modules/natural-compare": { 6961 - "version": "1.4.0", 6962 - "dev": true, 6963 - "license": "MIT" 6964 - }, 6965 - "node_modules/neotraverse": { 6966 - "version": "0.6.18", 6967 - "license": "MIT", 6968 - "engines": { 6969 - "node": ">= 10" 6970 - } 6971 - }, 6972 - "node_modules/nlcst-to-string": { 6973 - "version": "4.0.0", 6974 - "license": "MIT", 6975 - "dependencies": { 6976 - "@types/nlcst": "^2.0.0" 6977 - }, 6978 - "funding": { 6979 - "type": "opencollective", 6980 - "url": "https://opencollective.com/unified" 6981 - } 6982 - }, 6983 - "node_modules/node-fetch-native": { 6984 - "version": "1.6.7", 6985 - "license": "MIT" 6986 - }, 6987 - "node_modules/node-mock-http": { 6988 - "version": "1.0.4", 6989 - "license": "MIT" 6990 - }, 6991 - "node_modules/node-releases": { 6992 - "version": "2.0.27", 6993 - "license": "MIT" 6994 - }, 6995 - "node_modules/normalize-path": { 6996 - "version": "3.0.0", 6997 - "license": "MIT", 6998 - "engines": { 6999 - "node": ">=0.10.0" 7000 - } 7001 - }, 7002 - "node_modules/nth-check": { 7003 - "version": "2.1.1", 7004 - "license": "BSD-2-Clause", 7005 - "dependencies": { 7006 - "boolbase": "^1.0.0" 7007 - }, 7008 - "funding": { 7009 - "url": "https://github.com/fb55/nth-check?sponsor=1" 7010 - } 7011 - }, 7012 - "node_modules/object-assign": { 7013 - "version": "4.1.1", 7014 - "license": "MIT", 7015 - "engines": { 7016 - "node": ">=0.10.0" 7017 - } 7018 - }, 7019 - "node_modules/object-hash": { 7020 - "version": "3.0.0", 7021 - "license": "MIT", 7022 - "engines": { 7023 - "node": ">= 6" 7024 - } 7025 - }, 7026 - "node_modules/object-inspect": { 7027 - "version": "1.13.4", 7028 - "dev": true, 7029 - "license": "MIT", 7030 - "engines": { 7031 - "node": ">= 0.4" 7032 - }, 7033 - "funding": { 7034 - "url": "https://github.com/sponsors/ljharb" 7035 - } 7036 - }, 7037 - "node_modules/object-keys": { 7038 - "version": "1.1.1", 7039 - "dev": true, 7040 - "license": "MIT", 7041 - "engines": { 7042 - "node": ">= 0.4" 7043 - } 7044 - }, 7045 - "node_modules/object.assign": { 7046 - "version": "4.1.7", 7047 - "dev": true, 7048 - "license": "MIT", 7049 - "dependencies": { 7050 - "call-bind": "^1.0.8", 7051 - "call-bound": "^1.0.3", 7052 - "define-properties": "^1.2.1", 7053 - "es-object-atoms": "^1.0.0", 7054 - "has-symbols": "^1.1.0", 7055 - "object-keys": "^1.1.1" 7056 - }, 7057 - "engines": { 7058 - "node": ">= 0.4" 7059 - }, 7060 - "funding": { 7061 - "url": "https://github.com/sponsors/ljharb" 7062 - } 7063 - }, 7064 - "node_modules/object.entries": { 7065 - "version": "1.1.9", 7066 - "dev": true, 7067 - "license": "MIT", 7068 - "dependencies": { 7069 - "call-bind": "^1.0.8", 7070 - "call-bound": "^1.0.4", 7071 - "define-properties": "^1.2.1", 7072 - "es-object-atoms": "^1.1.1" 7073 - }, 7074 - "engines": { 7075 - "node": ">= 0.4" 7076 - } 7077 - }, 7078 - "node_modules/object.fromentries": { 7079 - "version": "2.0.8", 7080 - "dev": true, 7081 - "license": "MIT", 7082 - "dependencies": { 7083 - "call-bind": "^1.0.7", 7084 - "define-properties": "^1.2.1", 7085 - "es-abstract": "^1.23.2", 7086 - "es-object-atoms": "^1.0.0" 7087 - }, 7088 - "engines": { 7089 - "node": ">= 0.4" 7090 - }, 7091 - "funding": { 7092 - "url": "https://github.com/sponsors/ljharb" 7093 - } 7094 - }, 7095 - "node_modules/object.values": { 7096 - "version": "1.2.1", 7097 - "dev": true, 7098 - "license": "MIT", 7099 - "dependencies": { 7100 - "call-bind": "^1.0.8", 7101 - "call-bound": "^1.0.3", 7102 - "define-properties": "^1.2.1", 7103 - "es-object-atoms": "^1.0.0" 7104 - }, 7105 - "engines": { 7106 - "node": ">= 0.4" 7107 - }, 7108 - "funding": { 7109 - "url": "https://github.com/sponsors/ljharb" 7110 - } 7111 - }, 7112 - "node_modules/ofetch": { 7113 - "version": "1.5.1", 7114 - "license": "MIT", 7115 - "dependencies": { 7116 - "destr": "^2.0.5", 7117 - "node-fetch-native": "^1.6.7", 7118 - "ufo": "^1.6.1" 7119 - } 7120 - }, 7121 - "node_modules/ohash": { 7122 - "version": "2.0.11", 7123 - "license": "MIT" 7124 - }, 7125 - "node_modules/on-finished": { 7126 - "version": "2.4.1", 7127 - "license": "MIT", 7128 - "dependencies": { 7129 - "ee-first": "1.1.1" 7130 - }, 7131 - "engines": { 7132 - "node": ">= 0.8" 7133 - } 7134 - }, 7135 - "node_modules/oniguruma-parser": { 7136 - "version": "0.12.1", 7137 - "license": "MIT" 7138 - }, 7139 - "node_modules/oniguruma-to-es": { 7140 - "version": "4.3.4", 7141 - "license": "MIT", 7142 - "dependencies": { 7143 - "oniguruma-parser": "^0.12.1", 7144 - "regex": "^6.0.1", 7145 - "regex-recursion": "^6.0.2" 7146 - } 7147 - }, 7148 - "node_modules/optionator": { 7149 - "version": "0.9.4", 7150 - "dev": true, 7151 - "license": "MIT", 7152 - "dependencies": { 7153 - "deep-is": "^0.1.3", 7154 - "fast-levenshtein": "^2.0.6", 7155 - "levn": "^0.4.1", 7156 - "prelude-ls": "^1.2.1", 7157 - "type-check": "^0.4.0", 7158 - "word-wrap": "^1.2.5" 7159 - }, 7160 - "engines": { 7161 - "node": ">= 0.8.0" 7162 - } 7163 - }, 7164 - "node_modules/own-keys": { 7165 - "version": "1.0.1", 7166 - "dev": true, 7167 - "license": "MIT", 7168 - "dependencies": { 7169 - "get-intrinsic": "^1.2.6", 7170 - "object-keys": "^1.1.1", 7171 - "safe-push-apply": "^1.0.0" 7172 - }, 7173 - "engines": { 7174 - "node": ">= 0.4" 7175 - }, 7176 - "funding": { 7177 - "url": "https://github.com/sponsors/ljharb" 7178 - } 7179 - }, 7180 - "node_modules/p-limit": { 7181 - "version": "6.2.0", 7182 - "license": "MIT", 7183 - "dependencies": { 7184 - "yocto-queue": "^1.1.1" 7185 - }, 7186 - "engines": { 7187 - "node": ">=18" 7188 - }, 7189 - "funding": { 7190 - "url": "https://github.com/sponsors/sindresorhus" 7191 - } 7192 - }, 7193 - "node_modules/p-locate": { 7194 - "version": "5.0.0", 7195 - "dev": true, 7196 - "license": "MIT", 7197 - "dependencies": { 7198 - "p-limit": "^3.0.2" 7199 - }, 7200 - "engines": { 7201 - "node": ">=10" 7202 - }, 7203 - "funding": { 7204 - "url": "https://github.com/sponsors/sindresorhus" 7205 - } 7206 - }, 7207 - "node_modules/p-locate/node_modules/p-limit": { 7208 - "version": "3.1.0", 7209 - "dev": true, 7210 - "license": "MIT", 7211 - "dependencies": { 7212 - "yocto-queue": "^0.1.0" 7213 - }, 7214 - "engines": { 7215 - "node": ">=10" 7216 - }, 7217 - "funding": { 7218 - "url": "https://github.com/sponsors/sindresorhus" 7219 - } 7220 - }, 7221 - "node_modules/p-locate/node_modules/p-limit/node_modules/yocto-queue": { 7222 - "version": "0.1.0", 7223 - "dev": true, 7224 - "license": "MIT", 7225 - "engines": { 7226 - "node": ">=10" 7227 - }, 7228 - "funding": { 7229 - "url": "https://github.com/sponsors/sindresorhus" 7230 - } 7231 - }, 7232 - "node_modules/p-queue": { 7233 - "version": "8.1.1", 7234 - "license": "MIT", 7235 - "dependencies": { 7236 - "eventemitter3": "^5.0.1", 7237 - "p-timeout": "^6.1.2" 7238 - }, 7239 - "engines": { 7240 - "node": ">=18" 7241 - }, 7242 - "funding": { 7243 - "url": "https://github.com/sponsors/sindresorhus" 7244 - } 7245 - }, 7246 - "node_modules/p-timeout": { 7247 - "version": "6.1.4", 7248 - "license": "MIT", 7249 - "engines": { 7250 - "node": ">=14.16" 7251 - }, 7252 - "funding": { 7253 - "url": "https://github.com/sponsors/sindresorhus" 7254 - } 7255 - }, 7256 - "node_modules/package-manager-detector": { 7257 - "version": "1.6.0", 7258 - "license": "MIT" 7259 - }, 7260 - "node_modules/pako": { 7261 - "version": "0.2.9", 7262 - "license": "MIT" 7263 - }, 7264 - "node_modules/parse-css-color": { 7265 - "version": "0.2.1", 7266 - "license": "MIT", 7267 - "dependencies": { 7268 - "color-name": "^1.1.4", 7269 - "hex-rgb": "^4.1.0" 7270 - } 7271 - }, 7272 - "node_modules/parse-latin": { 7273 - "version": "7.0.0", 7274 - "license": "MIT", 7275 - "dependencies": { 7276 - "@types/nlcst": "^2.0.0", 7277 - "@types/unist": "^3.0.0", 7278 - "nlcst-to-string": "^4.0.0", 7279 - "unist-util-modify-children": "^4.0.0", 7280 - "unist-util-visit-children": "^3.0.0", 7281 - "vfile": "^6.0.0" 7282 - }, 7283 - "funding": { 7284 - "type": "github", 7285 - "url": "https://github.com/sponsors/wooorm" 7286 - } 7287 - }, 7288 - "node_modules/parse5": { 7289 - "version": "7.3.0", 7290 - "license": "MIT", 7291 - "dependencies": { 7292 - "entities": "^6.0.0" 7293 - }, 7294 - "funding": { 7295 - "url": "https://github.com/inikulin/parse5?sponsor=1" 7296 - } 7297 - }, 7298 - "node_modules/path-exists": { 7299 - "version": "4.0.0", 7300 - "dev": true, 7301 - "license": "MIT", 7302 - "engines": { 7303 - "node": ">=8" 7304 - } 7305 - }, 7306 - "node_modules/path-key": { 7307 - "version": "3.1.1", 7308 - "dev": true, 7309 - "license": "MIT", 7310 - "engines": { 7311 - "node": ">=8" 7312 - } 7313 - }, 7314 - "node_modules/path-parse": { 7315 - "version": "1.0.7", 7316 - "license": "MIT" 7317 - }, 7318 - "node_modules/piccolore": { 7319 - "version": "0.1.3", 7320 - "license": "ISC" 7321 - }, 7322 - "node_modules/picocolors": { 7323 - "version": "1.1.1", 7324 - "license": "ISC" 7325 - }, 7326 - "node_modules/picomatch": { 7327 - "version": "4.0.3", 7328 - "license": "MIT", 7329 - "engines": { 7330 - "node": ">=12" 7331 - }, 7332 - "funding": { 7333 - "url": "https://github.com/sponsors/jonschlinkert" 7334 - } 7335 - }, 7336 - "node_modules/pify": { 7337 - "version": "2.3.0", 7338 - "license": "MIT", 7339 - "engines": { 7340 - "node": ">=0.10.0" 7341 - } 7342 - }, 7343 - "node_modules/pirates": { 7344 - "version": "4.0.7", 7345 - "license": "MIT", 7346 - "engines": { 7347 - "node": ">= 6" 7348 - } 7349 - }, 7350 - "node_modules/possible-typed-array-names": { 7351 - "version": "1.1.0", 7352 - "dev": true, 7353 - "license": "MIT", 7354 - "engines": { 7355 - "node": ">= 0.4" 7356 - } 7357 - }, 7358 - "node_modules/postcss": { 7359 - "version": "8.5.6", 7360 - "funding": [ 7361 - { 7362 - "type": "opencollective", 7363 - "url": "https://opencollective.com/postcss/" 7364 - }, 7365 - { 7366 - "type": "tidelift", 7367 - "url": "https://tidelift.com/funding/github/npm/postcss" 7368 - }, 7369 - { 7370 - "type": "github", 7371 - "url": "https://github.com/sponsors/ai" 7372 - } 7373 - ], 7374 - "license": "MIT", 7375 - "dependencies": { 7376 - "nanoid": "^3.3.11", 7377 - "picocolors": "^1.1.1", 7378 - "source-map-js": "^1.2.1" 7379 - }, 7380 - "engines": { 7381 - "node": "^10 || ^12 || >=14" 7382 - } 7383 - }, 7384 - "node_modules/postcss-import": { 7385 - "version": "15.1.0", 7386 - "license": "MIT", 7387 - "dependencies": { 7388 - "postcss-value-parser": "^4.0.0", 7389 - "read-cache": "^1.0.0", 7390 - "resolve": "^1.1.7" 7391 - }, 7392 - "engines": { 7393 - "node": ">=14.0.0" 7394 - }, 7395 - "peerDependencies": { 7396 - "postcss": "^8.0.0" 7397 - } 7398 - }, 7399 - "node_modules/postcss-import/node_modules/resolve": { 7400 - "version": "1.22.11", 7401 - "license": "MIT", 7402 - "dependencies": { 7403 - "is-core-module": "^2.16.1", 7404 - "path-parse": "^1.0.7", 7405 - "supports-preserve-symlinks-flag": "^1.0.0" 7406 - }, 7407 - "bin": { 7408 - "resolve": "bin/resolve" 7409 - }, 7410 - "engines": { 7411 - "node": ">= 0.4" 7412 - }, 7413 - "funding": { 7414 - "url": "https://github.com/sponsors/ljharb" 7415 - } 7416 - }, 7417 - "node_modules/postcss-js": { 7418 - "version": "4.1.0", 7419 - "funding": [ 7420 - { 7421 - "type": "opencollective", 7422 - "url": "https://opencollective.com/postcss/" 7423 - }, 7424 - { 7425 - "type": "github", 7426 - "url": "https://github.com/sponsors/ai" 7427 - } 7428 - ], 7429 - "license": "MIT", 7430 - "dependencies": { 7431 - "camelcase-css": "^2.0.1" 7432 - }, 7433 - "engines": { 7434 - "node": "^12 || ^14 || >= 16" 7435 - }, 7436 - "peerDependencies": { 7437 - "postcss": "^8.4.21" 7438 - } 7439 - }, 7440 - "node_modules/postcss-load-config": { 7441 - "version": "4.0.2", 7442 - "funding": [ 7443 - { 7444 - "type": "opencollective", 7445 - "url": "https://opencollective.com/postcss/" 7446 - }, 7447 - { 7448 - "type": "github", 7449 - "url": "https://github.com/sponsors/ai" 7450 - } 7451 - ], 7452 - "license": "MIT", 7453 - "dependencies": { 7454 - "lilconfig": "^3.0.0", 7455 - "yaml": "^2.3.4" 7456 - }, 7457 - "engines": { 7458 - "node": ">= 14" 7459 - }, 7460 - "peerDependencies": { 7461 - "postcss": ">=8.0.9", 7462 - "ts-node": ">=9.0.0" 7463 - }, 7464 - "peerDependenciesMeta": { 7465 - "postcss": { 7466 - "optional": true 7467 - }, 7468 - "ts-node": { 7469 - "optional": true 7470 - } 7471 - } 7472 - }, 7473 - "node_modules/postcss-nested": { 7474 - "version": "6.2.0", 7475 - "funding": [ 7476 - { 7477 - "type": "opencollective", 7478 - "url": "https://opencollective.com/postcss/" 7479 - }, 7480 - { 7481 - "type": "github", 7482 - "url": "https://github.com/sponsors/ai" 7483 - } 7484 - ], 7485 - "license": "MIT", 7486 - "dependencies": { 7487 - "postcss-selector-parser": "^6.1.1" 7488 - }, 7489 - "engines": { 7490 - "node": ">=12.0" 7491 - }, 7492 - "peerDependencies": { 7493 - "postcss": "^8.2.14" 7494 - } 7495 - }, 7496 - "node_modules/postcss-selector-parser": { 7497 - "version": "6.1.2", 7498 - "license": "MIT", 7499 - "dependencies": { 7500 - "cssesc": "^3.0.0", 7501 - "util-deprecate": "^1.0.2" 7502 - }, 7503 - "engines": { 7504 - "node": ">=4" 7505 - } 7506 - }, 7507 - "node_modules/postcss-value-parser": { 7508 - "version": "4.2.0", 7509 - "license": "MIT" 7510 - }, 7511 - "node_modules/prelude-ls": { 7512 - "version": "1.2.1", 7513 - "dev": true, 7514 - "license": "MIT", 7515 - "engines": { 7516 - "node": ">= 0.8.0" 7517 - } 7518 - }, 7519 - "node_modules/prettier": { 7520 - "version": "3.8.1", 7521 - "dev": true, 7522 - "license": "MIT", 7523 - "bin": { 7524 - "prettier": "bin/prettier.cjs" 7525 - }, 7526 - "engines": { 7527 - "node": ">=14" 7528 - }, 7529 - "funding": { 7530 - "url": "https://github.com/prettier/prettier?sponsor=1" 7531 - } 7532 - }, 7533 - "node_modules/prettier-linter-helpers": { 7534 - "version": "1.0.1", 7535 - "dev": true, 7536 - "license": "MIT", 7537 - "dependencies": { 7538 - "fast-diff": "^1.1.2" 7539 - }, 7540 - "engines": { 7541 - "node": ">=6.0.0" 7542 - } 7543 - }, 7544 - "node_modules/prismjs": { 7545 - "version": "1.30.0", 7546 - "license": "MIT", 7547 - "engines": { 7548 - "node": ">=6" 7549 - } 7550 - }, 7551 - "node_modules/prompts": { 7552 - "version": "2.4.2", 7553 - "license": "MIT", 7554 - "dependencies": { 7555 - "kleur": "^3.0.3", 7556 - "sisteransi": "^1.0.5" 7557 - }, 7558 - "engines": { 7559 - "node": ">= 6" 7560 - } 7561 - }, 7562 - "node_modules/prop-types": { 7563 - "version": "15.8.1", 7564 - "dev": true, 7565 - "license": "MIT", 7566 - "dependencies": { 7567 - "loose-envify": "^1.4.0", 7568 - "object-assign": "^4.1.1", 7569 - "react-is": "^16.13.1" 7570 - } 7571 - }, 7572 - "node_modules/property-information": { 7573 - "version": "7.1.0", 7574 - "license": "MIT", 7575 - "funding": { 7576 - "type": "github", 7577 - "url": "https://github.com/sponsors/wooorm" 7578 - } 7579 - }, 7580 - "node_modules/punycode": { 7581 - "version": "2.3.1", 7582 - "dev": true, 7583 - "license": "MIT", 7584 - "engines": { 7585 - "node": ">=6" 7586 - } 7587 - }, 7588 - "node_modules/queue-microtask": { 7589 - "version": "1.2.3", 7590 - "funding": [ 7591 - { 7592 - "type": "github", 7593 - "url": "https://github.com/sponsors/feross" 7594 - }, 7595 - { 7596 - "type": "patreon", 7597 - "url": "https://www.patreon.com/feross" 7598 - }, 7599 - { 7600 - "type": "consulting", 7601 - "url": "https://feross.org/support" 7602 - } 7603 - ], 7604 - "license": "MIT" 7605 - }, 7606 - "node_modules/radix3": { 7607 - "version": "1.1.2", 7608 - "license": "MIT" 7609 - }, 7610 - "node_modules/range-parser": { 7611 - "version": "1.2.1", 7612 - "license": "MIT", 7613 - "engines": { 7614 - "node": ">= 0.6" 7615 - } 7616 - }, 7617 - "node_modules/react": { 7618 - "version": "19.2.4", 7619 - "license": "MIT", 7620 - "engines": { 7621 - "node": ">=0.10.0" 7622 - } 7623 - }, 7624 - "node_modules/react-dom": { 7625 - "version": "19.2.4", 7626 - "license": "MIT", 7627 - "dependencies": { 7628 - "scheduler": "^0.27.0" 7629 - }, 7630 - "peerDependencies": { 7631 - "react": "^19.2.4" 7632 - } 7633 - }, 7634 - "node_modules/react-icons": { 7635 - "version": "5.5.0", 7636 - "dev": true, 7637 - "license": "MIT", 7638 - "peerDependencies": { 7639 - "react": "*" 7640 - } 7641 - }, 7642 - "node_modules/react-is": { 7643 - "version": "16.13.1", 7644 - "dev": true, 7645 - "license": "MIT" 7646 - }, 7647 - "node_modules/react-refresh": { 7648 - "version": "0.17.0", 7649 - "license": "MIT", 7650 - "engines": { 7651 - "node": ">=0.10.0" 7652 - } 7653 - }, 7654 - "node_modules/react-router": { 7655 - "version": "7.13.0", 7656 - "license": "MIT", 7657 - "dependencies": { 7658 - "cookie": "^1.0.1", 7659 - "set-cookie-parser": "^2.6.0" 7660 - }, 7661 - "engines": { 7662 - "node": ">=20.0.0" 7663 - }, 7664 - "peerDependencies": { 7665 - "react": ">=18", 7666 - "react-dom": ">=18" 7667 - }, 7668 - "peerDependenciesMeta": { 7669 - "react-dom": { 7670 - "optional": true 7671 - } 7672 - } 7673 - }, 7674 - "node_modules/react-router-dom": { 7675 - "version": "7.13.0", 7676 - "license": "MIT", 7677 - "dependencies": { 7678 - "react-router": "7.13.0" 7679 - }, 7680 - "engines": { 7681 - "node": ">=20.0.0" 7682 - }, 7683 - "peerDependencies": { 7684 - "react": ">=18", 7685 - "react-dom": ">=18" 7686 - } 7687 - }, 7688 - "node_modules/read-cache": { 7689 - "version": "1.0.0", 7690 - "license": "MIT", 7691 - "dependencies": { 7692 - "pify": "^2.3.0" 7693 - } 7694 - }, 7695 - "node_modules/reflect.getprototypeof": { 7696 - "version": "1.0.10", 7697 - "dev": true, 7698 - "license": "MIT", 7699 - "dependencies": { 7700 - "call-bind": "^1.0.8", 7701 - "define-properties": "^1.2.1", 7702 - "es-abstract": "^1.23.9", 7703 - "es-errors": "^1.3.0", 7704 - "es-object-atoms": "^1.0.0", 7705 - "get-intrinsic": "^1.2.7", 7706 - "get-proto": "^1.0.1", 7707 - "which-builtin-type": "^1.2.1" 7708 - }, 7709 - "engines": { 7710 - "node": ">= 0.4" 7711 - }, 7712 - "funding": { 7713 - "url": "https://github.com/sponsors/ljharb" 7714 - } 7715 - }, 7716 - "node_modules/regex": { 7717 - "version": "6.1.0", 7718 - "license": "MIT", 7719 - "dependencies": { 7720 - "regex-utilities": "^2.3.0" 7721 - } 7722 - }, 7723 - "node_modules/regex-recursion": { 7724 - "version": "6.0.2", 7725 - "license": "MIT", 7726 - "dependencies": { 7727 - "regex-utilities": "^2.3.0" 7728 - } 7729 - }, 7730 - "node_modules/regex-utilities": { 7731 - "version": "2.3.0", 7732 - "license": "MIT" 7733 - }, 7734 - "node_modules/regexp.prototype.flags": { 7735 - "version": "1.5.4", 7736 - "dev": true, 7737 - "license": "MIT", 7738 - "dependencies": { 7739 - "call-bind": "^1.0.8", 7740 - "define-properties": "^1.2.1", 7741 - "es-errors": "^1.3.0", 7742 - "get-proto": "^1.0.1", 7743 - "gopd": "^1.2.0", 7744 - "set-function-name": "^2.0.2" 7745 - }, 7746 - "engines": { 7747 - "node": ">= 0.4" 7748 - }, 7749 - "funding": { 7750 - "url": "https://github.com/sponsors/ljharb" 7751 - } 7752 - }, 7753 - "node_modules/rehype": { 7754 - "version": "13.0.2", 7755 - "license": "MIT", 7756 - "dependencies": { 7757 - "@types/hast": "^3.0.0", 7758 - "rehype-parse": "^9.0.0", 7759 - "rehype-stringify": "^10.0.0", 7760 - "unified": "^11.0.0" 7761 - }, 7762 - "funding": { 7763 - "type": "opencollective", 7764 - "url": "https://opencollective.com/unified" 7765 - } 7766 - }, 7767 - "node_modules/rehype-parse": { 7768 - "version": "9.0.1", 7769 - "license": "MIT", 7770 - "dependencies": { 7771 - "@types/hast": "^3.0.0", 7772 - "hast-util-from-html": "^2.0.0", 7773 - "unified": "^11.0.0" 7774 - }, 7775 - "funding": { 7776 - "type": "opencollective", 7777 - "url": "https://opencollective.com/unified" 7778 - } 7779 - }, 7780 - "node_modules/rehype-raw": { 7781 - "version": "7.0.0", 7782 - "license": "MIT", 7783 - "dependencies": { 7784 - "@types/hast": "^3.0.0", 7785 - "hast-util-raw": "^9.0.0", 7786 - "vfile": "^6.0.0" 7787 - }, 7788 - "funding": { 7789 - "type": "opencollective", 7790 - "url": "https://opencollective.com/unified" 7791 - } 7792 - }, 7793 - "node_modules/rehype-stringify": { 7794 - "version": "10.0.1", 7795 - "license": "MIT", 7796 - "dependencies": { 7797 - "@types/hast": "^3.0.0", 7798 - "hast-util-to-html": "^9.0.0", 7799 - "unified": "^11.0.0" 7800 - }, 7801 - "funding": { 7802 - "type": "opencollective", 7803 - "url": "https://opencollective.com/unified" 7804 - } 7805 - }, 7806 - "node_modules/remark-gfm": { 7807 - "version": "4.0.1", 7808 - "license": "MIT", 7809 - "dependencies": { 7810 - "@types/mdast": "^4.0.0", 7811 - "mdast-util-gfm": "^3.0.0", 7812 - "micromark-extension-gfm": "^3.0.0", 7813 - "remark-parse": "^11.0.0", 7814 - "remark-stringify": "^11.0.0", 7815 - "unified": "^11.0.0" 7816 - }, 7817 - "funding": { 7818 - "type": "opencollective", 7819 - "url": "https://opencollective.com/unified" 7820 - } 7821 - }, 7822 - "node_modules/remark-parse": { 7823 - "version": "11.0.0", 7824 - "license": "MIT", 7825 - "dependencies": { 7826 - "@types/mdast": "^4.0.0", 7827 - "mdast-util-from-markdown": "^2.0.0", 7828 - "micromark-util-types": "^2.0.0", 7829 - "unified": "^11.0.0" 7830 - }, 7831 - "funding": { 7832 - "type": "opencollective", 7833 - "url": "https://opencollective.com/unified" 7834 - } 7835 - }, 7836 - "node_modules/remark-rehype": { 7837 - "version": "11.1.2", 7838 - "license": "MIT", 7839 - "dependencies": { 7840 - "@types/hast": "^3.0.0", 7841 - "@types/mdast": "^4.0.0", 7842 - "mdast-util-to-hast": "^13.0.0", 7843 - "unified": "^11.0.0", 7844 - "vfile": "^6.0.0" 7845 - }, 7846 - "funding": { 7847 - "type": "opencollective", 7848 - "url": "https://opencollective.com/unified" 7849 - } 7850 - }, 7851 - "node_modules/remark-smartypants": { 7852 - "version": "3.0.2", 7853 - "license": "MIT", 7854 - "dependencies": { 7855 - "retext": "^9.0.0", 7856 - "retext-smartypants": "^6.0.0", 7857 - "unified": "^11.0.4", 7858 - "unist-util-visit": "^5.0.0" 7859 - }, 7860 - "engines": { 7861 - "node": ">=16.0.0" 7862 - } 7863 - }, 7864 - "node_modules/remark-stringify": { 7865 - "version": "11.0.0", 7866 - "license": "MIT", 7867 - "dependencies": { 7868 - "@types/mdast": "^4.0.0", 7869 - "mdast-util-to-markdown": "^2.0.0", 7870 - "unified": "^11.0.0" 7871 - }, 7872 - "funding": { 7873 - "type": "opencollective", 7874 - "url": "https://opencollective.com/unified" 7875 - } 7876 - }, 7877 - "node_modules/resolve": { 7878 - "version": "2.0.0-next.5", 7879 - "dev": true, 7880 - "license": "MIT", 7881 - "dependencies": { 7882 - "is-core-module": "^2.13.0", 7883 - "path-parse": "^1.0.7", 7884 - "supports-preserve-symlinks-flag": "^1.0.0" 7885 - }, 7886 - "bin": { 7887 - "resolve": "bin/resolve" 7888 - }, 7889 - "funding": { 7890 - "url": "https://github.com/sponsors/ljharb" 7891 - } 7892 - }, 7893 - "node_modules/retext": { 7894 - "version": "9.0.0", 7895 - "license": "MIT", 7896 - "dependencies": { 7897 - "@types/nlcst": "^2.0.0", 7898 - "retext-latin": "^4.0.0", 7899 - "retext-stringify": "^4.0.0", 7900 - "unified": "^11.0.0" 7901 - }, 7902 - "funding": { 7903 - "type": "opencollective", 7904 - "url": "https://opencollective.com/unified" 7905 - } 7906 - }, 7907 - "node_modules/retext-latin": { 7908 - "version": "4.0.0", 7909 - "license": "MIT", 7910 - "dependencies": { 7911 - "@types/nlcst": "^2.0.0", 7912 - "parse-latin": "^7.0.0", 7913 - "unified": "^11.0.0" 7914 - }, 7915 - "funding": { 7916 - "type": "opencollective", 7917 - "url": "https://opencollective.com/unified" 7918 - } 7919 - }, 7920 - "node_modules/retext-smartypants": { 7921 - "version": "6.2.0", 7922 - "license": "MIT", 7923 - "dependencies": { 7924 - "@types/nlcst": "^2.0.0", 7925 - "nlcst-to-string": "^4.0.0", 7926 - "unist-util-visit": "^5.0.0" 7927 - }, 7928 - "funding": { 7929 - "type": "opencollective", 7930 - "url": "https://opencollective.com/unified" 7931 - } 7932 - }, 7933 - "node_modules/retext-stringify": { 7934 - "version": "4.0.0", 7935 - "license": "MIT", 7936 - "dependencies": { 7937 - "@types/nlcst": "^2.0.0", 7938 - "nlcst-to-string": "^4.0.0", 7939 - "unified": "^11.0.0" 7940 - }, 7941 - "funding": { 7942 - "type": "opencollective", 7943 - "url": "https://opencollective.com/unified" 7944 - } 7945 - }, 7946 - "node_modules/reusify": { 7947 - "version": "1.1.0", 7948 - "license": "MIT", 7949 - "engines": { 7950 - "iojs": ">=1.0.0", 7951 - "node": ">=0.10.0" 7952 - } 7953 - }, 7954 - "node_modules/rollup": { 7955 - "version": "4.57.1", 7956 - "license": "MIT", 7957 - "dependencies": { 7958 - "@types/estree": "1.0.8" 7959 - }, 7960 - "bin": { 7961 - "rollup": "dist/bin/rollup" 7962 - }, 7963 - "engines": { 7964 - "node": ">=18.0.0", 7965 - "npm": ">=8.0.0" 7966 - }, 7967 - "optionalDependencies": { 7968 - "@rollup/rollup-android-arm-eabi": "4.57.1", 7969 - "@rollup/rollup-android-arm64": "4.57.1", 7970 - "@rollup/rollup-darwin-arm64": "4.57.1", 7971 - "@rollup/rollup-darwin-x64": "4.57.1", 7972 - "@rollup/rollup-freebsd-arm64": "4.57.1", 7973 - "@rollup/rollup-freebsd-x64": "4.57.1", 7974 - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", 7975 - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", 7976 - "@rollup/rollup-linux-arm64-gnu": "4.57.1", 7977 - "@rollup/rollup-linux-arm64-musl": "4.57.1", 7978 - "@rollup/rollup-linux-loong64-gnu": "4.57.1", 7979 - "@rollup/rollup-linux-loong64-musl": "4.57.1", 7980 - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", 7981 - "@rollup/rollup-linux-ppc64-musl": "4.57.1", 7982 - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", 7983 - "@rollup/rollup-linux-riscv64-musl": "4.57.1", 7984 - "@rollup/rollup-linux-s390x-gnu": "4.57.1", 7985 - "@rollup/rollup-linux-x64-gnu": "4.57.1", 7986 - "@rollup/rollup-linux-x64-musl": "4.57.1", 7987 - "@rollup/rollup-openbsd-x64": "4.57.1", 7988 - "@rollup/rollup-openharmony-arm64": "4.57.1", 7989 - "@rollup/rollup-win32-arm64-msvc": "4.57.1", 7990 - "@rollup/rollup-win32-ia32-msvc": "4.57.1", 7991 - "@rollup/rollup-win32-x64-gnu": "4.57.1", 7992 - "@rollup/rollup-win32-x64-msvc": "4.57.1", 7993 - "fsevents": "~2.3.2" 7994 - } 7995 - }, 7996 - "node_modules/run-parallel": { 7997 - "version": "1.2.0", 7998 - "funding": [ 7999 - { 8000 - "type": "github", 8001 - "url": "https://github.com/sponsors/feross" 8002 - }, 8003 - { 8004 - "type": "patreon", 8005 - "url": "https://www.patreon.com/feross" 8006 - }, 8007 - { 8008 - "type": "consulting", 8009 - "url": "https://feross.org/support" 8010 - } 8011 - ], 8012 - "license": "MIT", 8013 - "dependencies": { 8014 - "queue-microtask": "^1.2.2" 8015 - } 8016 - }, 8017 - "node_modules/safe-array-concat": { 8018 - "version": "1.1.3", 8019 - "dev": true, 8020 - "license": "MIT", 8021 - "dependencies": { 8022 - "call-bind": "^1.0.8", 8023 - "call-bound": "^1.0.2", 8024 - "get-intrinsic": "^1.2.6", 8025 - "has-symbols": "^1.1.0", 8026 - "isarray": "^2.0.5" 8027 - }, 8028 - "engines": { 8029 - "node": ">=0.4" 8030 - }, 8031 - "funding": { 8032 - "url": "https://github.com/sponsors/ljharb" 8033 - } 8034 - }, 8035 - "node_modules/safe-push-apply": { 8036 - "version": "1.0.0", 8037 - "dev": true, 8038 - "license": "MIT", 8039 - "dependencies": { 8040 - "es-errors": "^1.3.0", 8041 - "isarray": "^2.0.5" 8042 - }, 8043 - "engines": { 8044 - "node": ">= 0.4" 8045 - }, 8046 - "funding": { 8047 - "url": "https://github.com/sponsors/ljharb" 8048 - } 8049 - }, 8050 - "node_modules/safe-regex-test": { 8051 - "version": "1.1.0", 8052 - "dev": true, 8053 - "license": "MIT", 8054 - "dependencies": { 8055 - "call-bound": "^1.0.2", 8056 - "es-errors": "^1.3.0", 8057 - "is-regex": "^1.2.1" 8058 - }, 8059 - "engines": { 8060 - "node": ">= 0.4" 8061 - }, 8062 - "funding": { 8063 - "url": "https://github.com/sponsors/ljharb" 8064 - } 8065 - }, 8066 - "node_modules/satori": { 8067 - "version": "0.19.2", 8068 - "resolved": "https://registry.npmjs.org/satori/-/satori-0.19.2.tgz", 8069 - "integrity": "sha512-71plFHWcq6WJBM5sf/n0eHOmTBiKLUB/G8du7SmLTTLHKEKrV3TPHGKcEVIoyjnbhnjvu9HhLyF9MATB/zzL7g==", 8070 - "license": "MPL-2.0", 8071 - "dependencies": { 8072 - "@shuding/opentype.js": "1.4.0-beta.0", 8073 - "css-background-parser": "^0.1.0", 8074 - "css-box-shadow": "1.0.0-3", 8075 - "css-gradient-parser": "^0.0.17", 8076 - "css-to-react-native": "^3.0.0", 8077 - "emoji-regex-xs": "^2.0.1", 8078 - "escape-html": "^1.0.3", 8079 - "linebreak": "^1.1.0", 8080 - "parse-css-color": "^0.2.1", 8081 - "postcss-value-parser": "^4.2.0", 8082 - "yoga-layout": "^3.2.1" 8083 - }, 8084 - "engines": { 8085 - "node": ">=16" 8086 - } 8087 - }, 8088 - "node_modules/sax": { 8089 - "version": "1.4.4", 8090 - "license": "BlueOak-1.0.0", 8091 - "engines": { 8092 - "node": ">=11.0.0" 8093 - } 8094 - }, 8095 - "node_modules/scheduler": { 8096 - "version": "0.27.0", 8097 - "license": "MIT" 8098 - }, 8099 - "node_modules/semver": { 8100 - "version": "6.3.1", 8101 - "dev": true, 8102 - "license": "ISC", 8103 - "bin": { 8104 - "semver": "bin/semver.js" 8105 - } 8106 - }, 8107 - "node_modules/send": { 8108 - "version": "1.2.1", 8109 - "license": "MIT", 8110 - "dependencies": { 8111 - "debug": "^4.4.3", 8112 - "encodeurl": "^2.0.0", 8113 - "escape-html": "^1.0.3", 8114 - "etag": "^1.8.1", 8115 - "fresh": "^2.0.0", 8116 - "http-errors": "^2.0.1", 8117 - "mime-types": "^3.0.2", 8118 - "ms": "^2.1.3", 8119 - "on-finished": "^2.4.1", 8120 - "range-parser": "^1.2.1", 8121 - "statuses": "^2.0.2" 8122 - }, 8123 - "engines": { 8124 - "node": ">= 18" 8125 - }, 8126 - "funding": { 8127 - "type": "opencollective", 8128 - "url": "https://opencollective.com/express" 8129 - } 8130 - }, 8131 - "node_modules/server-destroy": { 8132 - "version": "1.0.1", 8133 - "license": "ISC" 8134 - }, 8135 - "node_modules/set-cookie-parser": { 8136 - "version": "2.7.2", 8137 - "license": "MIT" 8138 - }, 8139 - "node_modules/set-function-length": { 8140 - "version": "1.2.2", 8141 - "dev": true, 8142 - "license": "MIT", 8143 - "dependencies": { 8144 - "define-data-property": "^1.1.4", 8145 - "es-errors": "^1.3.0", 8146 - "function-bind": "^1.1.2", 8147 - "get-intrinsic": "^1.2.4", 8148 - "gopd": "^1.0.1", 8149 - "has-property-descriptors": "^1.0.2" 8150 - }, 8151 - "engines": { 8152 - "node": ">= 0.4" 8153 - } 8154 - }, 8155 - "node_modules/set-function-name": { 8156 - "version": "2.0.2", 8157 - "dev": true, 8158 - "license": "MIT", 8159 - "dependencies": { 8160 - "define-data-property": "^1.1.4", 8161 - "es-errors": "^1.3.0", 8162 - "functions-have-names": "^1.2.3", 8163 - "has-property-descriptors": "^1.0.2" 8164 - }, 8165 - "engines": { 8166 - "node": ">= 0.4" 8167 - } 8168 - }, 8169 - "node_modules/set-proto": { 8170 - "version": "1.0.0", 8171 - "dev": true, 8172 - "license": "MIT", 8173 - "dependencies": { 8174 - "dunder-proto": "^1.0.1", 8175 - "es-errors": "^1.3.0", 8176 - "es-object-atoms": "^1.0.0" 8177 - }, 8178 - "engines": { 8179 - "node": ">= 0.4" 8180 - } 8181 - }, 8182 - "node_modules/setprototypeof": { 8183 - "version": "1.2.0", 8184 - "license": "ISC" 8185 - }, 8186 - "node_modules/sharp": { 8187 - "version": "0.34.5", 8188 - "hasInstallScript": true, 8189 - "license": "Apache-2.0", 8190 - "optional": true, 8191 - "dependencies": { 8192 - "@img/colour": "^1.0.0", 8193 - "detect-libc": "^2.1.2", 8194 - "semver": "^7.7.3" 8195 - }, 8196 - "engines": { 8197 - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 8198 - }, 8199 - "funding": { 8200 - "url": "https://opencollective.com/libvips" 8201 - }, 8202 - "optionalDependencies": { 8203 - "@img/sharp-darwin-arm64": "0.34.5", 8204 - "@img/sharp-darwin-x64": "0.34.5", 8205 - "@img/sharp-libvips-darwin-arm64": "1.2.4", 8206 - "@img/sharp-libvips-darwin-x64": "1.2.4", 8207 - "@img/sharp-libvips-linux-arm": "1.2.4", 8208 - "@img/sharp-libvips-linux-arm64": "1.2.4", 8209 - "@img/sharp-libvips-linux-ppc64": "1.2.4", 8210 - "@img/sharp-libvips-linux-riscv64": "1.2.4", 8211 - "@img/sharp-libvips-linux-s390x": "1.2.4", 8212 - "@img/sharp-libvips-linux-x64": "1.2.4", 8213 - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", 8214 - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", 8215 - "@img/sharp-linux-arm": "0.34.5", 8216 - "@img/sharp-linux-arm64": "0.34.5", 8217 - "@img/sharp-linux-ppc64": "0.34.5", 8218 - "@img/sharp-linux-riscv64": "0.34.5", 8219 - "@img/sharp-linux-s390x": "0.34.5", 8220 - "@img/sharp-linux-x64": "0.34.5", 8221 - "@img/sharp-linuxmusl-arm64": "0.34.5", 8222 - "@img/sharp-linuxmusl-x64": "0.34.5", 8223 - "@img/sharp-wasm32": "0.34.5", 8224 - "@img/sharp-win32-arm64": "0.34.5", 8225 - "@img/sharp-win32-ia32": "0.34.5", 8226 - "@img/sharp-win32-x64": "0.34.5" 8227 - } 8228 - }, 8229 - "node_modules/sharp/node_modules/semver": { 8230 - "version": "7.7.3", 8231 - "license": "ISC", 8232 - "optional": true, 8233 - "bin": { 8234 - "semver": "bin/semver.js" 8235 - }, 8236 - "engines": { 8237 - "node": ">=10" 8238 - } 8239 - }, 8240 - "node_modules/shebang-command": { 8241 - "version": "2.0.0", 8242 - "dev": true, 8243 - "license": "MIT", 8244 - "dependencies": { 8245 - "shebang-regex": "^3.0.0" 8246 - }, 8247 - "engines": { 8248 - "node": ">=8" 8249 - } 8250 - }, 8251 - "node_modules/shebang-regex": { 8252 - "version": "3.0.0", 8253 - "dev": true, 8254 - "license": "MIT", 8255 - "engines": { 8256 - "node": ">=8" 8257 - } 8258 - }, 8259 - "node_modules/shiki": { 8260 - "version": "3.22.0", 8261 - "license": "MIT", 8262 - "dependencies": { 8263 - "@shikijs/core": "3.22.0", 8264 - "@shikijs/engine-javascript": "3.22.0", 8265 - "@shikijs/engine-oniguruma": "3.22.0", 8266 - "@shikijs/langs": "3.22.0", 8267 - "@shikijs/themes": "3.22.0", 8268 - "@shikijs/types": "3.22.0", 8269 - "@shikijs/vscode-textmate": "^10.0.2", 8270 - "@types/hast": "^3.0.4" 8271 - } 8272 - }, 8273 - "node_modules/side-channel": { 8274 - "version": "1.1.0", 8275 - "dev": true, 8276 - "license": "MIT", 8277 - "dependencies": { 8278 - "es-errors": "^1.3.0", 8279 - "object-inspect": "^1.13.3", 8280 - "side-channel-list": "^1.0.0", 8281 - "side-channel-map": "^1.0.1", 8282 - "side-channel-weakmap": "^1.0.2" 8283 - }, 8284 - "engines": { 8285 - "node": ">= 0.4" 8286 - }, 8287 - "funding": { 8288 - "url": "https://github.com/sponsors/ljharb" 8289 - } 8290 - }, 8291 - "node_modules/side-channel-list": { 8292 - "version": "1.0.0", 8293 - "dev": true, 8294 - "license": "MIT", 8295 - "dependencies": { 8296 - "es-errors": "^1.3.0", 8297 - "object-inspect": "^1.13.3" 8298 - }, 8299 - "engines": { 8300 - "node": ">= 0.4" 8301 - }, 8302 - "funding": { 8303 - "url": "https://github.com/sponsors/ljharb" 8304 - } 8305 - }, 8306 - "node_modules/side-channel-map": { 8307 - "version": "1.0.1", 8308 - "dev": true, 8309 - "license": "MIT", 8310 - "dependencies": { 8311 - "call-bound": "^1.0.2", 8312 - "es-errors": "^1.3.0", 8313 - "get-intrinsic": "^1.2.5", 8314 - "object-inspect": "^1.13.3" 8315 - }, 8316 - "engines": { 8317 - "node": ">= 0.4" 8318 - }, 8319 - "funding": { 8320 - "url": "https://github.com/sponsors/ljharb" 8321 - } 8322 - }, 8323 - "node_modules/side-channel-weakmap": { 8324 - "version": "1.0.2", 8325 - "dev": true, 8326 - "license": "MIT", 8327 - "dependencies": { 8328 - "call-bound": "^1.0.2", 8329 - "es-errors": "^1.3.0", 8330 - "get-intrinsic": "^1.2.5", 8331 - "object-inspect": "^1.13.3", 8332 - "side-channel-map": "^1.0.1" 8333 - }, 8334 - "engines": { 8335 - "node": ">= 0.4" 8336 - }, 8337 - "funding": { 8338 - "url": "https://github.com/sponsors/ljharb" 8339 - } 8340 - }, 8341 - "node_modules/sisteransi": { 8342 - "version": "1.0.5", 8343 - "license": "MIT" 8344 - }, 8345 - "node_modules/smol-toml": { 8346 - "version": "1.6.0", 8347 - "license": "BSD-3-Clause", 8348 - "engines": { 8349 - "node": ">= 18" 8350 - }, 8351 - "funding": { 8352 - "url": "https://github.com/sponsors/cyyynthia" 8353 - } 8354 - }, 8355 - "node_modules/source-map-js": { 8356 - "version": "1.2.1", 8357 - "license": "BSD-3-Clause", 8358 - "engines": { 8359 - "node": ">=0.10.0" 8360 - } 8361 - }, 8362 - "node_modules/space-separated-tokens": { 8363 - "version": "2.0.2", 8364 - "license": "MIT", 8365 - "funding": { 8366 - "type": "github", 8367 - "url": "https://github.com/sponsors/wooorm" 8368 - } 8369 - }, 8370 - "node_modules/statuses": { 8371 - "version": "2.0.2", 8372 - "license": "MIT", 8373 - "engines": { 8374 - "node": ">= 0.8" 8375 - } 8376 - }, 8377 - "node_modules/stop-iteration-iterator": { 8378 - "version": "1.1.0", 8379 - "dev": true, 8380 - "license": "MIT", 8381 - "dependencies": { 8382 - "es-errors": "^1.3.0", 8383 - "internal-slot": "^1.1.0" 8384 - }, 8385 - "engines": { 8386 - "node": ">= 0.4" 8387 - } 8388 - }, 8389 - "node_modules/string-width": { 8390 - "version": "7.2.0", 8391 - "license": "MIT", 8392 - "dependencies": { 8393 - "emoji-regex": "^10.3.0", 8394 - "get-east-asian-width": "^1.0.0", 8395 - "strip-ansi": "^7.1.0" 8396 - }, 8397 - "engines": { 8398 - "node": ">=18" 8399 - }, 8400 - "funding": { 8401 - "url": "https://github.com/sponsors/sindresorhus" 8402 - } 8403 - }, 8404 - "node_modules/string.prototype.codepointat": { 8405 - "version": "0.2.1", 8406 - "license": "MIT" 8407 - }, 8408 - "node_modules/string.prototype.matchall": { 8409 - "version": "4.0.12", 8410 - "dev": true, 8411 - "license": "MIT", 8412 - "dependencies": { 8413 - "call-bind": "^1.0.8", 8414 - "call-bound": "^1.0.3", 8415 - "define-properties": "^1.2.1", 8416 - "es-abstract": "^1.23.6", 8417 - "es-errors": "^1.3.0", 8418 - "es-object-atoms": "^1.0.0", 8419 - "get-intrinsic": "^1.2.6", 8420 - "gopd": "^1.2.0", 8421 - "has-symbols": "^1.1.0", 8422 - "internal-slot": "^1.1.0", 8423 - "regexp.prototype.flags": "^1.5.3", 8424 - "set-function-name": "^2.0.2", 8425 - "side-channel": "^1.1.0" 8426 - }, 8427 - "engines": { 8428 - "node": ">= 0.4" 8429 - }, 8430 - "funding": { 8431 - "url": "https://github.com/sponsors/ljharb" 8432 - } 8433 - }, 8434 - "node_modules/string.prototype.repeat": { 8435 - "version": "1.0.0", 8436 - "dev": true, 8437 - "license": "MIT", 8438 - "dependencies": { 8439 - "define-properties": "^1.1.3", 8440 - "es-abstract": "^1.17.5" 8441 - } 8442 - }, 8443 - "node_modules/string.prototype.trim": { 8444 - "version": "1.2.10", 8445 - "dev": true, 8446 - "license": "MIT", 8447 - "dependencies": { 8448 - "call-bind": "^1.0.8", 8449 - "call-bound": "^1.0.2", 8450 - "define-data-property": "^1.1.4", 8451 - "define-properties": "^1.2.1", 8452 - "es-abstract": "^1.23.5", 8453 - "es-object-atoms": "^1.0.0", 8454 - "has-property-descriptors": "^1.0.2" 8455 - }, 8456 - "engines": { 8457 - "node": ">= 0.4" 8458 - }, 8459 - "funding": { 8460 - "url": "https://github.com/sponsors/ljharb" 8461 - } 8462 - }, 8463 - "node_modules/string.prototype.trimend": { 8464 - "version": "1.0.9", 8465 - "dev": true, 8466 - "license": "MIT", 8467 - "dependencies": { 8468 - "call-bind": "^1.0.8", 8469 - "call-bound": "^1.0.2", 8470 - "define-properties": "^1.2.1", 8471 - "es-object-atoms": "^1.0.0" 8472 - }, 8473 - "engines": { 8474 - "node": ">= 0.4" 8475 - }, 8476 - "funding": { 8477 - "url": "https://github.com/sponsors/ljharb" 8478 - } 8479 - }, 8480 - "node_modules/string.prototype.trimstart": { 8481 - "version": "1.0.8", 8482 - "dev": true, 8483 - "license": "MIT", 8484 - "dependencies": { 8485 - "call-bind": "^1.0.7", 8486 - "define-properties": "^1.2.1", 8487 - "es-object-atoms": "^1.0.0" 8488 - }, 8489 - "engines": { 8490 - "node": ">= 0.4" 8491 - }, 8492 - "funding": { 8493 - "url": "https://github.com/sponsors/ljharb" 8494 - } 8495 - }, 8496 - "node_modules/stringify-entities": { 8497 - "version": "4.0.4", 8498 - "license": "MIT", 8499 - "dependencies": { 8500 - "character-entities-html4": "^2.0.0", 8501 - "character-entities-legacy": "^3.0.0" 8502 - }, 8503 - "funding": { 8504 - "type": "github", 8505 - "url": "https://github.com/sponsors/wooorm" 8506 - } 8507 - }, 8508 - "node_modules/strip-ansi": { 8509 - "version": "7.1.2", 8510 - "license": "MIT", 8511 - "dependencies": { 8512 - "ansi-regex": "^6.0.1" 8513 - }, 8514 - "engines": { 8515 - "node": ">=12" 8516 - }, 8517 - "funding": { 8518 - "url": "https://github.com/chalk/strip-ansi?sponsor=1" 8519 - } 8520 - }, 8521 - "node_modules/sucrase": { 8522 - "version": "3.35.1", 8523 - "license": "MIT", 8524 - "dependencies": { 8525 - "@jridgewell/gen-mapping": "^0.3.2", 8526 - "commander": "^4.0.0", 8527 - "lines-and-columns": "^1.1.6", 8528 - "mz": "^2.7.0", 8529 - "pirates": "^4.0.1", 8530 - "tinyglobby": "^0.2.11", 8531 - "ts-interface-checker": "^0.1.9" 8532 - }, 8533 - "bin": { 8534 - "sucrase": "bin/sucrase", 8535 - "sucrase-node": "bin/sucrase-node" 8536 - }, 8537 - "engines": { 8538 - "node": ">=16 || 14 >=14.17" 8539 - } 8540 - }, 8541 - "node_modules/sucrase/node_modules/commander": { 8542 - "version": "4.1.1", 8543 - "license": "MIT", 8544 - "engines": { 8545 - "node": ">= 6" 8546 - } 8547 - }, 8548 - "node_modules/supports-preserve-symlinks-flag": { 8549 - "version": "1.0.0", 8550 - "license": "MIT", 8551 - "engines": { 8552 - "node": ">= 0.4" 8553 - }, 8554 - "funding": { 8555 - "url": "https://github.com/sponsors/ljharb" 8556 - } 8557 - }, 8558 - "node_modules/svgo": { 8559 - "version": "4.0.0", 8560 - "license": "MIT", 8561 - "dependencies": { 8562 - "commander": "^11.1.0", 8563 - "css-select": "^5.1.0", 8564 - "css-tree": "^3.0.1", 8565 - "css-what": "^6.1.0", 8566 - "csso": "^5.0.5", 8567 - "picocolors": "^1.1.1", 8568 - "sax": "^1.4.1" 8569 - }, 8570 - "bin": { 8571 - "svgo": "bin/svgo.js" 8572 - }, 8573 - "engines": { 8574 - "node": ">=16" 8575 - }, 8576 - "funding": { 8577 - "type": "opencollective", 8578 - "url": "https://opencollective.com/svgo" 8579 - } 8580 - }, 8581 - "node_modules/synckit": { 8582 - "version": "0.11.12", 8583 - "dev": true, 8584 - "license": "MIT", 8585 - "dependencies": { 8586 - "@pkgr/core": "^0.2.9" 8587 - }, 8588 - "engines": { 8589 - "node": "^14.18.0 || >=16.0.0" 8590 - }, 8591 - "funding": { 8592 - "url": "https://opencollective.com/synckit" 8593 - } 8594 - }, 8595 - "node_modules/tailwind-merge": { 8596 - "version": "3.4.0", 8597 - "license": "MIT", 8598 - "funding": { 8599 - "type": "github", 8600 - "url": "https://github.com/sponsors/dcastil" 8601 - } 8602 - }, 8603 - "node_modules/tailwindcss": { 8604 - "version": "3.4.19", 8605 - "license": "MIT", 8606 - "dependencies": { 8607 - "@alloc/quick-lru": "^5.2.0", 8608 - "arg": "^5.0.2", 8609 - "chokidar": "^3.6.0", 8610 - "didyoumean": "^1.2.2", 8611 - "dlv": "^1.1.3", 8612 - "fast-glob": "^3.3.2", 8613 - "glob-parent": "^6.0.2", 8614 - "is-glob": "^4.0.3", 8615 - "jiti": "^1.21.7", 8616 - "lilconfig": "^3.1.3", 8617 - "micromatch": "^4.0.8", 8618 - "normalize-path": "^3.0.0", 8619 - "object-hash": "^3.0.0", 8620 - "picocolors": "^1.1.1", 8621 - "postcss": "^8.4.47", 8622 - "postcss-import": "^15.1.0", 8623 - "postcss-js": "^4.0.1", 8624 - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", 8625 - "postcss-nested": "^6.2.0", 8626 - "postcss-selector-parser": "^6.1.2", 8627 - "resolve": "^1.22.8", 8628 - "sucrase": "^3.35.0" 8629 - }, 8630 - "bin": { 8631 - "tailwind": "lib/cli.js", 8632 - "tailwindcss": "lib/cli.js" 8633 - }, 8634 - "engines": { 8635 - "node": ">=14.0.0" 8636 - } 8637 - }, 8638 - "node_modules/tailwindcss/node_modules/chokidar": { 8639 - "version": "3.6.0", 8640 - "license": "MIT", 8641 - "dependencies": { 8642 - "anymatch": "~3.1.2", 8643 - "braces": "~3.0.2", 8644 - "glob-parent": "~5.1.2", 8645 - "is-binary-path": "~2.1.0", 8646 - "is-glob": "~4.0.1", 8647 - "normalize-path": "~3.0.0", 8648 - "readdirp": "~3.6.0" 8649 - }, 8650 - "engines": { 8651 - "node": ">= 8.10.0" 8652 - }, 8653 - "funding": { 8654 - "url": "https://paulmillr.com/funding/" 8655 - }, 8656 - "optionalDependencies": { 8657 - "fsevents": "~2.3.2" 8658 - } 8659 - }, 8660 - "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { 8661 - "version": "5.1.2", 8662 - "license": "ISC", 8663 - "dependencies": { 8664 - "is-glob": "^4.0.1" 8665 - }, 8666 - "engines": { 8667 - "node": ">= 6" 8668 - } 8669 - }, 8670 - "node_modules/tailwindcss/node_modules/jiti": { 8671 - "version": "1.21.7", 8672 - "license": "MIT", 8673 - "bin": { 8674 - "jiti": "bin/jiti.js" 8675 - } 8676 - }, 8677 - "node_modules/tailwindcss/node_modules/picomatch": { 8678 - "version": "2.3.1", 8679 - "license": "MIT", 8680 - "engines": { 8681 - "node": ">=8.6" 8682 - }, 8683 - "funding": { 8684 - "url": "https://github.com/sponsors/jonschlinkert" 8685 - } 8686 - }, 8687 - "node_modules/tailwindcss/node_modules/readdirp": { 8688 - "version": "3.6.0", 8689 - "license": "MIT", 8690 - "dependencies": { 8691 - "picomatch": "^2.2.1" 8692 - }, 8693 - "engines": { 8694 - "node": ">=8.10.0" 8695 - } 8696 - }, 8697 - "node_modules/tailwindcss/node_modules/resolve": { 8698 - "version": "1.22.11", 8699 - "license": "MIT", 8700 - "dependencies": { 8701 - "is-core-module": "^2.16.1", 8702 - "path-parse": "^1.0.7", 8703 - "supports-preserve-symlinks-flag": "^1.0.0" 8704 - }, 8705 - "bin": { 8706 - "resolve": "bin/resolve" 8707 - }, 8708 - "engines": { 8709 - "node": ">= 0.4" 8710 - }, 8711 - "funding": { 8712 - "url": "https://github.com/sponsors/ljharb" 8713 - } 8714 - }, 8715 - "node_modules/tapable": { 8716 - "version": "2.3.0", 8717 - "license": "MIT", 8718 - "engines": { 8719 - "node": ">=6" 8720 - }, 8721 - "funding": { 8722 - "type": "opencollective", 8723 - "url": "https://opencollective.com/webpack" 8724 - } 8725 - }, 8726 - "node_modules/thenify": { 8727 - "version": "3.3.1", 8728 - "license": "MIT", 8729 - "dependencies": { 8730 - "any-promise": "^1.0.0" 8731 - } 8732 - }, 8733 - "node_modules/thenify-all": { 8734 - "version": "1.6.0", 8735 - "license": "MIT", 8736 - "dependencies": { 8737 - "thenify": ">= 3.1.0 < 4" 8738 - }, 8739 - "engines": { 8740 - "node": ">=0.8" 8741 - } 8742 - }, 8743 - "node_modules/tiny-inflate": { 8744 - "version": "1.0.3", 8745 - "license": "MIT" 8746 - }, 8747 - "node_modules/tinyexec": { 8748 - "version": "1.0.2", 8749 - "license": "MIT", 8750 - "engines": { 8751 - "node": ">=18" 8752 - } 8753 - }, 8754 - "node_modules/tinyglobby": { 8755 - "version": "0.2.15", 8756 - "license": "MIT", 8757 - "dependencies": { 8758 - "fdir": "^6.5.0", 8759 - "picomatch": "^4.0.3" 8760 - }, 8761 - "engines": { 8762 - "node": ">=12.0.0" 8763 - }, 8764 - "funding": { 8765 - "url": "https://github.com/sponsors/SuperchupuDev" 8766 - } 8767 - }, 8768 - "node_modules/to-regex-range": { 8769 - "version": "5.0.1", 8770 - "license": "MIT", 8771 - "dependencies": { 8772 - "is-number": "^7.0.0" 8773 - }, 8774 - "engines": { 8775 - "node": ">=8.0" 8776 - } 8777 - }, 8778 - "node_modules/toidentifier": { 8779 - "version": "1.0.1", 8780 - "license": "MIT", 8781 - "engines": { 8782 - "node": ">=0.6" 8783 - } 8784 - }, 8785 - "node_modules/trim-lines": { 8786 - "version": "3.0.1", 8787 - "license": "MIT", 8788 - "funding": { 8789 - "type": "github", 8790 - "url": "https://github.com/sponsors/wooorm" 8791 - } 8792 - }, 8793 - "node_modules/trough": { 8794 - "version": "2.2.0", 8795 - "license": "MIT", 8796 - "funding": { 8797 - "type": "github", 8798 - "url": "https://github.com/sponsors/wooorm" 8799 - } 8800 - }, 8801 - "node_modules/ts-api-utils": { 8802 - "version": "2.4.0", 8803 - "dev": true, 8804 - "license": "MIT", 8805 - "engines": { 8806 - "node": ">=18.12" 8807 - }, 8808 - "peerDependencies": { 8809 - "typescript": ">=4.8.4" 8810 - } 8811 - }, 8812 - "node_modules/ts-interface-checker": { 8813 - "version": "0.1.13", 8814 - "license": "Apache-2.0" 8815 - }, 8816 - "node_modules/tsconfck": { 8817 - "version": "3.1.6", 8818 - "license": "MIT", 8819 - "bin": { 8820 - "tsconfck": "bin/tsconfck.js" 8821 - }, 8822 - "engines": { 8823 - "node": "^18 || >=20" 8824 - }, 8825 - "peerDependencies": { 8826 - "typescript": "^5.0.0" 8827 - }, 8828 - "peerDependenciesMeta": { 8829 - "typescript": { 8830 - "optional": true 8831 - } 8832 - } 8833 - }, 8834 - "node_modules/tslib": { 8835 - "version": "2.8.1", 8836 - "license": "0BSD", 8837 - "optional": true 8838 - }, 8839 - "node_modules/type-check": { 8840 - "version": "0.4.0", 8841 - "dev": true, 8842 - "license": "MIT", 8843 - "dependencies": { 8844 - "prelude-ls": "^1.2.1" 8845 - }, 8846 - "engines": { 8847 - "node": ">= 0.8.0" 8848 - } 8849 - }, 8850 - "node_modules/type-fest": { 8851 - "version": "4.41.0", 8852 - "license": "(MIT OR CC0-1.0)", 8853 - "engines": { 8854 - "node": ">=16" 8855 - }, 8856 - "funding": { 8857 - "url": "https://github.com/sponsors/sindresorhus" 8858 - } 8859 - }, 8860 - "node_modules/typed-array-buffer": { 8861 - "version": "1.0.3", 8862 - "dev": true, 8863 - "license": "MIT", 8864 - "dependencies": { 8865 - "call-bound": "^1.0.3", 8866 - "es-errors": "^1.3.0", 8867 - "is-typed-array": "^1.1.14" 8868 - }, 8869 - "engines": { 8870 - "node": ">= 0.4" 8871 - } 8872 - }, 8873 - "node_modules/typed-array-byte-length": { 8874 - "version": "1.0.3", 8875 - "dev": true, 8876 - "license": "MIT", 8877 - "dependencies": { 8878 - "call-bind": "^1.0.8", 8879 - "for-each": "^0.3.3", 8880 - "gopd": "^1.2.0", 8881 - "has-proto": "^1.2.0", 8882 - "is-typed-array": "^1.1.14" 8883 - }, 8884 - "engines": { 8885 - "node": ">= 0.4" 8886 - }, 8887 - "funding": { 8888 - "url": "https://github.com/sponsors/ljharb" 8889 - } 8890 - }, 8891 - "node_modules/typed-array-byte-offset": { 8892 - "version": "1.0.4", 8893 - "dev": true, 8894 - "license": "MIT", 8895 - "dependencies": { 8896 - "available-typed-arrays": "^1.0.7", 8897 - "call-bind": "^1.0.8", 8898 - "for-each": "^0.3.3", 8899 - "gopd": "^1.2.0", 8900 - "has-proto": "^1.2.0", 8901 - "is-typed-array": "^1.1.15", 8902 - "reflect.getprototypeof": "^1.0.9" 8903 - }, 8904 - "engines": { 8905 - "node": ">= 0.4" 8906 - }, 8907 - "funding": { 8908 - "url": "https://github.com/sponsors/ljharb" 8909 - } 8910 - }, 8911 - "node_modules/typed-array-length": { 8912 - "version": "1.0.7", 8913 - "dev": true, 8914 - "license": "MIT", 8915 - "dependencies": { 8916 - "call-bind": "^1.0.7", 8917 - "for-each": "^0.3.3", 8918 - "gopd": "^1.0.1", 8919 - "is-typed-array": "^1.1.13", 8920 - "possible-typed-array-names": "^1.0.0", 8921 - "reflect.getprototypeof": "^1.0.6" 8922 - }, 8923 - "engines": { 8924 - "node": ">= 0.4" 8925 - }, 8926 - "funding": { 8927 - "url": "https://github.com/sponsors/ljharb" 8928 - } 8929 - }, 8930 - "node_modules/typescript": { 8931 - "version": "5.9.3", 8932 - "dev": true, 8933 - "license": "Apache-2.0", 8934 - "bin": { 8935 - "tsc": "bin/tsc", 8936 - "tsserver": "bin/tsserver" 8937 - }, 8938 - "engines": { 8939 - "node": ">=14.17" 8940 - } 8941 - }, 8942 - "node_modules/typescript-eslint": { 8943 - "version": "8.54.0", 8944 - "dev": true, 8945 - "license": "MIT", 8946 - "dependencies": { 8947 - "@typescript-eslint/eslint-plugin": "8.54.0", 8948 - "@typescript-eslint/parser": "8.54.0", 8949 - "@typescript-eslint/typescript-estree": "8.54.0", 8950 - "@typescript-eslint/utils": "8.54.0" 8951 - }, 8952 - "engines": { 8953 - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 8954 - }, 8955 - "funding": { 8956 - "type": "opencollective", 8957 - "url": "https://opencollective.com/typescript-eslint" 8958 - }, 8959 - "peerDependencies": { 8960 - "eslint": "^8.57.0 || ^9.0.0", 8961 - "typescript": ">=4.8.4 <6.0.0" 8962 - } 8963 - }, 8964 - "node_modules/ufo": { 8965 - "version": "1.6.3", 8966 - "license": "MIT" 8967 - }, 8968 - "node_modules/ultrahtml": { 8969 - "version": "1.6.0", 8970 - "license": "MIT" 8971 - }, 8972 - "node_modules/unbox-primitive": { 8973 - "version": "1.1.0", 8974 - "dev": true, 8975 - "license": "MIT", 8976 - "dependencies": { 8977 - "call-bound": "^1.0.3", 8978 - "has-bigints": "^1.0.2", 8979 - "has-symbols": "^1.1.0", 8980 - "which-boxed-primitive": "^1.1.1" 8981 - }, 8982 - "engines": { 8983 - "node": ">= 0.4" 8984 - }, 8985 - "funding": { 8986 - "url": "https://github.com/sponsors/ljharb" 8987 - } 8988 - }, 8989 - "node_modules/uncrypto": { 8990 - "version": "0.1.3", 8991 - "license": "MIT" 8992 - }, 8993 - "node_modules/undici-types": { 8994 - "version": "7.16.0", 8995 - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 8996 - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 8997 - "dev": true, 8998 - "license": "MIT" 8999 - }, 9000 - "node_modules/unicode-trie": { 9001 - "version": "2.0.0", 9002 - "license": "MIT", 9003 - "dependencies": { 9004 - "pako": "^0.2.5", 9005 - "tiny-inflate": "^1.0.0" 9006 - } 9007 - }, 9008 - "node_modules/unified": { 9009 - "version": "11.0.5", 9010 - "license": "MIT", 9011 - "dependencies": { 9012 - "@types/unist": "^3.0.0", 9013 - "bail": "^2.0.0", 9014 - "devlop": "^1.0.0", 9015 - "extend": "^3.0.0", 9016 - "is-plain-obj": "^4.0.0", 9017 - "trough": "^2.0.0", 9018 - "vfile": "^6.0.0" 9019 - }, 9020 - "funding": { 9021 - "type": "opencollective", 9022 - "url": "https://opencollective.com/unified" 9023 - } 9024 - }, 9025 - "node_modules/unifont": { 9026 - "version": "0.7.3", 9027 - "license": "MIT", 9028 - "dependencies": { 9029 - "css-tree": "^3.1.0", 9030 - "ofetch": "^1.5.1", 9031 - "ohash": "^2.0.11" 9032 - } 9033 - }, 9034 - "node_modules/unist-util-find-after": { 9035 - "version": "5.0.0", 9036 - "license": "MIT", 9037 - "dependencies": { 9038 - "@types/unist": "^3.0.0", 9039 - "unist-util-is": "^6.0.0" 9040 - }, 9041 - "funding": { 9042 - "type": "opencollective", 9043 - "url": "https://opencollective.com/unified" 9044 - } 9045 - }, 9046 - "node_modules/unist-util-is": { 9047 - "version": "6.0.1", 9048 - "license": "MIT", 9049 - "dependencies": { 9050 - "@types/unist": "^3.0.0" 9051 - }, 9052 - "funding": { 9053 - "type": "opencollective", 9054 - "url": "https://opencollective.com/unified" 9055 - } 9056 - }, 9057 - "node_modules/unist-util-modify-children": { 9058 - "version": "4.0.0", 9059 - "license": "MIT", 9060 - "dependencies": { 9061 - "@types/unist": "^3.0.0", 9062 - "array-iterate": "^2.0.0" 9063 - }, 9064 - "funding": { 9065 - "type": "opencollective", 9066 - "url": "https://opencollective.com/unified" 9067 - } 9068 - }, 9069 - "node_modules/unist-util-position": { 9070 - "version": "5.0.0", 9071 - "license": "MIT", 9072 - "dependencies": { 9073 - "@types/unist": "^3.0.0" 9074 - }, 9075 - "funding": { 9076 - "type": "opencollective", 9077 - "url": "https://opencollective.com/unified" 9078 - } 9079 - }, 9080 - "node_modules/unist-util-remove-position": { 9081 - "version": "5.0.0", 9082 - "license": "MIT", 9083 - "dependencies": { 9084 - "@types/unist": "^3.0.0", 9085 - "unist-util-visit": "^5.0.0" 9086 - }, 9087 - "funding": { 9088 - "type": "opencollective", 9089 - "url": "https://opencollective.com/unified" 9090 - } 9091 - }, 9092 - "node_modules/unist-util-stringify-position": { 9093 - "version": "4.0.0", 9094 - "license": "MIT", 9095 - "dependencies": { 9096 - "@types/unist": "^3.0.0" 9097 - }, 9098 - "funding": { 9099 - "type": "opencollective", 9100 - "url": "https://opencollective.com/unified" 9101 - } 9102 - }, 9103 - "node_modules/unist-util-visit": { 9104 - "version": "5.1.0", 9105 - "license": "MIT", 9106 - "dependencies": { 9107 - "@types/unist": "^3.0.0", 9108 - "unist-util-is": "^6.0.0", 9109 - "unist-util-visit-parents": "^6.0.0" 9110 - }, 9111 - "funding": { 9112 - "type": "opencollective", 9113 - "url": "https://opencollective.com/unified" 9114 - } 9115 - }, 9116 - "node_modules/unist-util-visit-children": { 9117 - "version": "3.0.0", 9118 - "license": "MIT", 9119 - "dependencies": { 9120 - "@types/unist": "^3.0.0" 9121 - }, 9122 - "funding": { 9123 - "type": "opencollective", 9124 - "url": "https://opencollective.com/unified" 9125 - } 9126 - }, 9127 - "node_modules/unist-util-visit-parents": { 9128 - "version": "6.0.2", 9129 - "license": "MIT", 9130 - "dependencies": { 9131 - "@types/unist": "^3.0.0", 9132 - "unist-util-is": "^6.0.0" 9133 - }, 9134 - "funding": { 9135 - "type": "opencollective", 9136 - "url": "https://opencollective.com/unified" 9137 - } 9138 - }, 9139 - "node_modules/unstorage": { 9140 - "version": "1.17.4", 9141 - "license": "MIT", 9142 - "dependencies": { 9143 - "anymatch": "^3.1.3", 9144 - "chokidar": "^5.0.0", 9145 - "destr": "^2.0.5", 9146 - "h3": "^1.15.5", 9147 - "lru-cache": "^11.2.0", 9148 - "node-fetch-native": "^1.6.7", 9149 - "ofetch": "^1.5.1", 9150 - "ufo": "^1.6.3" 9151 - }, 9152 - "peerDependencies": { 9153 - "@azure/app-configuration": "^1.8.0", 9154 - "@azure/cosmos": "^4.2.0", 9155 - "@azure/data-tables": "^13.3.0", 9156 - "@azure/identity": "^4.6.0", 9157 - "@azure/keyvault-secrets": "^4.9.0", 9158 - "@azure/storage-blob": "^12.26.0", 9159 - "@capacitor/preferences": "^6 || ^7 || ^8", 9160 - "@deno/kv": ">=0.9.0", 9161 - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", 9162 - "@planetscale/database": "^1.19.0", 9163 - "@upstash/redis": "^1.34.3", 9164 - "@vercel/blob": ">=0.27.1", 9165 - "@vercel/functions": "^2.2.12 || ^3.0.0", 9166 - "@vercel/kv": "^1 || ^2 || ^3", 9167 - "aws4fetch": "^1.0.20", 9168 - "db0": ">=0.2.1", 9169 - "idb-keyval": "^6.2.1", 9170 - "ioredis": "^5.4.2", 9171 - "uploadthing": "^7.4.4" 9172 - }, 9173 - "peerDependenciesMeta": { 9174 - "@azure/app-configuration": { 9175 - "optional": true 9176 - }, 9177 - "@azure/cosmos": { 9178 - "optional": true 9179 - }, 9180 - "@azure/data-tables": { 9181 - "optional": true 9182 - }, 9183 - "@azure/identity": { 9184 - "optional": true 9185 - }, 9186 - "@azure/keyvault-secrets": { 9187 - "optional": true 9188 - }, 9189 - "@azure/storage-blob": { 9190 - "optional": true 9191 - }, 9192 - "@capacitor/preferences": { 9193 - "optional": true 9194 - }, 9195 - "@deno/kv": { 9196 - "optional": true 9197 - }, 9198 - "@netlify/blobs": { 9199 - "optional": true 9200 - }, 9201 - "@planetscale/database": { 9202 - "optional": true 9203 - }, 9204 - "@upstash/redis": { 9205 - "optional": true 9206 - }, 9207 - "@vercel/blob": { 9208 - "optional": true 9209 - }, 9210 - "@vercel/functions": { 9211 - "optional": true 9212 - }, 9213 - "@vercel/kv": { 9214 - "optional": true 9215 - }, 9216 - "aws4fetch": { 9217 - "optional": true 9218 - }, 9219 - "db0": { 9220 - "optional": true 9221 - }, 9222 - "idb-keyval": { 9223 - "optional": true 9224 - }, 9225 - "ioredis": { 9226 - "optional": true 9227 - }, 9228 - "uploadthing": { 9229 - "optional": true 9230 - } 9231 - } 9232 - }, 9233 - "node_modules/unstorage/node_modules/chokidar": { 9234 - "version": "5.0.0", 9235 - "license": "MIT", 9236 - "dependencies": { 9237 - "readdirp": "^5.0.0" 9238 - }, 9239 - "engines": { 9240 - "node": ">= 20.19.0" 9241 - }, 9242 - "funding": { 9243 - "url": "https://paulmillr.com/funding/" 9244 - } 9245 - }, 9246 - "node_modules/unstorage/node_modules/chokidar/node_modules/readdirp": { 9247 - "version": "5.0.0", 9248 - "license": "MIT", 9249 - "engines": { 9250 - "node": ">= 20.19.0" 9251 - }, 9252 - "funding": { 9253 - "type": "individual", 9254 - "url": "https://paulmillr.com/funding/" 9255 - } 9256 - }, 9257 - "node_modules/update-browserslist-db": { 9258 - "version": "1.2.3", 9259 - "funding": [ 9260 - { 9261 - "type": "opencollective", 9262 - "url": "https://opencollective.com/browserslist" 9263 - }, 9264 - { 9265 - "type": "tidelift", 9266 - "url": "https://tidelift.com/funding/github/npm/browserslist" 9267 - }, 9268 - { 9269 - "type": "github", 9270 - "url": "https://github.com/sponsors/ai" 9271 - } 9272 - ], 9273 - "license": "MIT", 9274 - "dependencies": { 9275 - "escalade": "^3.2.0", 9276 - "picocolors": "^1.1.1" 9277 - }, 9278 - "bin": { 9279 - "update-browserslist-db": "cli.js" 9280 - }, 9281 - "peerDependencies": { 9282 - "browserslist": ">= 4.21.0" 9283 - } 9284 - }, 9285 - "node_modules/uri-js": { 9286 - "version": "4.4.1", 9287 - "dev": true, 9288 - "license": "BSD-2-Clause", 9289 - "dependencies": { 9290 - "punycode": "^2.1.0" 9291 - } 9292 - }, 9293 - "node_modules/util-deprecate": { 9294 - "version": "1.0.2", 9295 - "license": "MIT" 9296 - }, 9297 - "node_modules/vfile": { 9298 - "version": "6.0.3", 9299 - "license": "MIT", 9300 - "dependencies": { 9301 - "@types/unist": "^3.0.0", 9302 - "vfile-message": "^4.0.0" 9303 - }, 9304 - "funding": { 9305 - "type": "opencollective", 9306 - "url": "https://opencollective.com/unified" 9307 - } 9308 - }, 9309 - "node_modules/vfile-location": { 9310 - "version": "5.0.3", 9311 - "license": "MIT", 9312 - "dependencies": { 9313 - "@types/unist": "^3.0.0", 9314 - "vfile": "^6.0.0" 9315 - }, 9316 - "funding": { 9317 - "type": "opencollective", 9318 - "url": "https://opencollective.com/unified" 9319 - } 9320 - }, 9321 - "node_modules/vfile-message": { 9322 - "version": "4.0.3", 9323 - "license": "MIT", 9324 - "dependencies": { 9325 - "@types/unist": "^3.0.0", 9326 - "unist-util-stringify-position": "^4.0.0" 9327 - }, 9328 - "funding": { 9329 - "type": "opencollective", 9330 - "url": "https://opencollective.com/unified" 9331 - } 9332 - }, 9333 - "node_modules/vite": { 9334 - "version": "6.4.1", 9335 - "license": "MIT", 9336 - "dependencies": { 9337 - "esbuild": "^0.25.0", 9338 - "fdir": "^6.4.4", 9339 - "picomatch": "^4.0.2", 9340 - "postcss": "^8.5.3", 9341 - "rollup": "^4.34.9", 9342 - "tinyglobby": "^0.2.13" 9343 - }, 9344 - "bin": { 9345 - "vite": "bin/vite.js" 9346 - }, 9347 - "engines": { 9348 - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 9349 - }, 9350 - "funding": { 9351 - "url": "https://github.com/vitejs/vite?sponsor=1" 9352 - }, 9353 - "optionalDependencies": { 9354 - "fsevents": "~2.3.3" 9355 - }, 9356 - "peerDependencies": { 9357 - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 9358 - "jiti": ">=1.21.0", 9359 - "less": "*", 9360 - "lightningcss": "^1.21.0", 9361 - "sass": "*", 9362 - "sass-embedded": "*", 9363 - "stylus": "*", 9364 - "sugarss": "*", 9365 - "terser": "^5.16.0", 9366 - "tsx": "^4.8.1", 9367 - "yaml": "^2.4.2" 9368 - }, 9369 - "peerDependenciesMeta": { 9370 - "@types/node": { 9371 - "optional": true 9372 - }, 9373 - "jiti": { 9374 - "optional": true 9375 - }, 9376 - "less": { 9377 - "optional": true 9378 - }, 9379 - "lightningcss": { 9380 - "optional": true 9381 - }, 9382 - "sass": { 9383 - "optional": true 9384 - }, 9385 - "sass-embedded": { 9386 - "optional": true 9387 - }, 9388 - "stylus": { 9389 - "optional": true 9390 - }, 9391 - "sugarss": { 9392 - "optional": true 9393 - }, 9394 - "terser": { 9395 - "optional": true 9396 - }, 9397 - "tsx": { 9398 - "optional": true 9399 - }, 9400 - "yaml": { 9401 - "optional": true 9402 - } 9403 - } 9404 - }, 9405 - "node_modules/vitefu": { 9406 - "version": "1.1.1", 9407 - "license": "MIT", 9408 - "workspaces": [ 9409 - "tests/deps/*", 9410 - "tests/projects/*", 9411 - "tests/projects/workspace/packages/*" 9412 - ], 9413 - "peerDependencies": { 9414 - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" 9415 - }, 9416 - "peerDependenciesMeta": { 9417 - "vite": { 9418 - "optional": true 9419 - } 9420 - } 9421 - }, 9422 - "node_modules/web-namespaces": { 9423 - "version": "2.0.1", 9424 - "license": "MIT", 9425 - "funding": { 9426 - "type": "github", 9427 - "url": "https://github.com/sponsors/wooorm" 9428 - } 9429 - }, 9430 - "node_modules/which": { 9431 - "version": "2.0.2", 9432 - "dev": true, 9433 - "license": "ISC", 9434 - "dependencies": { 9435 - "isexe": "^2.0.0" 9436 - }, 9437 - "bin": { 9438 - "node-which": "bin/node-which" 9439 - }, 9440 - "engines": { 9441 - "node": ">= 8" 9442 - } 9443 - }, 9444 - "node_modules/which-boxed-primitive": { 9445 - "version": "1.1.1", 9446 - "dev": true, 9447 - "license": "MIT", 9448 - "dependencies": { 9449 - "is-bigint": "^1.1.0", 9450 - "is-boolean-object": "^1.2.1", 9451 - "is-number-object": "^1.1.1", 9452 - "is-string": "^1.1.1", 9453 - "is-symbol": "^1.1.1" 9454 - }, 9455 - "engines": { 9456 - "node": ">= 0.4" 9457 - }, 9458 - "funding": { 9459 - "url": "https://github.com/sponsors/ljharb" 9460 - } 9461 - }, 9462 - "node_modules/which-builtin-type": { 9463 - "version": "1.2.1", 9464 - "dev": true, 9465 - "license": "MIT", 9466 - "dependencies": { 9467 - "call-bound": "^1.0.2", 9468 - "function.prototype.name": "^1.1.6", 9469 - "has-tostringtag": "^1.0.2", 9470 - "is-async-function": "^2.0.0", 9471 - "is-date-object": "^1.1.0", 9472 - "is-finalizationregistry": "^1.1.0", 9473 - "is-generator-function": "^1.0.10", 9474 - "is-regex": "^1.2.1", 9475 - "is-weakref": "^1.0.2", 9476 - "isarray": "^2.0.5", 9477 - "which-boxed-primitive": "^1.1.0", 9478 - "which-collection": "^1.0.2", 9479 - "which-typed-array": "^1.1.16" 9480 - }, 9481 - "engines": { 9482 - "node": ">= 0.4" 9483 - }, 9484 - "funding": { 9485 - "url": "https://github.com/sponsors/ljharb" 9486 - } 9487 - }, 9488 - "node_modules/which-collection": { 9489 - "version": "1.0.2", 9490 - "dev": true, 9491 - "license": "MIT", 9492 - "dependencies": { 9493 - "is-map": "^2.0.3", 9494 - "is-set": "^2.0.3", 9495 - "is-weakmap": "^2.0.2", 9496 - "is-weakset": "^2.0.3" 9497 - }, 9498 - "engines": { 9499 - "node": ">= 0.4" 9500 - }, 9501 - "funding": { 9502 - "url": "https://github.com/sponsors/ljharb" 9503 - } 9504 - }, 9505 - "node_modules/which-pm-runs": { 9506 - "version": "1.1.0", 9507 - "license": "MIT", 9508 - "engines": { 9509 - "node": ">=4" 9510 - } 9511 - }, 9512 - "node_modules/which-typed-array": { 9513 - "version": "1.1.20", 9514 - "dev": true, 9515 - "license": "MIT", 9516 - "dependencies": { 9517 - "available-typed-arrays": "^1.0.7", 9518 - "call-bind": "^1.0.8", 9519 - "call-bound": "^1.0.4", 9520 - "for-each": "^0.3.5", 9521 - "get-proto": "^1.0.1", 9522 - "gopd": "^1.2.0", 9523 - "has-tostringtag": "^1.0.2" 9524 - }, 9525 - "engines": { 9526 - "node": ">= 0.4" 9527 - }, 9528 - "funding": { 9529 - "url": "https://github.com/sponsors/ljharb" 9530 - } 9531 - }, 9532 - "node_modules/widest-line": { 9533 - "version": "5.0.0", 9534 - "license": "MIT", 9535 - "dependencies": { 9536 - "string-width": "^7.0.0" 9537 - }, 9538 - "engines": { 9539 - "node": ">=18" 9540 - }, 9541 - "funding": { 9542 - "url": "https://github.com/sponsors/sindresorhus" 9543 - } 9544 - }, 9545 - "node_modules/word-wrap": { 9546 - "version": "1.2.5", 9547 - "dev": true, 9548 - "license": "MIT", 9549 - "engines": { 9550 - "node": ">=0.10.0" 9551 - } 9552 - }, 9553 - "node_modules/wrap-ansi": { 9554 - "version": "9.0.2", 9555 - "license": "MIT", 9556 - "dependencies": { 9557 - "ansi-styles": "^6.2.1", 9558 - "string-width": "^7.0.0", 9559 - "strip-ansi": "^7.1.0" 9560 - }, 9561 - "engines": { 9562 - "node": ">=18" 9563 - }, 9564 - "funding": { 9565 - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 9566 - } 9567 - }, 9568 - "node_modules/xxhash-wasm": { 9569 - "version": "1.1.0", 9570 - "license": "MIT" 9571 - }, 9572 - "node_modules/yallist": { 9573 - "version": "3.1.1", 9574 - "license": "ISC" 9575 - }, 9576 - "node_modules/yaml": { 9577 - "version": "2.8.2", 9578 - "license": "ISC", 9579 - "bin": { 9580 - "yaml": "bin.mjs" 9581 - }, 9582 - "engines": { 9583 - "node": ">= 14.6" 9584 - }, 9585 - "funding": { 9586 - "url": "https://github.com/sponsors/eemeli" 9587 - } 9588 - }, 9589 - "node_modules/yargs-parser": { 9590 - "version": "21.1.1", 9591 - "license": "ISC", 9592 - "engines": { 9593 - "node": ">=12" 9594 - } 9595 - }, 9596 - "node_modules/yocto-queue": { 9597 - "version": "1.2.2", 9598 - "license": "MIT", 9599 - "engines": { 9600 - "node": ">=12.20" 9601 - }, 9602 - "funding": { 9603 - "url": "https://github.com/sponsors/sindresorhus" 9604 - } 9605 - }, 9606 - "node_modules/yocto-spinner": { 9607 - "version": "0.2.3", 9608 - "license": "MIT", 9609 - "dependencies": { 9610 - "yoctocolors": "^2.1.1" 9611 - }, 9612 - "engines": { 9613 - "node": ">=18.19" 9614 - }, 9615 - "funding": { 9616 - "url": "https://github.com/sponsors/sindresorhus" 9617 - } 9618 - }, 9619 - "node_modules/yoctocolors": { 9620 - "version": "2.1.2", 9621 - "license": "MIT", 9622 - "engines": { 9623 - "node": ">=18" 9624 - }, 9625 - "funding": { 9626 - "url": "https://github.com/sponsors/sindresorhus" 9627 - } 9628 - }, 9629 - "node_modules/yoga-layout": { 9630 - "version": "3.2.1", 9631 - "license": "MIT" 9632 - }, 9633 - "node_modules/zod": { 9634 - "version": "3.25.76", 9635 - "license": "MIT", 9636 - "funding": { 9637 - "url": "https://github.com/sponsors/colinhacks" 9638 - } 9639 - }, 9640 - "node_modules/zod-to-json-schema": { 9641 - "version": "3.25.1", 9642 - "license": "ISC", 9643 - "peerDependencies": { 9644 - "zod": "^3.25 || ^4" 9645 - } 9646 - }, 9647 - "node_modules/zod-to-ts": { 9648 - "version": "1.2.0", 9649 - "peerDependencies": { 9650 - "typescript": "^4.9.4 || ^5.0.2", 9651 - "zod": "^3" 9652 - } 9653 - }, 9654 - "node_modules/zod-validation-error": { 9655 - "version": "4.0.2", 9656 - "dev": true, 9657 - "license": "MIT", 9658 - "engines": { 9659 - "node": ">=18.0.0" 9660 - }, 9661 - "peerDependencies": { 9662 - "zod": "^3.25.0 || ^4.0.0" 9663 - } 9664 - }, 9665 - "node_modules/zwitch": { 9666 - "version": "2.0.4", 9667 - "license": "MIT", 9668 - "funding": { 9669 - "type": "github", 9670 - "url": "https://github.com/sponsors/wooorm" 9671 - } 9672 - } 9673 - } 9674 - }
+3 -4
web/package.json
··· 10 10 "lint": "eslint 'src/**/*.{ts,tsx,js,jsx}' --fix" 11 11 }, 12 12 "dependencies": { 13 - "@astrojs/node": "^9.5.2", 14 - "@astrojs/react": "^4.4.2", 13 + "@astrojs/node": "^10.0.3", 14 + "@astrojs/react": "^5.0.1", 15 15 "@astrojs/tailwind": "^6.0.2", 16 16 "@nanostores/react": "^1.0.0", 17 17 "@resvg/resvg-js": "^2.6.2", 18 18 "@tailwindcss/vite": "^4.1.18", 19 - "astro": "^5.17.1", 19 + "astro": "^6.0.8", 20 20 "autoprefixer": "^10.4.24", 21 21 "clsx": "^2.1.1", 22 22 "date-fns": "^4.1.0", ··· 26 26 "postcss": "^8.5.6", 27 27 "react": "^19.2.4", 28 28 "react-dom": "^19.2.4", 29 - "react-router-dom": "^7.13.0", 30 29 "satori": "^0.19.2", 31 30 "tailwind-merge": "^3.4.0", 32 31 "tailwindcss": "^3.4.19"
-257
web/src/App.tsx
··· 1 - import React from "react"; 2 - import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; 3 - import { initAuth, $user } from "./store/auth"; 4 - import { loadPreferences } from "./store/preferences"; 5 - import { useStore } from "@nanostores/react"; 6 - 7 - import AppLayout from "./layouts/AppLayout"; 8 - import Feed from "./views/core/Feed"; 9 - import Login from "./views/auth/Login"; 10 - import Notifications from "./views/core/Notifications"; 11 - import Collections from "./views/collections/Collections"; 12 - import Settings from "./views/core/Settings"; 13 - import NewAnnotationPage from "./views/core/New"; 14 - import MasonryFeed from "./components/feed/MasonryFeed"; 15 - import { 16 - ProfileWrapper, 17 - SelfProfileWrapper, 18 - CollectionDetailWrapper, 19 - AnnotationDetailWrapper, 20 - UserUrlWrapper, 21 - UrlWrapper, 22 - } from "./routes/wrappers"; 23 - import About from "./views/About"; 24 - import AdminModeration from "./views/core/AdminModeration"; 25 - import Search from "./views/core/Search"; 26 - import Discover from "./views/core/Discover"; 27 - 28 - function RootRoute() { 29 - const user = useStore($user); 30 - 31 - if (user) { 32 - return <Navigate to="/home" replace />; 33 - } 34 - 35 - return <About />; 36 - } 37 - 38 - export default function App() { 39 - React.useEffect(() => { 40 - initAuth(); 41 - loadPreferences(); 42 - }, []); 43 - 44 - return ( 45 - <BrowserRouter> 46 - <Routes> 47 - <Route path="/" element={<RootRoute />} /> 48 - <Route path="/login" element={<Login />} /> 49 - <Route path="/about" element={<About />} /> 50 - <Route path="/auth/*" element={<div>Redirecting...</div>} /> 51 - 52 - <Route 53 - path="/home" 54 - element={ 55 - <AppLayout> 56 - <Feed initialType="all" /> 57 - </AppLayout> 58 - } 59 - /> 60 - <Route path="/my-feed" element={<Navigate to="/home" replace />} /> 61 - 62 - <Route 63 - path="/search" 64 - element={ 65 - <AppLayout> 66 - <Search /> 67 - </AppLayout> 68 - } 69 - /> 70 - 71 - <Route 72 - path="/discover" 73 - element={ 74 - <AppLayout> 75 - <Discover /> 76 - </AppLayout> 77 - } 78 - /> 79 - 80 - <Route 81 - path="/annotations" 82 - element={ 83 - <AppLayout> 84 - <MasonryFeed 85 - motivation="commenting" 86 - emptyMessage="You haven't annotated anything yet." 87 - showTabs={true} 88 - title="Annotations" 89 - /> 90 - </AppLayout> 91 - } 92 - /> 93 - <Route 94 - path="/bookmarks" 95 - element={ 96 - <AppLayout> 97 - <MasonryFeed 98 - motivation="bookmarking" 99 - emptyMessage="You haven't bookmarked anything yet." 100 - showTabs={true} 101 - title="Bookmarks" 102 - /> 103 - </AppLayout> 104 - } 105 - /> 106 - <Route 107 - path="/highlights" 108 - element={ 109 - <AppLayout> 110 - <MasonryFeed 111 - motivation="highlighting" 112 - emptyMessage="You haven't highlighted anything yet." 113 - showTabs={true} 114 - title="Highlights" 115 - /> 116 - </AppLayout> 117 - } 118 - /> 119 - 120 - <Route 121 - path="/collections" 122 - element={ 123 - <AppLayout> 124 - <Collections /> 125 - </AppLayout> 126 - } 127 - /> 128 - <Route 129 - path="/:handle/collection/:rkey" 130 - element={ 131 - <AppLayout> 132 - <CollectionDetailWrapper /> 133 - </AppLayout> 134 - } 135 - /> 136 - <Route 137 - path="/collections/:rkey" 138 - element={ 139 - <AppLayout> 140 - <CollectionDetailWrapper /> 141 - </AppLayout> 142 - } 143 - /> 144 - 145 - <Route 146 - path="/profile/:did" 147 - element={ 148 - <AppLayout> 149 - <ProfileWrapper /> 150 - </AppLayout> 151 - } 152 - /> 153 - <Route 154 - path="/profile" 155 - element={ 156 - <AppLayout> 157 - <SelfProfileWrapper /> 158 - </AppLayout> 159 - } 160 - /> 161 - 162 - <Route 163 - path="/new" 164 - element={ 165 - <AppLayout> 166 - <NewAnnotationPage /> 167 - </AppLayout> 168 - } 169 - /> 170 - <Route 171 - path="/at/:did/:rkey" 172 - element={ 173 - <AppLayout> 174 - <AnnotationDetailWrapper /> 175 - </AppLayout> 176 - } 177 - /> 178 - <Route 179 - path="/annotation/:uri" 180 - element={ 181 - <AppLayout> 182 - <AnnotationDetailWrapper /> 183 - </AppLayout> 184 - } 185 - /> 186 - <Route 187 - path="/:handle/annotation/:rkey" 188 - element={ 189 - <AppLayout> 190 - <AnnotationDetailWrapper /> 191 - </AppLayout> 192 - } 193 - /> 194 - <Route 195 - path="/:handle/highlight/:rkey" 196 - element={ 197 - <AppLayout> 198 - <AnnotationDetailWrapper /> 199 - </AppLayout> 200 - } 201 - /> 202 - <Route 203 - path="/:handle/bookmark/:rkey" 204 - element={ 205 - <AppLayout> 206 - <AnnotationDetailWrapper /> 207 - </AppLayout> 208 - } 209 - /> 210 - <Route 211 - path="/:handle/url/*" 212 - element={ 213 - <AppLayout> 214 - <UserUrlWrapper /> 215 - </AppLayout> 216 - } 217 - /> 218 - <Route 219 - path="/url/*" 220 - element={ 221 - <AppLayout> 222 - <UrlWrapper /> 223 - </AppLayout> 224 - } 225 - /> 226 - 227 - <Route 228 - path="/admin/moderation" 229 - element={ 230 - <AppLayout> 231 - <AdminModeration /> 232 - </AppLayout> 233 - } 234 - /> 235 - 236 - <Route 237 - path="/settings" 238 - element={ 239 - <AppLayout> 240 - <Settings /> 241 - </AppLayout> 242 - } 243 - /> 244 - <Route 245 - path="/notifications" 246 - element={ 247 - <AppLayout> 248 - <Notifications /> 249 - </AppLayout> 250 - } 251 - /> 252 - 253 - <Route path="*" element={<Navigate to="/home" replace />} /> 254 - </Routes> 255 - </BrowserRouter> 256 - ); 257 - }
+35 -24
web/src/components/common/Card.tsx
··· 43 43 ContentLabel, 44 44 LabelVisibility, 45 45 } from "../../types"; 46 - import { Link } from "react-router-dom"; 46 + 47 47 import { Avatar } from "../ui"; 48 48 import CollectionIcon from "./CollectionIcon"; 49 49 import ProfileHoverCard from "./ProfileHoverCard"; ··· 318 318 : null; 319 319 320 320 const decodeHTMLEntities = (text: string) => { 321 - const textarea = document.createElement("textarea"); 322 - textarea.innerHTML = text; 323 - return textarea.value; 321 + const entities: Record<string, string> = { 322 + "&amp;": "&", 323 + "&lt;": "<", 324 + "&gt;": ">", 325 + "&quot;": '"', 326 + "&#39;": "'", 327 + "&#x27;": "'", 328 + "&#x2F;": "/", 329 + "&nbsp;": " ", 330 + }; 331 + return text.replace( 332 + /&(?:amp|lt|gt|quot|nbsp|#39|#x27|#x2F);/g, 333 + (match) => entities[match] || match, 334 + ); 324 335 }; 325 336 326 337 const displayTitle = decodeHTMLEntities( ··· 339 350 {item.addedBy && item.addedBy.did !== item.author?.did ? ( 340 351 <> 341 352 <ProfileHoverCard did={item.addedBy.did}> 342 - <Link 343 - to={`/profile/${item.addedBy.did}`} 353 + <a 354 + href={`/profile/${item.addedBy.did}`} 344 355 className="flex items-center gap-1.5 font-medium hover:text-primary-600 dark:hover:text-primary-400 transition-colors" 345 356 > 346 357 <Avatar ··· 351 362 <span> 352 363 {item.addedBy.displayName || `@${item.addedBy.handle}`} 353 364 </span> 354 - </Link> 365 + </a> 355 366 </ProfileHoverCard> 356 367 <span>added to</span> 357 368 </> ··· 370 381 {index > 0 && index === item.context!.length - 1 && ( 371 382 <span>and</span> 372 383 )} 373 - <Link 374 - to={`/${item.addedBy?.handle || ""}/collection/${(col.uri || "").split("/").pop()}`} 384 + <a 385 + href={`/${item.addedBy?.handle || ""}/collection/${(col.uri || "").split("/").pop()}`} 375 386 className="inline-flex items-center gap-1 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" 376 387 > 377 388 <CollectionIcon icon={col.icon} size={14} /> 378 389 <span className="font-medium">{col.name}</span> 379 - </Link> 390 + </a> 380 391 </React.Fragment> 381 392 )) 382 393 ) : ( 383 - <Link 384 - to={`/${item.addedBy?.handle || ""}/collection/${(item.collection!.uri || "").split("/").pop()}`} 394 + <a 395 + href={`/${item.addedBy?.handle || ""}/collection/${(item.collection!.uri || "").split("/").pop()}`} 385 396 className="inline-flex items-center gap-1 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" 386 397 > 387 398 <CollectionIcon icon={item.collection!.icon} size={14} /> 388 399 <span className="font-medium">{item.collection!.name}</span> 389 - </Link> 400 + </a> 390 401 )} 391 402 </div> 392 403 )} 393 404 394 405 <div className="flex items-start gap-3"> 395 406 <ProfileHoverCard did={item.author?.did}> 396 - <Link to={`/profile/${item.author?.did}`} className="shrink-0"> 407 + <a href={`/profile/${item.author?.did}`} className="shrink-0"> 397 408 <div className="rounded-full overflow-hidden"> 398 409 <div 399 410 className={clsx( ··· 410 421 /> 411 422 </div> 412 423 </div> 413 - </Link> 424 + </a> 414 425 </ProfileHoverCard> 415 426 416 427 <div className="flex-1 min-w-0"> 417 428 <div className="flex items-center gap-1.5 flex-wrap"> 418 429 <ProfileHoverCard did={item.author?.did}> 419 - <Link 420 - to={`/profile/${item.author?.did}`} 430 + <a 431 + href={`/profile/${item.author?.did}`} 421 432 className="font-semibold text-surface-900 dark:text-white text-[15px] hover:underline" 422 433 > 423 434 {item.author?.displayName || item.author?.handle} 424 - </Link> 435 + </a> 425 436 </ProfileHoverCard> 426 437 <span className="text-surface-400 dark:text-surface-500 text-sm"> 427 438 @{item.author?.handle} ··· 652 663 {item.tags && item.tags.length > 0 && ( 653 664 <div className="flex flex-wrap gap-2 mt-3"> 654 665 {item.tags.map((tag) => ( 655 - <Link 666 + <a 656 667 key={tag} 657 - to={`/home?tag=${encodeURIComponent(tag)}`} 668 + href={`/home?tag=${encodeURIComponent(tag)}`} 658 669 className="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-surface-100 dark:bg-surface-800 text-xs font-medium text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-700 hover:text-primary-600 dark:hover:text-primary-400 transition-colors" 659 670 onClick={(e) => e.stopPropagation()} 660 671 > 661 672 <Tag size={10} /> 662 673 <span>{tag}</span> 663 - </Link> 674 + </a> 664 675 ))} 665 676 </div> 666 677 )} ··· 681 692 </button> 682 693 683 694 {type === "annotation" && ( 684 - <Link 685 - to={detailUrl} 695 + <a 696 + href={detailUrl} 686 697 className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-sm text-surface-400 dark:text-surface-500 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-all" 687 698 > 688 699 <MessageSquare size={16} /> 689 700 {(item.replyCount || 0) > 0 && ( 690 701 <span className="text-xs font-medium">{item.replyCount}</span> 691 702 )} 692 - </Link> 703 + </a> 693 704 )} 694 705 695 706 {user && (
+6 -7
web/src/components/common/ProfileHoverCard.tsx
··· 1 1 import React, { useState, useEffect, useRef } from "react"; 2 - import { Link } from "react-router-dom"; 3 2 import Avatar from "../ui/Avatar"; 4 3 import RichText from "./RichText"; 5 4 import { getProfile } from "../../api/client"; ··· 114 113 </div> 115 114 ) : profile ? ( 116 115 <div className="space-y-3"> 117 - <Link 118 - to={`/profile/${profile.did}`} 116 + <a 117 + href={`/profile/${profile.did}`} 119 118 className="flex items-start gap-3 group" 120 119 > 121 120 <Avatar ··· 132 131 @{profile.handle} 133 132 </p> 134 133 </div> 135 - </Link> 134 + </a> 136 135 137 136 {profile.description && ( 138 137 <p className="text-sm text-surface-600 dark:text-surface-300 whitespace-pre-line line-clamp-3"> ··· 140 139 </p> 141 140 )} 142 141 143 - <Link 144 - to={`/profile/${profile.did}`} 142 + <a 143 + href={`/profile/${profile.did}`} 145 144 className="block w-full text-center py-2 bg-primary-600 hover:bg-primary-700 text-white text-sm font-medium rounded-lg transition-colors" 146 145 > 147 146 View Profile 148 - </Link> 147 + </a> 149 148 </div> 150 149 ) : ( 151 150 <p className="text-sm text-surface-500 text-center py-2">
+3 -4
web/src/components/common/RichText.tsx
··· 1 1 import React from "react"; 2 - import { Link } from "react-router-dom"; 3 2 import ExternalLinkModal from "../modals/ExternalLinkModal"; 4 3 import { useStore } from "@nanostores/react"; 5 4 import { $preferences } from "../../store/preferences"; ··· 138 137 } 139 138 140 139 finalParts.push( 141 - <Link 140 + <a 142 141 key={`mention-${partIndex}-${startIndex}`} 143 - to={`/profile/${handle}`} 142 + href={`/profile/${handle}`} 144 143 className="text-primary-600 dark:text-primary-400 hover:underline" 145 144 onClick={(e) => e.stopPropagation()} 146 145 > 147 146 @{handle} 148 - </Link>, 147 + </a>, 149 148 ); 150 149 151 150 lastMentionIndex = startIndex + fullMatch.length;
+3
web/src/components/modals/SignUpModal.tsx
··· 195 195 try { 196 196 const result = await startSignup(serviceUrl); 197 197 if (result.authorizationUrl) { 198 + const url = new URL(result.authorizationUrl); 199 + if (url.protocol !== "https:") 200 + throw new Error("Invalid authorization URL"); 198 201 window.location.href = result.authorizationUrl; 199 202 } 200 203 } catch (err) {
+90 -53
web/src/components/navigation/MobileNav.tsx
··· 15 15 X, 16 16 } from "lucide-react"; 17 17 import React, { useEffect, useState } from "react"; 18 - import { Link, useLocation } from "react-router-dom"; 19 18 import { getUnreadNotificationCount } from "../../api/client"; 20 19 import { $user, logout } from "../../store/auth"; 20 + import type { UserProfile } from "../../types"; 21 21 import { AppleIcon } from "../common/Icons"; 22 22 23 - export default function MobileNav() { 24 - const user = useStore($user); 25 - const location = useLocation(); 23 + interface MobileNavProps { 24 + initialUser?: UserProfile | null; 25 + currentPath?: string; 26 + } 27 + 28 + export default function MobileNav({ 29 + initialUser, 30 + currentPath: initialPath, 31 + }: MobileNavProps) { 32 + const storeUser = useStore($user); 33 + const user = storeUser || initialUser || null; 34 + const [currentPath, setCurrentPath] = useState(initialPath || "/"); 26 35 const [isMenuOpen, setIsMenuOpen] = useState(false); 27 36 const [unreadCount, setUnreadCount] = useState(0); 28 37 29 38 const isAuthenticated = !!user; 30 39 40 + useEffect(() => { 41 + if (initialUser && !storeUser) { 42 + $user.set(initialUser); 43 + } 44 + }, [initialUser, storeUser]); 45 + 46 + useEffect(() => { 47 + const handler = () => setCurrentPath(window.location.pathname); 48 + document.addEventListener("astro:page-load", handler); 49 + return () => document.removeEventListener("astro:page-load", handler); 50 + }, []); 51 + 31 52 const isActive = (path: string) => { 32 - if (path === "/") return location.pathname === "/"; 33 - return location.pathname.startsWith(path); 53 + if (path === "/") return currentPath === "/"; 54 + return currentPath.startsWith(path); 34 55 }; 35 56 36 57 useEffect(() => { ··· 57 78 <div className="p-4 space-y-1"> 58 79 {isAuthenticated && user ? ( 59 80 <> 60 - <Link 61 - to={`/profile/${user.did}`} 81 + <a 82 + href={`/profile/${user.did}`} 62 83 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors" 63 84 onClick={closeMenu} 64 85 > ··· 81 102 @{user.handle} 82 103 </span> 83 104 </div> 84 - </Link> 105 + </a> 85 106 86 107 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 87 108 88 - <Link 89 - to="/annotations" 109 + <a 110 + href="/annotations" 90 111 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 91 112 onClick={closeMenu} 92 113 > 93 114 <MessageSquareText size={20} /> 94 115 <span>Annotations</span> 95 - </Link> 116 + </a> 96 117 97 - <Link 98 - to="/highlights" 118 + <a 119 + href="/highlights" 99 120 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 100 121 onClick={closeMenu} 101 122 > 102 123 <Highlighter size={20} /> 103 124 <span>Highlights</span> 104 - </Link> 125 + </a> 105 126 106 - <Link 107 - to="/bookmarks" 127 + <a 128 + href="/bookmarks" 108 129 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 109 130 onClick={closeMenu} 110 131 > 111 132 <Bookmark size={20} /> 112 133 <span>Bookmarks</span> 113 - </Link> 134 + </a> 114 135 115 - <Link 116 - to="/collections" 136 + <a 137 + href="/collections" 117 138 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 118 139 onClick={closeMenu} 119 140 > 120 141 <Folder size={20} /> 121 142 <span>Collections</span> 122 - </Link> 143 + </a> 123 144 124 - <Link 125 - to="/settings" 145 + <a 146 + href="/settings" 126 147 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 127 148 onClick={closeMenu} 128 149 > 129 150 <Settings size={20} /> 130 151 <span>Settings</span> 131 - </Link> 152 + </a> 132 153 133 154 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 134 155 ··· 158 179 </> 159 180 ) : ( 160 181 <> 161 - <Link 162 - to="/login" 182 + <a 183 + href="/login" 163 184 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 164 185 onClick={closeMenu} 165 186 > 166 187 <User size={20} /> 167 188 <span>Sign In</span> 168 - </Link> 169 - <Link 170 - to="/collections" 189 + </a> 190 + <a 191 + href="/collections" 171 192 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 172 193 onClick={closeMenu} 173 194 > 174 195 <Folder size={20} /> 175 196 <span>Collections</span> 176 - </Link> 177 - <Link 178 - to="/settings" 197 + </a> 198 + <a 199 + href="/settings" 179 200 className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200" 180 201 onClick={closeMenu} 181 202 > 182 203 <Settings size={20} /> 183 204 <span>Settings</span> 184 - </Link> 205 + </a> 185 206 186 207 <div className="h-px bg-surface-200 dark:bg-surface-700 my-2" /> 187 208 ··· 201 222 </div> 202 223 )} 203 224 204 - <nav className="fixed bottom-0 left-0 right-0 h-14 bg-white dark:bg-surface-900 border-t border-surface-200 dark:border-surface-700 flex items-center justify-around px-2 z-50 md:hidden safe-area-bottom"> 205 - <Link 206 - to="/home" 225 + <nav className="fixed bottom-0 left-0 right-0 h-14 bg-white/90 dark:bg-surface-900/90 backdrop-blur-md border-t border-surface-200 dark:border-surface-700 flex items-center justify-around px-2 z-50 md:hidden safe-area-bottom"> 226 + <a 227 + href="/home" 228 + data-astro-prefetch="viewport" 207 229 className={`flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 208 230 isActive("/home") 209 231 ? "text-primary-600" 210 232 : "text-surface-500 hover:text-surface-700" 211 233 }`} 212 - onClick={closeMenu} 234 + onClick={() => { 235 + setCurrentPath("/home"); 236 + closeMenu(); 237 + }} 213 238 > 214 239 <Home size={24} strokeWidth={1.5} /> 215 - </Link> 240 + </a> 216 241 217 - <Link 218 - to="/search" 242 + <a 243 + href="/search" 244 + data-astro-prefetch="viewport" 219 245 className={`flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 220 246 isActive("/search") 221 247 ? "text-primary-600" 222 248 : "text-surface-500 hover:text-surface-700" 223 249 }`} 224 - onClick={closeMenu} 250 + onClick={() => { 251 + setCurrentPath("/search"); 252 + closeMenu(); 253 + }} 225 254 > 226 255 <Search size={24} strokeWidth={1.5} /> 227 - </Link> 256 + </a> 228 257 229 258 {isAuthenticated ? ( 230 259 <> 231 - <Link 232 - to="/new" 260 + <a 261 + href="/new" 262 + data-astro-prefetch="viewport" 233 263 className="flex items-center justify-center w-12 h-12 rounded-full bg-primary-600 text-white shadow-lg hover:bg-primary-500 transition-colors -mt-4" 234 - onClick={closeMenu} 264 + onClick={() => { 265 + setCurrentPath("/new"); 266 + closeMenu(); 267 + }} 235 268 > 236 269 <PenSquare size={20} strokeWidth={2} /> 237 - </Link> 270 + </a> 238 271 239 - <Link 240 - to="/notifications" 272 + <a 273 + href="/notifications" 274 + data-astro-prefetch="viewport" 241 275 className={`relative flex flex-col items-center justify-center w-14 h-14 rounded-xl transition-colors ${ 242 276 isActive("/notifications") 243 277 ? "text-primary-600" 244 278 : "text-surface-500 hover:text-surface-700" 245 279 }`} 246 - onClick={closeMenu} 280 + onClick={() => { 281 + setCurrentPath("/notifications"); 282 + closeMenu(); 283 + }} 247 284 > 248 285 <Bell size={24} strokeWidth={1.5} /> 249 286 {unreadCount > 0 && ( 250 287 <span className="absolute top-2 right-2 w-2 h-2 bg-red-500 rounded-full" /> 251 288 )} 252 - </Link> 289 + </a> 253 290 </> 254 291 ) : ( 255 - <Link 256 - to="/login" 292 + <a 293 + href="/login" 257 294 className="flex items-center justify-center w-12 h-12 rounded-full bg-primary-600 text-white shadow-lg hover:bg-primary-500 transition-colors -mt-4" 258 295 onClick={closeMenu} 259 296 > 260 297 <User size={20} strokeWidth={2} /> 261 - </Link> 298 + </a> 262 299 )} 263 300 264 301 <button
+13 -15
web/src/components/navigation/RightSidebar.tsx
··· 1 1 import React, { useCallback, useEffect, useRef, useState } from "react"; 2 - import { useNavigate } from "react-router-dom"; 3 2 import { Search, Coffee } from "lucide-react"; 4 3 import { 5 4 getTrendingTags, ··· 18 17 ); 19 18 } 20 19 20 + function navigate(path: string) { 21 + window.location.href = path; 22 + } 23 + 21 24 export default function RightSidebar() { 22 - const navigate = useNavigate(); 23 25 const [tags, setTags] = useState<Tag[]>([]); 24 26 const [browser] = useState<"chrome" | "firefox" | "edge" | "other">(() => { 25 27 if (typeof navigator === "undefined") return "other"; ··· 82 84 return () => document.removeEventListener("mousedown", handleClickOutside); 83 85 }, []); 84 86 85 - const selectSuggestion = useCallback( 86 - (actor: ActorSearchItem) => { 87 - isSelectionRef.current = true; 88 - setSearchQuery(""); 89 - setSuggestions([]); 90 - setShowSuggestions(false); 91 - navigate(`/profile/${encodeURIComponent(actor.handle)}`); 92 - }, 93 - [navigate], 94 - ); 87 + const selectSuggestion = useCallback((actor: ActorSearchItem) => { 88 + isSelectionRef.current = true; 89 + setSearchQuery(""); 90 + setSuggestions([]); 91 + setShowSuggestions(false); 92 + navigate(`/profile/${encodeURIComponent(actor.handle)}`); 93 + }, []); 95 94 96 95 const handleKeyDown = useCallback( 97 96 (e: React.KeyboardEvent) => { ··· 135 134 suggestions, 136 135 selectedIndex, 137 136 searchQuery, 138 - navigate, 139 137 selectSuggestion, 140 138 ], 141 139 ); ··· 178 176 suggestions.length > 0 && 179 177 setShowSuggestions(true) 180 178 } 181 - placeholder="Search..." 182 - className="w-full bg-surface-100 dark:bg-surface-800/80 rounded-lg pl-9 pr-4 py-2 text-sm text-surface-900 dark:text-white placeholder:text-surface-400 dark:placeholder:text-surface-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20 focus:bg-white dark:focus:bg-surface-800 transition-all border border-surface-200/60 dark:border-surface-700/60" 179 + placeholder="Search people, tags, URLs..." 180 + className="w-full bg-surface-100 dark:bg-surface-800/80 rounded-lg pl-9 pr-4 py-2 text-sm text-surface-900 dark:text-white placeholder:text-surface-400 dark:placeholder:text-surface-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20 focus:bg-white dark:focus:bg-surface-800 transition-all border border-transparent focus:border-surface-200 dark:focus:border-surface-700" 183 181 /> 184 182 185 183 {showSuggestions && suggestions.length > 0 && (
+49 -23
web/src/components/navigation/Sidebar.tsx
··· 19 19 import { $user, logout } from "../../store/auth"; 20 20 import { $theme, cycleTheme } from "../../store/theme"; 21 21 import { getUnreadNotificationCount } from "../../api/client"; 22 - import { Link, useLocation } from "react-router-dom"; 23 22 import { Avatar, CountBadge } from "../ui"; 23 + import type { UserProfile } from "../../types"; 24 24 25 - export default function Sidebar() { 26 - const user = useStore($user); 25 + interface SidebarProps { 26 + initialUser?: UserProfile | null; 27 + currentPath?: string; 28 + } 29 + 30 + export default function Sidebar({ 31 + initialUser, 32 + currentPath: initialPath, 33 + }: SidebarProps) { 34 + const storeUser = useStore($user); 35 + const user = storeUser || initialUser || null; 27 36 const theme = useStore($theme); 28 - const location = useLocation(); 29 - const currentPath = location.pathname; 37 + const [currentPath, setCurrentPath] = useState(initialPath || "/"); 30 38 const [unreadCount, setUnreadCount] = useState(0); 39 + 40 + useEffect(() => { 41 + if (initialUser && !storeUser) { 42 + $user.set(initialUser); 43 + } 44 + }, [initialUser, storeUser]); 45 + 46 + useEffect(() => { 47 + const handler = () => setCurrentPath(window.location.pathname); 48 + document.addEventListener("astro:page-load", handler); 49 + return () => document.removeEventListener("astro:page-load", handler); 50 + }, []); 51 + 52 + const handleNav = (href: string) => () => { 53 + setCurrentPath(href); 54 + }; 31 55 32 56 useEffect(() => { 33 57 if (!user) return; ··· 85 109 return ( 86 110 <aside className="sticky top-0 h-screen hidden md:flex flex-col justify-between py-6 px-2 lg:px-4 z-50 w-[68px] lg:w-[260px] transition-all duration-200"> 87 111 <div className="flex flex-col gap-6"> 88 - <Link 89 - to="/home" 112 + <a 113 + href="/home" 90 114 className="px-3 hover:opacity-80 transition-opacity w-fit flex items-center gap-2.5" 91 115 > 92 116 <img src="/logo.svg" alt="Margin" className="w-8 h-8" /> 93 - </Link> 117 + </a> 94 118 95 119 <nav className="flex flex-col gap-0.5"> 96 120 {navItems.map((item) => { ··· 98 122 currentPath === item.href || 99 123 (item.href !== "/home" && currentPath.startsWith(item.href)); 100 124 return ( 101 - <Link 125 + <a 102 126 key={item.href} 103 - to={item.href} 127 + href={item.href} 104 128 title={item.label} 129 + onClick={handleNav(item.href)} 130 + data-astro-prefetch="viewport" 105 131 className={`flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg transition-all duration-150 text-[14px] group ${ 106 132 isActive 107 133 ? "font-semibold text-primary-700 dark:text-primary-300 bg-primary-50 dark:bg-primary-950/40" ··· 117 143 {(item.badge ?? 0) > 0 && ( 118 144 <CountBadge count={item.badge ?? 0} /> 119 145 )} 120 - </Link> 146 + </a> 121 147 ); 122 148 })} 123 149 124 150 {user && ( 125 - <Link 126 - to="/new" 151 + <a 152 + href="/new" 127 153 title="New annotation" 128 154 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 mt-2 rounded-lg bg-primary-600 dark:bg-primary-500 text-white hover:bg-primary-700 dark:hover:bg-primary-400 transition-colors text-[14px] font-semibold" 129 155 > 130 156 <PenSquare size={20} strokeWidth={1.75} /> 131 157 <span className="hidden lg:inline">New</span> 132 - </Link> 158 + </a> 133 159 )} 134 160 </nav> 135 161 </div> ··· 156 182 157 183 {user ? ( 158 184 <> 159 - <Link 160 - to="/settings" 185 + <a 186 + href="/settings" 161 187 title="Settings" 162 188 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800 text-[13px] font-medium text-surface-500 dark:text-surface-400 transition-colors" 163 189 > 164 190 <Settings size={18} /> 165 191 <span className="hidden lg:inline">Settings</span> 166 - </Link> 192 + </a> 167 193 168 194 <div className="h-px bg-surface-200/60 dark:bg-surface-800/60 my-2" /> 169 195 170 - <Link 171 - to={`/profile/${user.did}`} 196 + <a 197 + href={`/profile/${user.did}`} 172 198 title={user.displayName || user.handle} 173 199 className="flex items-center justify-center lg:justify-start gap-2.5 p-2 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors w-full" 174 200 > ··· 181 207 @{user.handle} 182 208 </p> 183 209 </div> 184 - </Link> 210 + </a> 185 211 186 212 <button 187 213 onClick={logout} ··· 196 222 <> 197 223 <div className="h-px bg-surface-200/60 dark:bg-surface-800/60 my-2" /> 198 224 199 - <Link 200 - to="/login" 225 + <a 226 + href="/login" 201 227 title="Sign in" 202 228 className="flex items-center justify-center lg:justify-start gap-3 px-0 lg:px-3 py-2.5 rounded-lg bg-primary-50 dark:bg-primary-950/40 text-primary-700 dark:text-primary-300 hover:bg-primary-100 dark:hover:bg-primary-950/60 text-[13px] font-semibold transition-colors" 203 229 > 204 230 <LogIn size={18} /> 205 231 <span className="hidden lg:inline">Sign in</span> 206 - </Link> 232 + </a> 207 233 </> 208 234 )} 209 235 </div>
+2 -2
web/src/components/ui/Button.tsx
··· 40 40 return ( 41 41 <button 42 42 className={clsx( 43 - "inline-flex items-center justify-center font-medium rounded-lg transition-all duration-200", 43 + "inline-flex items-center justify-center font-medium rounded-lg transition-all duration-150", 44 44 "focus:outline-none focus:ring-2 focus:ring-primary-500/20", 45 - "disabled:opacity-50 disabled:cursor-not-allowed", 45 + "active:scale-[0.97] disabled:opacity-50 disabled:cursor-not-allowed", 46 46 variants[variant], 47 47 sizes[size], 48 48 className,
+2 -2
web/src/components/ui/Tabs.tsx
··· 23 23 return ( 24 24 <div 25 25 className={clsx( 26 - "flex max-w-full overflow-x-auto gap-1 bg-surface-100 dark:bg-surface-800 p-1 rounded-lg w-fit", 26 + "flex max-w-full overflow-x-auto gap-0.5 bg-surface-100 dark:bg-surface-800 p-1 rounded-lg w-fit", 27 27 className, 28 28 )} 29 29 > ··· 32 32 key={tab.id} 33 33 onClick={() => onChange(tab.id)} 34 34 className={clsx( 35 - "px-3 py-1.5 text-sm font-medium rounded-md transition-all relative", 35 + "px-3 py-1.5 text-[13px] font-medium rounded-md transition-all relative whitespace-nowrap", 36 36 activeTab === tab.id 37 37 ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 38 38 : "text-surface-500 dark:text-surface-400 hover:text-surface-700 dark:hover:text-surface-200",
+7
web/src/env.d.ts
··· 1 + /// <reference types="astro/client" /> 2 + 3 + declare namespace App { 4 + interface Locals { 5 + user: import("./types").UserProfile | null; 6 + } 7 + }
+42
web/src/layouts/AppLayout.astro
··· 1 + --- 2 + import BaseLayout from './BaseLayout.astro'; 3 + import Sidebar from '../components/navigation/Sidebar'; 4 + import RightSidebar from '../components/navigation/RightSidebar'; 5 + import MobileNav from '../components/navigation/MobileNav'; 6 + import type { UserProfile } from '../types'; 7 + 8 + interface Props { 9 + title?: string; 10 + description?: string; 11 + image?: string; 12 + user?: UserProfile | null; 13 + } 14 + 15 + const { title, description, image, user = Astro.locals.user } = Astro.props; 16 + --- 17 + 18 + <BaseLayout title={title} description={description} image={image}> 19 + <div class="min-h-screen bg-surface-100 dark:bg-surface-900 flex"> 20 + <div transition:persist="sidebar"> 21 + <Sidebar client:load initialUser={user} currentPath={Astro.url.pathname} /> 22 + </div> 23 + 24 + <div class="flex-1 min-w-0 transition-all duration-200"> 25 + <div class="flex w-full max-w-[1800px] mx-auto"> 26 + <main class="flex-1 w-full min-w-0 py-2 md:py-3"> 27 + <div class="bg-white dark:bg-surface-800 rounded-2xl min-h-[calc(100vh-16px)] md:min-h-[calc(100vh-24px)] py-6 px-4 md:px-6 lg:px-8 pb-20 md:pb-6"> 28 + <slot /> 29 + </div> 30 + </main> 31 + 32 + <div transition:persist="right-sidebar"> 33 + <RightSidebar client:visible /> 34 + </div> 35 + </div> 36 + </div> 37 + 38 + <div transition:persist="mobile-nav"> 39 + <MobileNav client:load initialUser={user} currentPath={Astro.url.pathname} /> 40 + </div> 41 + </div> 42 + </BaseLayout>
+24
web/src/layouts/BaseLayout.astro
··· 1 1 --- 2 + import { ClientRouter } from 'astro:transitions'; 2 3 import '../styles/global.css'; 3 4 4 5 interface Props { ··· 33 34 <meta name="twitter:image" content={image} /> 34 35 35 36 <title>{title}</title> 37 + 38 + <ClientRouter /> 39 + 40 + <script is:inline> 41 + (function() { 42 + function applyTheme() { 43 + var theme = localStorage.getItem('theme') || 'system'; 44 + var root = document.documentElement; 45 + if (theme === 'system') { 46 + root.dataset.theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 47 + } else { 48 + root.dataset.theme = theme; 49 + } 50 + } 51 + applyTheme(); 52 + document.addEventListener('astro:before-swap', function(e) { 53 + var theme = localStorage.getItem('theme') || 'system'; 54 + var dark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches); 55 + e.newDocument.documentElement.dataset.theme = dark ? 'dark' : 'light'; 56 + }); 57 + document.addEventListener('astro:after-swap', applyTheme); 58 + })(); 59 + </script> 36 60 </head> 37 61 <body class="bg-surface-50 dark:bg-surface-950 min-h-screen text-surface-900 dark:text-white"> 38 62 <slot />
-16
web/src/layouts/OGLayout.astro
··· 1 - --- 2 - import BaseLayout from './BaseLayout.astro'; 3 - import App from '../App'; 4 - 5 - interface Props { 6 - title?: string; 7 - description?: string; 8 - image?: string; 9 - } 10 - 11 - const { title = 'Margin', description = 'Annotate the web', image = 'https://margin.at/og.png' } = Astro.props; 12 - --- 13 - 14 - <BaseLayout title={title} description={description} image={image}> 15 - <App client:only="react" /> 16 - </BaseLayout>
+490
web/src/lib/api.ts
··· 1 + import type { 2 + AnnotationItem, 3 + UserProfile, 4 + FeedResponse, 5 + Collection, 6 + NotificationItem, 7 + Target, 8 + Selector, 9 + } from "../types"; 10 + 11 + const API_URL = 12 + process.env.API_URL || `http://localhost:${process.env.API_PORT || 8081}`; 13 + 14 + interface RawItem { 15 + type?: string; 16 + collectionUri?: string; 17 + annotation?: RawItem; 18 + highlight?: RawItem; 19 + bookmark?: RawItem; 20 + uri?: string; 21 + id?: string; 22 + cid?: string; 23 + author?: UserProfile; 24 + creator?: UserProfile; 25 + collection?: { uri: string; name: string; icon?: string }; 26 + context?: { uri: string; name: string; icon?: string }[]; 27 + created?: string; 28 + createdAt?: string; 29 + target?: string | { source?: string; title?: string; selector?: Selector }; 30 + url?: string; 31 + targetUrl?: string; 32 + title?: string; 33 + selector?: Selector; 34 + viewer?: { like?: string; [key: string]: unknown }; 35 + viewerHasLiked?: boolean; 36 + motivation?: string; 37 + [key: string]: unknown; 38 + } 39 + 40 + export function normalizeItem(raw: RawItem): AnnotationItem { 41 + if (raw.type === "CollectionItem" || raw.collectionUri) { 42 + const inner = raw.annotation || raw.highlight || raw.bookmark || {}; 43 + const normalizedInner = normalizeItem(inner); 44 + return { 45 + ...normalizedInner, 46 + uri: normalizedInner.uri || raw.uri || "", 47 + cid: normalizedInner.cid || raw.cid || "", 48 + author: (normalizedInner.author || 49 + raw.author || 50 + raw.creator) as UserProfile, 51 + collection: raw.collection 52 + ? { 53 + uri: raw.collection.uri, 54 + name: raw.collection.name, 55 + icon: raw.collection.icon, 56 + } 57 + : undefined, 58 + context: raw.context?.map((c) => ({ 59 + uri: c.uri, 60 + name: c.name, 61 + icon: c.icon, 62 + })), 63 + addedBy: raw.creator || raw.author, 64 + createdAt: 65 + normalizedInner.createdAt || 66 + raw.created || 67 + raw.createdAt || 68 + new Date().toISOString(), 69 + collectionItemUri: raw.id || raw.uri, 70 + }; 71 + } 72 + 73 + let target: Target | undefined; 74 + if (raw.target) { 75 + if (typeof raw.target === "string") { 76 + target = { source: raw.target, title: raw.title, selector: raw.selector }; 77 + } else { 78 + target = { 79 + source: raw.target.source || "", 80 + title: raw.target.title || raw.title, 81 + selector: raw.target.selector || raw.selector, 82 + }; 83 + } 84 + } 85 + if (!target || !target.source) { 86 + const url = 87 + raw.url || 88 + raw.targetUrl || 89 + (typeof raw.target === "string" ? raw.target : raw.target?.source); 90 + if (url) { 91 + target = { 92 + source: url, 93 + title: 94 + raw.title || 95 + (typeof raw.target !== "string" ? raw.target?.title : undefined), 96 + selector: 97 + raw.selector || 98 + (typeof raw.target !== "string" ? raw.target?.selector : undefined), 99 + }; 100 + } 101 + } 102 + 103 + return { 104 + ...raw, 105 + uri: raw.id || raw.uri || "", 106 + cid: raw.cid || "", 107 + author: (raw.creator || raw.author) as UserProfile, 108 + createdAt: raw.created || raw.createdAt || new Date().toISOString(), 109 + target, 110 + viewer: raw.viewer || { like: raw.viewerHasLiked ? "true" : undefined }, 111 + motivation: raw.motivation || "highlighting", 112 + parentUri: (raw as Record<string, unknown>).inReplyTo as string | undefined, 113 + }; 114 + } 115 + 116 + async function serverFetch(path: string, cookie?: string): Promise<Response> { 117 + const headers: Record<string, string> = { 118 + "Content-Type": "application/json", 119 + }; 120 + if (cookie) headers["Cookie"] = cookie; 121 + return fetch(`${API_URL}${path}`, { headers }); 122 + } 123 + 124 + const sessionCache = new Map<string, { user: UserProfile; expires: number }>(); 125 + 126 + export async function getSession(cookie: string): Promise<UserProfile | null> { 127 + try { 128 + const cacheKey = cookie.match(/margin_session=([^;]+)/)?.[1] || ""; 129 + const cached = sessionCache.get(cacheKey); 130 + if (cached && Date.now() < cached.expires) { 131 + return cached.user; 132 + } 133 + 134 + const res = await serverFetch("/auth/session", cookie); 135 + if (!res.ok) return null; 136 + const data = await res.json(); 137 + if (!data.authenticated && !data.did) return null; 138 + 139 + const profile: UserProfile = { 140 + did: data.did, 141 + handle: data.handle, 142 + displayName: data.displayName, 143 + avatar: data.avatar, 144 + description: data.description, 145 + website: data.website, 146 + links: data.links, 147 + followersCount: data.followersCount, 148 + followsCount: data.followsCount, 149 + postsCount: data.postsCount, 150 + }; 151 + 152 + // Fetch bsky profile and margin profile in parallel with a 3s timeout 153 + const controller = new AbortController(); 154 + const timeout = setTimeout(() => controller.abort(), 3000); 155 + 156 + const [bskyRes, marginRes] = await Promise.allSettled([ 157 + fetch( 158 + `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(data.did)}`, 159 + { signal: controller.signal }, 160 + ), 161 + serverFetch(`/api/profile/${data.did}`, cookie), 162 + ]); 163 + 164 + clearTimeout(timeout); 165 + 166 + if (bskyRes.status === "fulfilled" && bskyRes.value.ok) { 167 + try { 168 + const bsky = await bskyRes.value.json(); 169 + if (bsky.avatar) profile.avatar = bsky.avatar; 170 + if (bsky.displayName) profile.displayName = bsky.displayName; 171 + } catch { 172 + /* ignore */ 173 + } 174 + } 175 + 176 + if (marginRes.status === "fulfilled" && marginRes.value.ok) { 177 + try { 178 + const mp = await marginRes.value.json(); 179 + if (mp?.description) profile.description = mp.description; 180 + if (mp?.followersCount) profile.followersCount = mp.followersCount; 181 + if (mp?.followsCount) profile.followsCount = mp.followsCount; 182 + if (mp?.postsCount) profile.postsCount = mp.postsCount; 183 + if (mp?.website) profile.website = mp.website; 184 + if (mp?.links) profile.links = mp.links; 185 + } catch { 186 + /* ignore */ 187 + } 188 + } 189 + 190 + if (cacheKey) { 191 + sessionCache.set(cacheKey, { 192 + user: profile, 193 + expires: Date.now() + 30_000, 194 + }); 195 + // Evict old entries 196 + if (sessionCache.size > 100) { 197 + const now = Date.now(); 198 + for (const [k, v] of sessionCache) { 199 + if (now > v.expires) sessionCache.delete(k); 200 + } 201 + } 202 + } 203 + 204 + return profile; 205 + } catch { 206 + return null; 207 + } 208 + } 209 + 210 + export interface GetFeedParams { 211 + type?: string; 212 + limit?: number; 213 + offset?: number; 214 + motivation?: string; 215 + tag?: string; 216 + creator?: string; 217 + source?: string; 218 + } 219 + 220 + function groupFeedItems(items: AnnotationItem[]): AnnotationItem[] { 221 + if (items.length === 0) return items; 222 + const grouped: AnnotationItem[] = [items[0]]; 223 + for (let i = 1; i < items.length; i++) { 224 + const prev = grouped[grouped.length - 1]; 225 + const curr = items[i]; 226 + if ( 227 + prev.collection && 228 + curr.collection && 229 + prev.uri === curr.uri && 230 + prev.addedBy?.did === curr.addedBy?.did 231 + ) { 232 + if (!prev.context) prev.context = [prev.collection]; 233 + if (!prev.context.find((c) => c.uri === curr.collection!.uri)) { 234 + prev.context.push(curr.collection); 235 + } 236 + continue; 237 + } 238 + grouped.push(curr); 239 + } 240 + return grouped; 241 + } 242 + 243 + export async function getFeed( 244 + cookie: string, 245 + params: GetFeedParams = {}, 246 + ): Promise<FeedResponse> { 247 + const qs = new URLSearchParams(); 248 + if (params.source) qs.append("source", params.source); 249 + if (params.type) qs.append("type", params.type); 250 + if (params.limit) qs.append("limit", params.limit.toString()); 251 + if (params.offset) qs.append("offset", params.offset.toString()); 252 + if (params.motivation) qs.append("motivation", params.motivation); 253 + if (params.tag) qs.append("tag", params.tag); 254 + if (params.creator) qs.append("creator", params.creator); 255 + 256 + const endpoint = params.source ? "/api/targets" : "/api/annotations/feed"; 257 + try { 258 + const res = await serverFetch(`${endpoint}?${qs.toString()}`, cookie); 259 + if (!res.ok) return { items: [], hasMore: false, fetchedCount: 0 }; 260 + const data = await res.json(); 261 + const items = (data.items || []).map(normalizeItem); 262 + return { 263 + items: groupFeedItems(items), 264 + hasMore: items.length >= (params.limit || 50), 265 + fetchedCount: items.length, 266 + }; 267 + } catch { 268 + return { items: [], hasMore: false, fetchedCount: 0 }; 269 + } 270 + } 271 + 272 + export async function searchItems( 273 + cookie: string, 274 + query: string, 275 + options: { creator?: string; limit?: number; offset?: number } = {}, 276 + ): Promise<FeedResponse> { 277 + const qs = new URLSearchParams(); 278 + qs.append("q", query); 279 + if (options.creator) qs.append("creator", options.creator); 280 + if (options.limit) qs.append("limit", options.limit.toString()); 281 + if (options.offset) qs.append("offset", options.offset.toString()); 282 + 283 + try { 284 + const res = await serverFetch(`/api/search?${qs.toString()}`, cookie); 285 + if (!res.ok) return { items: [], hasMore: false, fetchedCount: 0 }; 286 + const data = await res.json(); 287 + const items = (data.items || []).map(normalizeItem); 288 + return { 289 + items, 290 + hasMore: items.length >= (options.limit || 50), 291 + fetchedCount: items.length, 292 + }; 293 + } catch { 294 + return { items: [], hasMore: false, fetchedCount: 0 }; 295 + } 296 + } 297 + 298 + export async function getAnnotation( 299 + cookie: string, 300 + uri: string, 301 + ): Promise<AnnotationItem | null> { 302 + try { 303 + const res = await serverFetch( 304 + `/api/annotation?uri=${encodeURIComponent(uri)}`, 305 + cookie, 306 + ); 307 + if (!res.ok) return null; 308 + const data = await res.json(); 309 + return normalizeItem(data); 310 + } catch { 311 + return null; 312 + } 313 + } 314 + 315 + export async function getReplies( 316 + cookie: string, 317 + uri: string, 318 + ): Promise<AnnotationItem[]> { 319 + try { 320 + const res = await serverFetch( 321 + `/api/replies?uri=${encodeURIComponent(uri)}`, 322 + cookie, 323 + ); 324 + if (!res.ok) return []; 325 + const data = await res.json(); 326 + return (data.items || []).map(normalizeItem); 327 + } catch { 328 + return []; 329 + } 330 + } 331 + 332 + export async function getProfile( 333 + cookie: string, 334 + did: string, 335 + ): Promise<UserProfile | null> { 336 + try { 337 + const res = await serverFetch( 338 + `/api/profile/${encodeURIComponent(did)}`, 339 + cookie, 340 + ); 341 + if (!res.ok) return null; 342 + return await res.json(); 343 + } catch { 344 + return null; 345 + } 346 + } 347 + 348 + export async function getCollections( 349 + cookie: string, 350 + author?: string, 351 + ): Promise<Collection[]> { 352 + const qs = author ? `?author=${encodeURIComponent(author)}` : ""; 353 + try { 354 + const res = await serverFetch(`/api/collections${qs}`, cookie); 355 + if (!res.ok) return []; 356 + const data = await res.json(); 357 + return data.collections || data || []; 358 + } catch { 359 + return []; 360 + } 361 + } 362 + 363 + export async function getCollection( 364 + cookie: string, 365 + uri: string, 366 + ): Promise<Collection | null> { 367 + try { 368 + const res = await serverFetch( 369 + `/api/collection?uri=${encodeURIComponent(uri)}`, 370 + cookie, 371 + ); 372 + if (!res.ok) return null; 373 + return await res.json(); 374 + } catch { 375 + return null; 376 + } 377 + } 378 + 379 + export async function getCollectionItems( 380 + cookie: string, 381 + uri: string, 382 + ): Promise<AnnotationItem[]> { 383 + try { 384 + const res = await serverFetch( 385 + `/api/collections/${encodeURIComponent(uri)}/items`, 386 + cookie, 387 + ); 388 + if (!res.ok) return []; 389 + const data = await res.json(); 390 + return (data.items || data || []).map(normalizeItem); 391 + } catch { 392 + return []; 393 + } 394 + } 395 + 396 + export async function getNotifications( 397 + cookie: string, 398 + limit = 50, 399 + offset = 0, 400 + ): Promise<{ items: NotificationItem[]; hasMore: boolean }> { 401 + try { 402 + const res = await serverFetch( 403 + `/api/notifications?limit=${limit}&offset=${offset}`, 404 + cookie, 405 + ); 406 + if (!res.ok) return { items: [], hasMore: false }; 407 + const data = await res.json(); 408 + return { 409 + items: data.notifications || [], 410 + hasMore: (data.notifications || []).length >= limit, 411 + }; 412 + } catch { 413 + return { items: [], hasMore: false }; 414 + } 415 + } 416 + 417 + export async function getTrendingTags( 418 + limit = 10, 419 + ): Promise<{ tag: string; count: number }[]> { 420 + try { 421 + const res = await serverFetch(`/api/tags/trending?limit=${limit}`); 422 + if (!res.ok) return []; 423 + return await res.json(); 424 + } catch { 425 + return []; 426 + } 427 + } 428 + 429 + export async function getRecommendations( 430 + cookie: string, 431 + params: { sort?: string; limit?: number; offset?: number } = {}, 432 + ): Promise<{ items: AnnotationItem[]; hasMore: boolean }> { 433 + const qs = new URLSearchParams(); 434 + if (params.sort) qs.append("sort", params.sort); 435 + if (params.limit) qs.append("limit", params.limit.toString()); 436 + if (params.offset) qs.append("offset", params.offset.toString()); 437 + 438 + try { 439 + const res = await serverFetch(`/api/documents?${qs.toString()}`, cookie); 440 + if (!res.ok) return { items: [], hasMore: false }; 441 + const data = await res.json(); 442 + return { 443 + items: data.items || [], 444 + hasMore: (data.items || []).length >= (params.limit || 20), 445 + }; 446 + } catch { 447 + return { items: [], hasMore: false }; 448 + } 449 + } 450 + 451 + export async function getByTarget( 452 + cookie: string, 453 + url: string, 454 + limit = 50, 455 + offset = 0, 456 + ): Promise<{ annotations: AnnotationItem[]; highlights: AnnotationItem[] }> { 457 + try { 458 + const res = await serverFetch( 459 + `/api/targets?source=${encodeURIComponent(url)}&limit=${limit}&offset=${offset}`, 460 + cookie, 461 + ); 462 + if (!res.ok) return { annotations: [], highlights: [] }; 463 + const data = await res.json(); 464 + const items = (data.items || []).map(normalizeItem); 465 + return { 466 + annotations: items.filter( 467 + (i: AnnotationItem) => i.motivation === "commenting", 468 + ), 469 + highlights: items.filter( 470 + (i: AnnotationItem) => i.motivation === "highlighting", 471 + ), 472 + }; 473 + } catch { 474 + return { annotations: [], highlights: [] }; 475 + } 476 + } 477 + 478 + export async function resolveHandle(handle: string): Promise<string | null> { 479 + if (handle.startsWith("did:")) return handle; 480 + try { 481 + const res = await fetch( 482 + `https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`, 483 + ); 484 + if (!res.ok) return null; 485 + const data = await res.json(); 486 + return data.did || null; 487 + } catch { 488 + return null; 489 + } 490 + }
+63
web/src/lib/metadataQueue.ts
··· 1 + type QueueEntry = { 2 + url: string; 3 + resolve: (data: Record<string, string> | null) => void; 4 + }; 5 + 6 + const MAX_CONCURRENT = 3; 7 + const queue: QueueEntry[] = []; 8 + let active = 0; 9 + const inflight = new Map<string, Promise<Record<string, string> | null>>(); 10 + 11 + function processQueue() { 12 + while (active < MAX_CONCURRENT && queue.length > 0) { 13 + const entry = queue.shift()!; 14 + active++; 15 + 16 + doFetch(entry.url) 17 + .then(entry.resolve) 18 + .finally(() => { 19 + active--; 20 + processQueue(); 21 + }); 22 + } 23 + } 24 + 25 + function doFetch(url: string): Promise<Record<string, string> | null> { 26 + const existing = inflight.get(url); 27 + if (existing) return existing; 28 + 29 + const promise = fetch(`/api/url-metadata?url=${encodeURIComponent(url)}`) 30 + .then((res) => (res.ok ? res.json() : null)) 31 + .catch(() => null) 32 + .finally(() => { 33 + inflight.delete(url); 34 + }); 35 + 36 + inflight.set(url, promise); 37 + return promise; 38 + } 39 + 40 + export function fetchMetadata( 41 + url: string, 42 + ): Promise<Record<string, string> | null> { 43 + try { 44 + const cached = sessionStorage.getItem(`og:${url}`); 45 + if (cached) return Promise.resolve(JSON.parse(cached)); 46 + } catch { 47 + /* ignore */ 48 + } 49 + 50 + return new Promise((resolve) => { 51 + queue.push({ url, resolve }); 52 + processQueue(); 53 + }).then((data) => { 54 + if (data) { 55 + try { 56 + sessionStorage.setItem(`og:${url}`, JSON.stringify(data)); 57 + } catch { 58 + /* ignore */ 59 + } 60 + } 61 + return data as Record<string, string> | null; 62 + }); 63 + }
+17 -3
web/src/middleware.ts
··· 1 1 import type { APIContext } from "astro"; 2 2 import { readFile } from "node:fs/promises"; 3 3 import { join } from "node:path"; 4 + import { getSession } from "./lib/api"; 4 5 5 6 const API_PORT = process.env.API_PORT || 8081; 6 7 const API_URL = process.env.API_URL || `http://localhost:${API_PORT}`; ··· 26 27 } 27 28 28 29 export async function onRequest( 29 - { request, url }: APIContext, 30 + context: APIContext, 30 31 next: () => Promise<Response>, 31 32 ): Promise<Response> { 33 + const { request, url, locals } = context; 34 + 32 35 if (url.pathname === "/favicon.ico") { 33 36 try { 34 37 const file = await readFile( ··· 49 52 (p) => url.pathname.startsWith(p) || url.pathname === p.replace(/\/$/, ""), 50 53 ); 51 54 52 - if (!shouldProxy) { 53 - return next(); 55 + if (shouldProxy) { 56 + return proxyToBackend(request, url); 54 57 } 55 58 59 + const cookie = request.headers.get("cookie") || ""; 60 + if (cookie.includes("margin_session")) { 61 + locals.user = await getSession(cookie); 62 + } else { 63 + locals.user = null; 64 + } 65 + 66 + return next(); 67 + } 68 + 69 + async function proxyToBackend(request: Request, url: URL): Promise<Response> { 56 70 const origin = request.headers.get("origin"); 57 71 58 72 if (request.method === "OPTIONS" && isExtensionOrigin(origin)) {
-10
web/src/pages/[...slug].astro
··· 1 - --- 2 - export const prerender = false; 3 - 4 - import BaseLayout from '../layouts/BaseLayout.astro'; 5 - import App from '../App'; 6 - --- 7 - 8 - <BaseLayout title="Margin" description="Annotate the web using the AT Protocol"> 9 - <App client:only="react" /> 10 - </BaseLayout>
+7 -4
web/src/pages/[handle]/annotation/[rkey].astro
··· 1 1 --- 2 - export const prerender = false; 3 2 4 - import OGLayout from '../../../layouts/OGLayout.astro'; 3 + import AppLayout from '../../../layouts/AppLayout.astro'; 4 + import AnnotationDetail from '../../../views/content/AnnotationDetail'; 5 5 import { resolveHandle, fetchOGForRoute } from '../../../lib/og'; 6 6 7 7 const { handle, rkey } = Astro.params; 8 + const user = Astro.locals.user; 8 9 9 - let title = 'Margin'; 10 + let title = 'Annotation - Margin'; 10 11 let description = 'Annotate the web'; 11 12 let image = 'https://margin.at/og.png'; 12 13 ··· 27 28 } 28 29 --- 29 30 30 - <OGLayout title={title} description={description} image={image} /> 31 + <AppLayout title={title} description={description} image={image} user={user}> 32 + <AnnotationDetail client:load handle={handle} rkey={rkey} type="annotation" /> 33 + </AppLayout>
+7 -4
web/src/pages/[handle]/bookmark/[rkey].astro
··· 1 1 --- 2 - export const prerender = false; 3 2 4 - import OGLayout from '../../../layouts/OGLayout.astro'; 3 + import AppLayout from '../../../layouts/AppLayout.astro'; 4 + import AnnotationDetail from '../../../views/content/AnnotationDetail'; 5 5 import { resolveHandle, fetchOGForRoute } from '../../../lib/og'; 6 6 7 7 const { handle, rkey } = Astro.params; 8 + const user = Astro.locals.user; 8 9 9 - let title = 'Margin'; 10 + let title = 'Bookmark - Margin'; 10 11 let description = 'Annotate the web'; 11 12 let image = 'https://margin.at/og.png'; 12 13 ··· 27 28 } 28 29 --- 29 30 30 - <OGLayout title={title} description={description} image={image} /> 31 + <AppLayout title={title} description={description} image={image} user={user}> 32 + <AnnotationDetail client:load handle={handle} rkey={rkey} type="bookmark" /> 33 + </AppLayout>
+7 -4
web/src/pages/[handle]/collection/[rkey].astro
··· 1 1 --- 2 - export const prerender = false; 3 2 4 - import OGLayout from '../../../layouts/OGLayout.astro'; 3 + import AppLayout from '../../../layouts/AppLayout.astro'; 4 + import CollectionDetail from '../../../views/collections/CollectionDetail'; 5 5 import { resolveHandle, fetchCollectionOG } from '../../../lib/og'; 6 6 7 7 const { handle, rkey } = Astro.params; 8 + const user = Astro.locals.user; 8 9 9 - let title = 'Margin'; 10 + let title = 'Collection - Margin'; 10 11 let description = 'Annotate the web'; 11 12 let image = 'https://margin.at/og.png'; 12 13 ··· 28 29 } 29 30 --- 30 31 31 - <OGLayout title={title} description={description} image={image} /> 32 + <AppLayout title={title} description={description} image={image} user={user}> 33 + <CollectionDetail client:load handle={handle} rkey={rkey} /> 34 + </AppLayout>
+7 -4
web/src/pages/[handle]/highlight/[rkey].astro
··· 1 1 --- 2 - export const prerender = false; 3 2 4 - import OGLayout from '../../../layouts/OGLayout.astro'; 3 + import AppLayout from '../../../layouts/AppLayout.astro'; 4 + import AnnotationDetail from '../../../views/content/AnnotationDetail'; 5 5 import { resolveHandle, fetchOGForRoute } from '../../../lib/og'; 6 6 7 7 const { handle, rkey } = Astro.params; 8 + const user = Astro.locals.user; 8 9 9 - let title = 'Margin'; 10 + let title = 'Highlight - Margin'; 10 11 let description = 'Annotate the web'; 11 12 let image = 'https://margin.at/og.png'; 12 13 ··· 27 28 } 28 29 --- 29 30 30 - <OGLayout title={title} description={description} image={image} /> 31 + <AppLayout title={title} description={description} image={image} user={user}> 32 + <AnnotationDetail client:load handle={handle} rkey={rkey} type="highlight" /> 33 + </AppLayout>
+9
web/src/pages/about.astro
··· 1 + --- 2 + 3 + import BaseLayout from '../layouts/BaseLayout.astro'; 4 + import About from '../views/About'; 5 + --- 6 + 7 + <BaseLayout title="About - Margin" description="Annotate the web using the AT Protocol"> 8 + <About client:load /> 9 + </BaseLayout>
+14
web/src/pages/admin/moderation.astro
··· 1 + --- 2 + 3 + import AppLayout from '../../layouts/AppLayout.astro'; 4 + import AdminModeration from '../../views/core/AdminModeration'; 5 + 6 + const user = Astro.locals.user; 7 + if (!user) { 8 + return Astro.redirect('/login'); 9 + } 10 + --- 11 + 12 + <AppLayout title="Admin - Margin" user={user}> 13 + <AdminModeration client:load /> 14 + </AppLayout>
+12
web/src/pages/annotations.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Feed from '../views/core/Feed'; 5 + 6 + const user = Astro.locals.user; 7 + const tag = Astro.url.searchParams.get('tag') || undefined; 8 + --- 9 + 10 + <AppLayout title="Annotations - Margin" user={user}> 11 + <Feed client:load initialType="all" motivation="commenting" showTabs={false} initialTag={tag} initialUser={user} /> 12 + </AppLayout>
+6 -3
web/src/pages/at/[did]/[rkey].astro
··· 1 1 --- 2 - export const prerender = false; 3 2 4 - import OGLayout from '../../../layouts/OGLayout.astro'; 3 + import AppLayout from '../../../layouts/AppLayout.astro'; 4 + import AnnotationDetail from '../../../views/content/AnnotationDetail'; 5 5 import { fetchOGForRoute } from '../../../lib/og'; 6 6 7 7 const { did, rkey } = Astro.params; 8 + const user = Astro.locals.user; 8 9 9 10 let title = 'Margin'; 10 11 let description = 'Annotate the web'; ··· 20 21 } 21 22 --- 22 23 23 - <OGLayout title={title} description={description} image={image} /> 24 + <AppLayout title={title} description={description} image={image} user={user}> 25 + <AnnotationDetail client:load did={did} rkey={rkey} /> 26 + </AppLayout>
+12
web/src/pages/bookmarks.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Feed from '../views/core/Feed'; 5 + 6 + const user = Astro.locals.user; 7 + const tag = Astro.url.searchParams.get('tag') || undefined; 8 + --- 9 + 10 + <AppLayout title="Bookmarks - Margin" user={user}> 11 + <Feed client:load initialType="all" motivation="bookmarking" showTabs={false} initialTag={tag} initialUser={user} /> 12 + </AppLayout>
+12
web/src/pages/collections/[rkey].astro
··· 1 + --- 2 + 3 + import AppLayout from '../../layouts/AppLayout.astro'; 4 + import CollectionDetail from '../../views/collections/CollectionDetail'; 5 + 6 + const { rkey } = Astro.params; 7 + const user = Astro.locals.user; 8 + --- 9 + 10 + <AppLayout title="Collection - Margin" user={user}> 11 + <CollectionDetail client:load rkey={rkey} /> 12 + </AppLayout>
+11
web/src/pages/collections/index.astro
··· 1 + --- 2 + 3 + import AppLayout from '../../layouts/AppLayout.astro'; 4 + import Collections from '../../views/collections/Collections'; 5 + 6 + const user = Astro.locals.user; 7 + --- 8 + 9 + <AppLayout title="Collections - Margin" user={user}> 10 + <Collections client:load /> 11 + </AppLayout>
+11
web/src/pages/discover.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Discover from '../views/core/Discover'; 5 + 6 + const user = Astro.locals.user; 7 + --- 8 + 9 + <AppLayout title="Discover - Margin" user={user}> 10 + <Discover client:load /> 11 + </AppLayout>
+12
web/src/pages/highlights.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Feed from '../views/core/Feed'; 5 + 6 + const user = Astro.locals.user; 7 + const tag = Astro.url.searchParams.get('tag') || undefined; 8 + --- 9 + 10 + <AppLayout title="Highlights - Margin" user={user}> 11 + <Feed client:load initialType="all" motivation="highlighting" showTabs={false} initialTag={tag} initialUser={user} /> 12 + </AppLayout>
+12
web/src/pages/home.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Feed from '../views/core/Feed'; 5 + 6 + const user = Astro.locals.user; 7 + const tag = Astro.url.searchParams.get('tag') || undefined; 8 + --- 9 + 10 + <AppLayout title="Home - Margin" user={user}> 11 + <Feed client:load initialType="all" initialTag={tag} initialUser={user} /> 12 + </AppLayout>
+8 -2
web/src/pages/index.astro
··· 1 1 --- 2 + 2 3 import BaseLayout from '../layouts/BaseLayout.astro'; 3 - import App from '../App'; 4 + import About from '../views/About'; 5 + 6 + const user = Astro.locals.user; 7 + if (user) { 8 + return Astro.redirect('/home'); 9 + } 4 10 --- 5 11 6 12 <BaseLayout title="Margin" description="Annotate the web using the AT Protocol"> 7 - <App client:only="react" /> 13 + <About client:load /> 8 14 </BaseLayout>
+16
web/src/pages/login.astro
··· 1 + --- 2 + 3 + import BaseLayout from '../layouts/BaseLayout.astro'; 4 + import Login from '../views/auth/Login'; 5 + 6 + const user = Astro.locals.user; 7 + if (user) { 8 + return Astro.redirect('/home'); 9 + } 10 + 11 + const error = Astro.url.searchParams.get('error') || undefined; 12 + --- 13 + 14 + <BaseLayout title="Sign In - Margin" description="Sign in to Margin"> 15 + <Login client:load initialError={error} /> 16 + </BaseLayout>
+18
web/src/pages/new.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import NewAnnotationPage from '../views/core/New'; 5 + 6 + const user = Astro.locals.user; 7 + if (!user) { 8 + return Astro.redirect('/login'); 9 + } 10 + 11 + const url = Astro.url.searchParams.get('url') || undefined; 12 + const selector = Astro.url.searchParams.get('selector') || undefined; 13 + const quote = Astro.url.searchParams.get('quote') || undefined; 14 + --- 15 + 16 + <AppLayout title="New Annotation - Margin" user={user}> 17 + <NewAnnotationPage client:load initialUrl={url} initialSelectorJson={selector} initialQuote={quote} /> 18 + </AppLayout>
+14
web/src/pages/notifications.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Notifications from '../views/core/Notifications'; 5 + 6 + const user = Astro.locals.user; 7 + if (!user) { 8 + return Astro.redirect('/login'); 9 + } 10 + --- 11 + 12 + <AppLayout title="Notifications - Margin" user={user}> 13 + <Notifications client:load /> 14 + </AppLayout>
+345 -211
web/src/pages/og-image.ts
··· 4 4 import { readFileSync } from "node:fs"; 5 5 import { join } from "node:path"; 6 6 7 - export const prerender = false; 8 - 9 7 function getPublicDir(): string { 10 8 if (import.meta.env.PROD) { 11 9 return join(process.cwd(), "dist", "client"); ··· 144 142 quote: "", 145 143 source: "", 146 144 title: item.name || "Collection", 147 - icon: item.icon || "📁", 145 + icon: item.icon || "", 148 146 description: item.description || "", 149 147 color: "", 150 148 }; ··· 158 156 159 157 function truncate(str: string, max: number): string { 160 158 if (str.length <= max) return str; 161 - return str.slice(0, max - 3) + "..."; 159 + return str.slice(0, max - 3) + "\u2026"; 162 160 } 163 161 164 162 function extractBody(body: unknown): string { ··· 171 169 } 172 170 173 171 const C = { 174 - bg: "#f4f4f5", 175 - text: "#18181b", 176 - textSecondary: "#52525b", 177 - textMuted: "#a1a1aa", 178 - textFaint: "#d4d4d8", 179 - primary: "#3b82f6", 180 - primaryDark: "#2563eb", 181 - border: "#e4e4e7", 172 + bg: "#18181b", 173 + bgCard: "#27272a", 174 + text: "#fafafa", 175 + textSecondary: "#a1a1aa", 176 + textMuted: "#71717a", 177 + textFaint: "#3f3f46", 178 + border: "#3f3f46", 179 + }; 180 + 181 + const typeAccent: Record<string, string> = { 182 + annotation: "#3b82f6", 183 + highlight: "#facc15", 184 + bookmark: "#22c55e", 185 + collection: "#a78bfa", 186 + }; 187 + 188 + const typeLabels: Record<string, string> = { 189 + annotation: "Annotation", 190 + highlight: "Highlight", 191 + bookmark: "Bookmark", 192 + collection: "Collection", 182 193 }; 183 194 184 195 const namedColors: Record<string, string> = { ··· 186 197 green: "#4ade80", 187 198 red: "#f87171", 188 199 blue: "#60a5fa", 200 + purple: "#a78bfa", 201 + pink: "#f472b6", 202 + orange: "#fb923c", 189 203 }; 190 204 191 205 function resolveHighlightColor(color: string): string { ··· 194 208 return namedColors[color] || "#facc15"; 195 209 } 196 210 197 - const typeColors: Record<string, string> = { 198 - annotation: "#3b82f6", 199 - highlight: "#facc15", 200 - bookmark: "#22c55e", 201 - collection: "#3b82f6", 211 + function sanitizeColor(color: string): string { 212 + if (/^#[0-9a-fA-F]{3,8}$/.test(color)) return color; 213 + if (/^rgba?\([0-9,.\s]+\)$/.test(color)) return color; 214 + return "#888888"; 215 + } 216 + 217 + function coloredLogoUri(color: string): string { 218 + const safe = sanitizeColor(color); 219 + const publicDir = getPublicDir(); 220 + const svg = readFileSync(join(publicDir, "logo.svg"), "utf-8"); 221 + const recolored = svg.replace(/fill="[^"]*"/, `fill="${safe}"`); 222 + return `data:image/svg+xml,${encodeURIComponent(recolored)}`; 223 + } 224 + 225 + function logoElement(height: number, color: string): unknown | null { 226 + const uri = coloredLogoUri(color); 227 + if (!uri) return null; 228 + const width = Math.round(height * (265 / 231)); 229 + return { 230 + type: "img", 231 + props: { 232 + src: uri, 233 + width, 234 + height, 235 + style: { flexShrink: 0, opacity: 0.9 }, 236 + }, 237 + }; 238 + } 239 + 240 + const lucideFileMap: Record<string, string> = { 241 + file: "file-text", 242 + pin: "map-pin", 243 + trending: "trending-up", 202 244 }; 203 245 204 - function lightTint(hex: string): string { 205 - const r = parseInt(hex.slice(1, 3), 16); 206 - const g = parseInt(hex.slice(3, 5), 16); 207 - const b = parseInt(hex.slice(5, 7), 16); 208 - const mix = (c: number) => Math.round(c * 0.12 + 255 * 0.88); 209 - return `rgb(${mix(r)}, ${mix(g)}, ${mix(b)})`; 246 + function lucideIconUri(iconName: string, color: string): string | null { 247 + try { 248 + const safe = sanitizeColor(color); 249 + const fileName = lucideFileMap[iconName] || iconName; 250 + const iconPath = join( 251 + process.cwd(), 252 + "node_modules", 253 + "lucide-react", 254 + "dist", 255 + "esm", 256 + "icons", 257 + `${fileName}.js`, 258 + ); 259 + const src = readFileSync(iconPath, "utf-8"); 260 + const paths = [...src.matchAll(/d:\s*"([^"]+)"/g)].map((m) => m[1]); 261 + if (!paths.length) return null; 262 + const pathEls = paths 263 + .map( 264 + (d) => 265 + `<path d="${d}" fill="none" stroke="${safe}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>`, 266 + ) 267 + .join(""); 268 + const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">${pathEls}</svg>`; 269 + return `data:image/svg+xml,${encodeURIComponent(svg)}`; 270 + } catch { 271 + return null; 272 + } 210 273 } 211 274 212 275 function avatarElement(url: string, name: string, size: number): unknown { ··· 217 280 src: url, 218 281 width: size, 219 282 height: size, 220 - style: { borderRadius: size / 2, flexShrink: 0 }, 283 + style: { 284 + borderRadius: size / 2, 285 + flexShrink: 0, 286 + border: `2px solid ${C.border}`, 287 + }, 221 288 }, 222 289 }; 223 290 } 224 291 const letter = 225 292 name[0] === "@" 226 - ? name[1]?.toUpperCase() || "?" 227 - : name[0]?.toUpperCase() || "?"; 293 + ? (name[1] || "?").toUpperCase() 294 + : (name[0] || "?").toUpperCase(); 228 295 return { 229 296 type: "div", 230 297 props: { ··· 232 299 width: size, 233 300 height: size, 234 301 borderRadius: size / 2, 235 - background: "#e4e4e7", 302 + background: C.bgCard, 303 + border: `2px solid ${C.border}`, 236 304 display: "flex", 237 305 alignItems: "center", 238 306 justifyContent: "center", 239 - color: "#71717a", 307 + color: C.textMuted, 240 308 fontSize: Math.round(size * 0.45), 241 309 fontWeight: 700, 242 310 flexShrink: 0, ··· 246 314 }; 247 315 } 248 316 249 - function coloredLogoUri(color: string): string { 250 - const publicDir = getPublicDir(); 251 - const svg = readFileSync(join(publicDir, "logo.svg"), "utf-8"); 252 - const recolored = svg.replace(/fill="[^"]*"/, `fill="${color}"`); 253 - return `data:image/svg+xml,${encodeURIComponent(recolored)}`; 317 + function accentBar(color: string): unknown { 318 + return { 319 + type: "div", 320 + props: { 321 + style: { 322 + position: "absolute", 323 + left: 0, 324 + top: 0, 325 + bottom: 0, 326 + width: 5, 327 + background: color, 328 + borderRadius: "3px 0 0 3px", 329 + }, 330 + }, 331 + }; 254 332 } 255 333 256 - function logoElement(size: number, color: string): unknown { 334 + function bgPattern(): unknown { 257 335 return { 258 - type: "img", 336 + type: "div", 259 337 props: { 260 - src: coloredLogoUri(color), 261 - width: size, 262 - height: size, 263 - style: { flexShrink: 0 }, 338 + style: { 339 + position: "absolute", 340 + top: 0, 341 + left: 0, 342 + right: 0, 343 + bottom: 0, 344 + backgroundImage: 345 + "radial-gradient(circle at 1px 1px, rgba(255,255,255,0.03) 1px, transparent 0)", 346 + backgroundSize: "32px 32px", 347 + }, 264 348 }, 265 349 }; 266 350 } 267 351 268 - const typeLabels: Record<string, string> = { 269 - annotation: "Annotation", 270 - highlight: "Highlight", 271 - bookmark: "Bookmark", 272 - collection: "Collection", 273 - }; 274 - 275 - function headerWithBadge(data: RecordData, accentColor: string): unknown { 352 + function header(data: RecordData, accentColor: string): unknown { 276 353 return { 277 354 type: "div", 278 355 props: { 279 - style: { display: "flex", alignItems: "center" }, 356 + style: { display: "flex", alignItems: "center", width: "100%" }, 280 357 children: [ 281 - avatarElement(data.avatarURL, data.displayName, 48), 358 + avatarElement(data.avatarURL, data.displayName || data.author, 64), 282 359 { 283 360 type: "div", 284 361 props: { 285 362 style: { 286 363 display: "flex", 287 364 flexDirection: "column", 288 - marginLeft: 14, 365 + marginLeft: 18, 289 366 flex: 1, 290 367 }, 291 368 children: [ 292 369 { 293 370 type: "span", 294 371 props: { 295 - style: { 296 - color: C.text, 297 - fontSize: 22, 298 - fontWeight: 600, 299 - }, 300 - children: data.displayName, 372 + style: { color: C.text, fontSize: 28, fontWeight: 700 }, 373 + children: truncate(data.displayName || data.author, 30), 301 374 }, 302 375 }, 303 376 { ··· 305 378 props: { 306 379 style: { 307 380 color: C.textMuted, 308 - fontSize: 17, 309 - marginTop: 1, 381 + fontSize: 20, 382 + marginTop: 3, 310 383 }, 311 384 children: data.author, 312 385 }, ··· 321 394 display: "flex", 322 395 alignItems: "center", 323 396 gap: 10, 397 + background: "rgba(255,255,255,0.06)", 398 + borderRadius: 24, 399 + padding: "8px 18px 8px 14px", 324 400 }, 325 401 children: [ 326 - logoElement(24, accentColor), 402 + logoElement(22, accentColor), 327 403 { 328 404 type: "span", 329 405 props: { 330 406 style: { 331 - color: C.textFaint, 407 + color: accentColor, 332 408 fontSize: 18, 333 - }, 334 - children: "|", 335 - }, 336 - }, 337 - { 338 - type: "span", 339 - props: { 340 - style: { 341 - color: accentColor, 342 - fontSize: 16, 343 409 fontWeight: 600, 344 - textTransform: "uppercase" as const, 345 - letterSpacing: 1, 410 + letterSpacing: 0.5, 346 411 }, 347 412 children: typeLabels[data.type] || data.type, 348 413 }, 349 414 }, 350 - ], 415 + ].filter(Boolean) as unknown[], 351 416 }, 352 417 }, 353 418 ], ··· 355 420 }; 356 421 } 357 422 358 - function footerSource(source?: string): unknown | null { 423 + function footerEl(source: string): unknown | null { 359 424 if (!source) return null; 360 425 return { 361 426 type: "div", ··· 364 429 display: "flex", 365 430 alignItems: "center", 366 431 marginTop: "auto", 367 - paddingTop: 16, 432 + paddingTop: 24, 368 433 }, 369 434 children: [ 435 + { 436 + type: "div", 437 + props: { 438 + style: { 439 + width: 8, 440 + height: 8, 441 + borderRadius: 4, 442 + background: C.textFaint, 443 + marginRight: 12, 444 + }, 445 + }, 446 + }, 370 447 { 371 448 type: "span", 372 449 props: { 373 - style: { color: C.textMuted, fontSize: 16 }, 450 + style: { color: C.textMuted, fontSize: 20 }, 374 451 children: source, 375 452 }, 376 453 }, ··· 379 456 }; 380 457 } 381 458 382 - function wrap(children: unknown[], bg?: string): unknown { 459 + function card(children: unknown[], accentColor: string): unknown { 383 460 return { 384 461 type: "div", 385 462 props: { 386 463 style: { 387 464 display: "flex", 388 - flexDirection: "column", 389 465 width: "100%", 390 466 height: "100%", 391 - background: bg || C.bg, 392 - padding: "48px 64px", 467 + background: C.bg, 468 + padding: 0, 393 469 fontFamily: "Inter", 470 + position: "relative", 394 471 }, 395 - children, 472 + children: [ 473 + bgPattern(), 474 + accentBar(accentColor), 475 + { 476 + type: "div", 477 + props: { 478 + style: { 479 + display: "flex", 480 + flexDirection: "column", 481 + width: "100%", 482 + height: "100%", 483 + padding: "48px 56px 48px 60px", 484 + position: "relative", 485 + }, 486 + children, 487 + }, 488 + }, 489 + ], 396 490 }, 397 491 }; 398 492 } 399 493 400 494 function buildAnnotationImage(data: RecordData) { 401 - const accent = typeColors.annotation; 402 - const children: unknown[] = [headerWithBadge(data, accent)]; 495 + const accent = typeAccent.annotation; 496 + const children: unknown[] = [header(data, accent)]; 403 497 404 498 if (data.text) { 405 499 const len = data.text.length; ··· 408 502 props: { 409 503 style: { 410 504 color: C.text, 411 - fontSize: len > 200 ? 26 : len > 100 ? 30 : 36, 412 - fontWeight: 500, 413 - lineHeight: 1.45, 414 - marginTop: 32, 505 + fontSize: len > 200 ? 32 : len > 100 ? 38 : 46, 506 + fontWeight: 400, 507 + lineHeight: 1.5, 508 + marginTop: 36, 415 509 overflow: "hidden", 416 510 }, 417 - children: truncate(data.text, 280), 511 + children: truncate(data.text, 240), 418 512 }, 419 513 }); 420 514 } ··· 423 517 children.push({ 424 518 type: "div", 425 519 props: { 426 - style: { display: "flex", marginTop: 24 }, 520 + style: { 521 + display: "flex", 522 + marginTop: 28, 523 + background: "rgba(59, 130, 246, 0.06)", 524 + borderRadius: 14, 525 + padding: "20px 24px", 526 + }, 427 527 children: [ 428 528 { 429 529 type: "div", ··· 433 533 borderRadius: 2, 434 534 background: accent, 435 535 flexShrink: 0, 536 + opacity: 0.7, 436 537 }, 437 538 }, 438 539 }, ··· 441 542 props: { 442 543 style: { 443 544 color: C.textSecondary, 444 - fontSize: 20, 545 + fontSize: 24, 445 546 lineHeight: 1.6, 446 - paddingLeft: 20, 547 + paddingLeft: 18, 447 548 fontStyle: "italic", 448 549 overflow: "hidden", 449 550 }, 450 - children: truncate(data.quote, 200), 551 + children: truncate(data.quote, 180), 451 552 }, 452 553 }, 453 554 ], ··· 455 556 }); 456 557 } 457 558 458 - const footer = footerSource(data.source); 459 - if (footer) children.push(footer); 460 - 461 - return wrap(children, lightTint(accent)); 559 + const f = footerEl(data.source); 560 + if (f) children.push(f); 561 + return card(children, accent); 462 562 } 463 563 464 564 function buildHighlightImage(data: RecordData) { 465 565 const highlightColor = resolveHighlightColor(data.color); 466 - const bgTint = lightTint(highlightColor); 467 566 const quoteText = data.quote || data.text || "Highlighted passage"; 468 567 const len = quoteText.length; 469 568 470 - return { 471 - type: "div", 472 - props: { 473 - style: { 474 - display: "flex", 475 - flexDirection: "column", 476 - width: "100%", 477 - height: "100%", 478 - background: bgTint, 479 - padding: "48px 64px", 480 - fontFamily: "Inter", 569 + const children: unknown[] = [ 570 + header(data, highlightColor), 571 + { 572 + type: "div", 573 + props: { 574 + style: { 575 + color: highlightColor, 576 + fontSize: 120, 577 + fontWeight: 700, 578 + lineHeight: 1, 579 + marginTop: 28, 580 + opacity: 0.5, 581 + }, 582 + children: "\u201C", 481 583 }, 482 - children: [ 483 - headerWithBadge(data, highlightColor), 484 - { 485 - type: "div", 486 - props: { 487 - style: { 488 - color: highlightColor, 489 - fontSize: 120, 490 - fontWeight: 700, 491 - lineHeight: 1, 492 - marginTop: 28, 493 - }, 494 - children: "\u201C", 495 - }, 584 + }, 585 + { 586 + type: "div", 587 + props: { 588 + style: { 589 + color: C.text, 590 + fontSize: len > 150 ? 32 : len > 80 ? 40 : 50, 591 + fontWeight: 600, 592 + lineHeight: 1.4, 593 + marginTop: -28, 594 + overflow: "hidden", 496 595 }, 497 - { 498 - type: "div", 499 - props: { 500 - style: { 501 - color: C.text, 502 - fontSize: len > 150 ? 28 : len > 80 ? 34 : 42, 503 - fontWeight: 600, 504 - lineHeight: 1.4, 505 - marginTop: -30, 506 - overflow: "hidden", 507 - }, 508 - children: truncate(quoteText, 240), 509 - }, 510 - }, 511 - data.text && data.quote 512 - ? { 513 - type: "div", 514 - props: { 515 - style: { 516 - color: C.textSecondary, 517 - fontSize: 20, 518 - marginTop: 20, 519 - }, 520 - children: truncate(data.text, 80), 521 - }, 522 - } 523 - : null, 524 - { 525 - type: "div", 526 - props: { 527 - style: { 528 - display: "flex", 529 - alignItems: "center", 530 - marginTop: "auto", 531 - paddingTop: 16, 532 - }, 533 - children: [ 534 - data.source 535 - ? { 536 - type: "span", 537 - props: { 538 - style: { color: C.textMuted, fontSize: 16 }, 539 - children: data.source, 540 - }, 541 - } 542 - : null, 543 - ].filter(Boolean), 544 - }, 545 - }, 546 - ].filter(Boolean), 596 + children: truncate(quoteText, 200), 597 + }, 547 598 }, 548 - }; 599 + ]; 600 + 601 + if (data.text && data.quote) { 602 + children.push({ 603 + type: "div", 604 + props: { 605 + style: { color: C.textMuted, fontSize: 22, marginTop: 22 }, 606 + children: truncate(data.text, 80), 607 + }, 608 + }); 609 + } 610 + 611 + const f = footerEl(data.source); 612 + if (f) children.push(f); 613 + return card(children, highlightColor); 549 614 } 550 615 551 616 function buildBookmarkImage(data: RecordData) { 552 - const children: unknown[] = [headerWithBadge(data, typeColors.bookmark)]; 617 + const accent = typeAccent.bookmark; 618 + const children: unknown[] = [header(data, accent)]; 553 619 554 620 if (data.source) { 555 621 children.push({ 556 622 type: "div", 557 623 props: { 558 624 style: { 559 - color: typeColors.bookmark, 560 - fontSize: 18, 561 - marginTop: 32, 625 + display: "flex", 626 + alignItems: "center", 627 + marginTop: 36, 628 + gap: 10, 562 629 }, 563 - children: data.source, 630 + children: [ 631 + { 632 + type: "div", 633 + props: { 634 + style: { 635 + width: 10, 636 + height: 10, 637 + borderRadius: 5, 638 + background: accent, 639 + opacity: 0.6, 640 + }, 641 + }, 642 + }, 643 + { 644 + type: "span", 645 + props: { 646 + style: { color: accent, fontSize: 22, fontWeight: 500 }, 647 + children: data.source, 648 + }, 649 + }, 650 + ], 564 651 }, 565 652 }); 566 653 } ··· 571 658 props: { 572 659 style: { 573 660 color: C.text, 574 - fontSize: titleLen > 60 ? 34 : 42, 661 + fontSize: titleLen > 60 ? 40 : 50, 575 662 fontWeight: 700, 576 - lineHeight: 1.25, 577 - marginTop: data.source ? 10 : 32, 663 + lineHeight: 1.3, 664 + marginTop: data.source ? 14 : 36, 578 665 overflow: "hidden", 579 666 }, 580 - children: truncate(data.text || "Untitled Bookmark", 90), 667 + children: truncate(data.text || "Untitled Bookmark", 80), 581 668 }, 582 669 }); 583 670 ··· 587 674 props: { 588 675 style: { 589 676 color: C.textSecondary, 590 - fontSize: 22, 677 + fontSize: 26, 591 678 lineHeight: 1.5, 592 - marginTop: 16, 679 + marginTop: 18, 593 680 overflow: "hidden", 594 681 }, 595 - children: truncate(data.quote, 180), 682 + children: truncate(data.quote, 160), 596 683 }, 597 684 }); 598 685 } 599 686 600 - return wrap(children, lightTint(typeColors.bookmark)); 687 + return card(children, accent); 601 688 } 602 689 603 690 function buildCollectionImage(data: RecordData) { 604 - const children: unknown[] = [headerWithBadge(data, typeColors.collection)]; 691 + const accent = typeAccent.collection; 692 + const children: unknown[] = [header(data, accent)]; 693 + 694 + const iconChildren: unknown[] = []; 695 + if (data.icon) { 696 + if (data.icon.startsWith("icon:")) { 697 + const iconName = data.icon.replace("icon:", ""); 698 + const uri = lucideIconUri(iconName, accent); 699 + if (uri) { 700 + iconChildren.push({ 701 + type: "img", 702 + props: { 703 + src: uri, 704 + width: 64, 705 + height: 64, 706 + style: { flexShrink: 0 }, 707 + }, 708 + }); 709 + } 710 + } else { 711 + iconChildren.push({ 712 + type: "span", 713 + props: { 714 + style: { fontSize: 72, lineHeight: 1 }, 715 + children: data.icon, 716 + }, 717 + }); 718 + } 719 + } 720 + iconChildren.push({ 721 + type: "span", 722 + props: { 723 + style: { 724 + color: C.text, 725 + fontSize: (data.title || "").length > 24 ? 44 : 56, 726 + fontWeight: 700, 727 + overflow: "hidden", 728 + lineHeight: 1.2, 729 + }, 730 + children: truncate(data.title || "Collection", 36), 731 + }, 732 + }); 605 733 606 734 children.push({ 607 735 type: "div", ··· 609 737 style: { 610 738 display: "flex", 611 739 alignItems: "center", 612 - gap: 20, 613 - marginTop: 36, 740 + gap: 24, 741 + marginTop: 40, 614 742 }, 615 - children: [ 616 - { 617 - type: "span", 618 - props: { style: { fontSize: 52 }, children: data.icon }, 619 - }, 620 - { 621 - type: "span", 622 - props: { 623 - style: { 624 - color: C.text, 625 - fontSize: 44, 626 - fontWeight: 700, 627 - overflow: "hidden", 628 - }, 629 - children: truncate(data.title, 36), 630 - }, 631 - }, 632 - ], 743 + children: iconChildren, 633 744 }, 634 745 }); 635 746 ··· 638 749 props: { 639 750 style: { 640 751 color: data.description ? C.textSecondary : C.textMuted, 641 - fontSize: 24, 752 + fontSize: 28, 642 753 lineHeight: 1.5, 643 - marginTop: 20, 754 + marginTop: 22, 644 755 overflow: "hidden", 645 756 }, 646 757 children: data.description 647 - ? truncate(data.description, 180) 758 + ? truncate(data.description, 160) 648 759 : "A collection on Margin", 649 760 }, 650 761 }); 651 762 652 - return wrap(children, lightTint(typeColors.collection)); 763 + children.push({ 764 + type: "div", 765 + props: { 766 + style: { 767 + display: "flex", 768 + alignItems: "center", 769 + gap: 12, 770 + marginTop: "auto", 771 + paddingTop: 24, 772 + }, 773 + children: [ 774 + logoElement(24, C.textFaint), 775 + { 776 + type: "span", 777 + props: { 778 + style: { color: C.textFaint, fontSize: 20 }, 779 + children: "margin.at", 780 + }, 781 + }, 782 + ].filter(Boolean) as unknown[], 783 + }, 784 + }); 785 + 786 + return card(children, accent); 653 787 } 654 788 655 789 export const GET: APIRoute = async ({ url }) => {
+16
web/src/pages/profile/[did].astro
··· 1 + --- 2 + 3 + import AppLayout from '../../layouts/AppLayout.astro'; 4 + import Profile from '../../views/profile/Profile'; 5 + 6 + const { did } = Astro.params; 7 + const user = Astro.locals.user; 8 + 9 + if (!did) { 10 + return Astro.redirect('/home'); 11 + } 12 + --- 13 + 14 + <AppLayout title="Profile - Margin" user={user}> 15 + <Profile client:load did={did} /> 16 + </AppLayout>
+8
web/src/pages/profile/index.astro
··· 1 + --- 2 + 3 + const user = Astro.locals.user; 4 + if (user) { 5 + return Astro.redirect(`/profile/${user.did}`); 6 + } 7 + return Astro.redirect('/login'); 8 + ---
+12
web/src/pages/search.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Search from '../views/core/Search'; 5 + 6 + const user = Astro.locals.user; 7 + const q = Astro.url.searchParams.get('q') || undefined; 8 + --- 9 + 10 + <AppLayout title={q ? `Search: ${q} - Margin` : 'Search - Margin'} user={user}> 11 + <Search client:load initialQuery={q} /> 12 + </AppLayout>
+11
web/src/pages/settings.astro
··· 1 + --- 2 + 3 + import AppLayout from '../layouts/AppLayout.astro'; 4 + import Settings from '../views/core/Settings'; 5 + 6 + const user = Astro.locals.user; 7 + --- 8 + 9 + <AppLayout title="Settings - Margin" user={user}> 10 + <Settings client:load /> 11 + </AppLayout>
-38
web/src/routes/wrappers.tsx
··· 1 - import { useStore } from "@nanostores/react"; 2 - import React from "react"; 3 - import { Navigate, useParams } from "react-router-dom"; 4 - import { $user } from "../store/auth"; 5 - import CollectionDetail from "../views/collections/CollectionDetail"; 6 - import AnnotationDetail from "../views/content/AnnotationDetail"; 7 - import UrlPage from "../views/content/UrlPage"; 8 - import UserUrlPage from "../views/content/UserUrlPage"; 9 - import Profile from "../views/profile/Profile"; 10 - 11 - export function ProfileWrapper() { 12 - const { did } = useParams(); 13 - if (!did) return <Navigate to="/home" replace />; 14 - return <Profile key={did} did={did} />; 15 - } 16 - 17 - export function SelfProfileWrapper() { 18 - const user = useStore($user); 19 - if (!user) return <Navigate to="/login" replace />; 20 - return <Navigate to={`/profile/${user.did}`} replace />; 21 - } 22 - 23 - export function CollectionDetailWrapper() { 24 - const { handle, rkey } = useParams(); 25 - return <CollectionDetail handle={handle} rkey={rkey} />; 26 - } 27 - 28 - export function AnnotationDetailWrapper() { 29 - return <AnnotationDetail />; 30 - } 31 - 32 - export function UserUrlWrapper() { 33 - return <UserUrlPage />; 34 - } 35 - 36 - export function UrlWrapper() { 37 - return <UrlPage />; 38 - }
+5
web/src/styles/global.css
··· 169 169 transform: scale(1); 170 170 } 171 171 } 172 + 173 + ::view-transition-old(root), 174 + ::view-transition-new(root) { 175 + animation: none; 176 + }
+52 -35
web/src/views/About.tsx
··· 1 1 import React from "react"; 2 2 import { useStore } from "@nanostores/react"; 3 - import { Link } from "react-router-dom"; 3 + 4 4 import { $theme, cycleTheme } from "../store/theme"; 5 5 import { $user } from "../store/auth"; 6 6 import { ··· 118 118 const ExtensionIcon = 119 119 browser === "firefox" ? FaFirefox : browser === "edge" ? FaEdge : Chrome; 120 120 const extensionLabel = 121 - browser === "firefox" ? "Firefox" : browser === "edge" ? "Edge" : "Chrome"; 121 + browser === "firefox" 122 + ? "Firefox" 123 + : browser === "edge" 124 + ? "Edge" 125 + : browser === "safari" 126 + ? "Chrome" 127 + : "Chrome"; 122 128 123 129 const [isScrolled, setIsScrolled] = React.useState(false); 124 130 125 131 React.useEffect(() => { 126 132 const handleScroll = () => { 127 - setIsScrolled(window.scrollY > 20); 133 + setIsScrolled((prev) => 134 + prev ? window.scrollY > 10 : window.scrollY > 50, 135 + ); 128 136 }; 129 137 window.addEventListener("scroll", handleScroll, { passive: true }); 130 138 return () => window.removeEventListener("scroll", handleScroll); ··· 135 143 <nav 136 144 className={`sticky top-0 z-50 transition-all duration-300 ${ 137 145 isScrolled 138 - ? "bg-white/80 dark:bg-surface-900/80 backdrop-blur-xl border-b border-surface-200/50 dark:border-surface-800/50 shadow-sm" 146 + ? "bg-white/80 dark:bg-surface-950/80 backdrop-blur-xl border-b border-surface-200/40 dark:border-surface-800/40" 139 147 : "bg-transparent border-b border-transparent" 140 148 }`} 141 149 > 142 150 <div 143 151 className={`max-w-5xl mx-auto px-4 sm:px-6 flex items-center justify-between transition-all duration-300 ${ 144 - isScrolled ? "h-14" : "h-24" 152 + isScrolled ? "h-14" : "h-16" 145 153 }`} 146 154 > 147 - <div className="flex items-center gap-2.5"> 148 - <img src="/logo.svg" alt="Margin" className="w-7 h-7" /> 149 - <span className="font-display font-bold text-lg tracking-tight text-surface-900 dark:text-white"> 150 - Margin 151 - </span> 155 + <div className="flex items-center gap-6"> 156 + <a 157 + href="/" 158 + className="flex items-center gap-2.5 hover:opacity-80 transition-opacity" 159 + > 160 + <img src="/logo.svg" alt="Margin" className="w-7 h-7" /> 161 + <span className="font-display font-bold text-lg tracking-tight text-surface-900 dark:text-white"> 162 + Margin 163 + </span> 164 + </a> 165 + <div className="hidden md:flex items-center gap-0.5"> 166 + <a 167 + href="/home" 168 + className="text-[13px] font-medium text-surface-500 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white transition-colors px-3 py-1.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800" 169 + > 170 + Feed 171 + </a> 172 + <a 173 + href="/discover" 174 + className="text-[13px] font-medium text-surface-500 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white transition-colors px-3 py-1.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800" 175 + > 176 + Discover 177 + </a> 178 + </div> 152 179 </div> 153 - <div className="flex items-center gap-1 sm:gap-2"> 154 - <div className="hidden md:flex items-center gap-1 mr-2"> 155 - {user && ( 156 - <Link 157 - to="/home" 158 - className="text-[13px] font-medium text-surface-500 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white transition-colors px-3 py-1.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800" 159 - > 160 - Feed 161 - </Link> 162 - )} 163 - </div> 164 - 180 + <div className="flex items-center gap-2"> 165 181 {!user && ( 166 - <Link 167 - to="/login" 182 + <a 183 + href="/login" 168 184 className="text-[13px] font-medium text-surface-600 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white transition-colors px-3 py-1.5 rounded-lg hover:bg-surface-100 dark:hover:bg-surface-800" 169 185 > 170 186 Sign in 171 - </Link> 187 + </a> 172 188 )} 173 189 174 190 <a 175 191 href={extensionLink} 176 192 target="_blank" 177 193 rel="noopener noreferrer" 178 - className="text-[13px] font-semibold px-3 sm:px-4 py-1.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-lg hover:bg-surface-800 dark:hover:bg-surface-100 transition-colors" 194 + className="inline-flex items-center gap-1.5 text-[13px] font-semibold px-3.5 py-1.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-lg hover:bg-surface-800 dark:hover:bg-surface-100 transition-colors" 179 195 > 196 + <ExtensionIcon size={14} /> 180 197 <span className="hidden sm:inline">Get Extension</span> 181 198 <span className="sm:hidden">Install</span> 182 199 </a> ··· 243 260 </p> 244 261 245 262 <div className="flex flex-col sm:flex-row items-center justify-center gap-4 mt-4"> 246 - <Link 247 - to={user ? "/home" : "/login"} 263 + <a 264 + href={user ? "/home" : "/login"} 248 265 className="group inline-flex items-center justify-center gap-2 px-8 py-3.5 bg-surface-900 dark:bg-white text-white dark:text-surface-900 rounded-[14px] font-semibold hover:bg-surface-800 dark:hover:bg-surface-200 hover:scale-[1.02] shadow-sm transition-all duration-200 w-full sm:w-auto" 249 266 > 250 267 {user ? "Open App" : "Get Started"} ··· 252 269 size={18} 253 270 className="transition-transform group-hover:translate-x-1" 254 271 /> 255 - </Link> 272 + </a> 256 273 <a 257 274 href={extensionLink} 258 275 target="_blank" ··· 600 617 identity and install the extension to get started. 601 618 </p> 602 619 <div className="flex flex-col sm:flex-row items-center justify-center gap-3"> 603 - <Link 604 - to={user ? "/home" : "/login"} 620 + <a 621 + href={user ? "/home" : "/login"} 605 622 className="inline-flex items-center gap-2 px-7 py-3 bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-400 text-white rounded-xl font-semibold transition-colors" 606 623 > 607 624 {user ? "Open App" : "Sign in"} 608 625 <ArrowRight size={16} /> 609 - </Link> 626 + </a> 610 627 <a 611 628 href="https://github.com/margin-at" 612 629 target="_blank" ··· 655 672 </div> 656 673 <div className="flex items-center gap-5 text-sm text-surface-400 dark:text-surface-500"> 657 674 {user && ( 658 - <Link 659 - to="/home" 675 + <a 676 + href="/home" 660 677 className="hover:text-surface-600 dark:hover:text-surface-300 transition-colors" 661 678 > 662 679 Feed 663 - </Link> 680 + </a> 664 681 )} 665 682 <a 666 683 href="/privacy"
+9 -12
web/src/views/auth/Login.tsx
··· 1 1 import React, { useState, useEffect, useRef } from "react"; 2 - import { useSearchParams, Navigate } from "react-router-dom"; 3 2 import { AtSign } from "lucide-react"; 4 3 import SignUpModal from "../../components/modals/SignUpModal"; 5 4 import { ··· 10 9 import { Avatar } from "../../components/ui"; 11 10 import { useStore } from "@nanostores/react"; 12 11 import { $theme } from "../../store/theme"; 13 - import { $user } from "../../store/auth"; 14 12 15 - export default function Login() { 13 + interface LoginProps { 14 + initialError?: string; 15 + } 16 + 17 + export default function Login({ initialError }: LoginProps) { 16 18 useStore($theme); // ensure theme is applied on this page 17 - const user = useStore($user); 18 - const [searchParams] = useSearchParams(); 19 19 const [handle, setHandle] = useState(""); 20 20 const [suggestions, setSuggestions] = useState<ActorSearchItem[]>([]); 21 21 const [showSuggestions, setShowSuggestions] = useState(false); 22 22 const [loading, setLoading] = useState(false); 23 - const [error, setError] = useState<string | null>( 24 - searchParams.get("error") || null, 25 - ); 23 + const [error, setError] = useState<string | null>(initialError || null); 26 24 const [selectedIndex, setSelectedIndex] = useState(-1); 27 25 const [showSignUp, setShowSignUp] = useState(false); 28 26 ··· 139 137 try { 140 138 const result = await startLogin(handle.trim()); 141 139 if (result.authorizationUrl) { 140 + const url = new URL(result.authorizationUrl); 141 + if (url.protocol !== "https:") 142 + throw new Error("Invalid authorization URL"); 142 143 window.location.href = result.authorizationUrl; 143 144 } 144 145 } catch (err) { ··· 147 148 setLoading(false); 148 149 } 149 150 }; 150 - 151 - if (user) { 152 - return <Navigate to="/home" replace />; 153 - } 154 151 155 152 return ( 156 153 <div className="min-h-screen flex items-center justify-center bg-surface-100 dark:bg-surface-800 p-4">
+3 -4
web/src/views/collections/CollectionDetail.tsx
··· 1 1 import React, { useEffect, useState } from "react"; 2 - import { Link } from "react-router-dom"; 3 2 import { 4 3 getCollection, 5 4 getCollectionItems, ··· 154 153 </span> 155 154 <span> 156 155 by{" "} 157 - <Link 158 - to={`/profile/${collection.creator?.did}`} 156 + <a 157 + href={`/profile/${collection.creator?.did}`} 159 158 className="hover:text-primary-600 dark:hover:text-primary-400 hover:underline transition-colors" 160 159 > 161 160 {collection.creator?.displayName || 162 161 collection.creator?.handle} 163 - </Link> 162 + </a> 164 163 </span> 165 164 </div> 166 165 </div>
+33 -32
web/src/views/content/AnnotationDetail.tsx
··· 1 1 import React, { useEffect, useState } from "react"; 2 - import { useParams, Link, useLocation, useNavigate } from "react-router-dom"; 3 2 import { useStore } from "@nanostores/react"; 4 3 import { $user } from "../../store/auth"; 5 4 import { ··· 21 20 } from "lucide-react"; 22 21 import { getAvatarUrl } from "../../api/client"; 23 22 24 - export default function AnnotationDetail() { 25 - const { uri, did, rkey, handle, type } = useParams(); 26 - const location = useLocation(); 27 - const navigate = useNavigate(); 23 + interface AnnotationDetailProps { 24 + handle?: string; 25 + rkey?: string; 26 + type?: string; 27 + uri?: string; 28 + did?: string; 29 + } 30 + 31 + export default function AnnotationDetail({ 32 + handle, 33 + rkey, 34 + type, 35 + uri, 36 + did, 37 + }: AnnotationDetailProps) { 28 38 const user = useStore($user); 29 39 30 40 const [annotation, setAnnotation] = useState<AnnotationItem | null>(null); ··· 47 57 48 58 if (handle && rkey) { 49 59 let collection = "at.margin.annotation"; 50 - if (type === "highlight" || location.pathname.includes("/highlight/")) 51 - collection = "at.margin.highlight"; 52 - if (type === "bookmark" || location.pathname.includes("/bookmark/")) 53 - collection = "at.margin.bookmark"; 60 + if (type === "highlight") collection = "at.margin.highlight"; 61 + if (type === "bookmark") collection = "at.margin.bookmark"; 54 62 55 63 try { 56 64 const resolvedDid = await resolveHandle(handle); ··· 68 76 } 69 77 } else if (did && rkey) { 70 78 setTargetUri(`at://${did}/at.margin.annotation/${rkey}`); 71 - } else { 72 - const pathParts = (location.pathname || "").split("/"); 73 - const atIndex = pathParts.indexOf("at"); 74 - if ( 75 - atIndex !== -1 && 76 - pathParts[atIndex + 1] && 77 - pathParts[atIndex + 2] 78 - ) { 79 - setTargetUri( 80 - `at://${pathParts[atIndex + 1]}/at.margin.annotation/${pathParts[atIndex + 2]}`, 81 - ); 82 - } 83 79 } 84 80 } 85 81 resolve(); 86 - }, [uri, did, rkey, handle, type, location.pathname]); 82 + }, [uri, did, rkey, handle, type]); 87 83 88 84 const refreshReplies = async () => { 89 85 if (!targetUri) return; ··· 190 186 <p className="text-surface-500 dark:text-surface-400 text-sm mb-6"> 191 187 {error || "This may have been deleted."} 192 188 </p> 193 - <Link 194 - to="/home" 189 + <a 190 + href="/home" 195 191 className="px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors" 196 192 > 197 193 Back to Feed 198 - </Link> 194 + </a> 199 195 </div> 200 196 ); 201 197 } ··· 203 199 return ( 204 200 <div className="max-w-2xl mx-auto pb-20"> 205 201 <div className="mb-4"> 206 - <Link 207 - to="/home" 202 + <a 203 + href="/home" 208 204 className="inline-flex items-center gap-1.5 text-sm font-medium text-surface-500 dark:text-surface-400 hover:text-surface-900 dark:hover:text-white transition-colors" 209 205 > 210 206 <ArrowLeft size={16} /> 211 207 Back 212 - </Link> 208 + </a> 213 209 </div> 214 210 215 - <Card item={annotation} onDelete={() => navigate("/home")} /> 211 + <Card 212 + item={annotation} 213 + onDelete={() => { 214 + window.location.href = "/home"; 215 + }} 216 + /> 216 217 217 218 {annotation.type !== "Bookmark" && 218 219 annotation.type !== "Highlight" && ··· 282 283 <p className="text-surface-500 dark:text-surface-400 text-sm mb-2"> 283 284 Sign in to reply 284 285 </p> 285 - <Link 286 - to="/login" 286 + <a 287 + href="/login" 287 288 className="text-primary-600 dark:text-primary-400 font-medium hover:underline text-sm" 288 289 > 289 290 Log in 290 - </Link> 291 + </a> 291 292 </div> 292 293 )} 293 294
+8 -8
web/src/views/content/UrlPage.tsx
··· 13 13 Users, 14 14 } from "lucide-react"; 15 15 import React, { useCallback, useEffect, useRef, useState } from "react"; 16 - import { useNavigate, useParams } from "react-router-dom"; 17 16 import { getByTarget } from "../../api/client"; 18 17 import Card from "../../components/common/Card"; 19 18 import { Button, EmptyState, Input, Tabs } from "../../components/ui"; 20 19 import { $user } from "../../store/auth"; 21 20 import type { AnnotationItem } from "../../types"; 22 21 23 - export default function UrlPage() { 24 - const params = useParams(); 25 - const navigate = useNavigate(); 26 - const urlPath = params["*"]; 22 + interface UrlPageProps { 23 + urlPath?: string; 24 + } 25 + 26 + export default function UrlPage({ urlPath }: UrlPageProps) { 27 27 const targetUrl = urlPath ? decodeURIComponent(urlPath) : ""; 28 28 29 29 const [annotations, setAnnotations] = useState<AnnotationItem[]>([]); ··· 113 113 114 114 const handleNavigateMyAnnotations = useCallback(async () => { 115 115 if (!user?.handle || !targetUrl) return; 116 - navigate(`/${user.handle}/url/${encodeURIComponent(targetUrl)}`); 117 - }, [user?.handle, targetUrl, navigate]); 116 + window.location.href = `/${user.handle}/url/${encodeURIComponent(targetUrl)}`; 117 + }, [user?.handle, targetUrl]); 118 118 119 119 const totalItems = annotations.length + highlights.length; 120 120 ··· 167 167 const q = (formData.get("q") as string)?.trim(); 168 168 if (q) { 169 169 const encoded = encodeURIComponent(q); 170 - navigate(`/url/${encoded}`); 170 + window.location.href = `/url/${encoded}`; 171 171 } 172 172 }} 173 173 className="max-w-md mx-auto flex gap-2"
+6 -5
web/src/views/content/UserUrlPage.tsx
··· 7 7 Search, 8 8 } from "lucide-react"; 9 9 import React, { useCallback, useEffect, useState } from "react"; 10 - import { useParams } from "react-router-dom"; 11 10 import { getUserTargetItems } from "../../api/client"; 12 11 import Card from "../../components/common/Card"; 13 12 import Avatar from "../../components/ui/Avatar"; 14 13 import { EmptyState, Tabs } from "../../components/ui"; 15 14 import type { AnnotationItem, UserProfile } from "../../types"; 16 15 17 - export default function UserUrlPage() { 18 - const params = useParams(); 19 - const handle = params.handle; 20 - const urlPath = params["*"]; 16 + interface UserUrlPageProps { 17 + handle?: string; 18 + urlPath?: string; 19 + } 20 + 21 + export default function UserUrlPage({ handle, urlPath }: UserUrlPageProps) { 21 22 const targetUrl = urlPath || ""; 22 23 23 24 const [profile, setProfile] = useState<UserProfile | null>(null);
+9 -10
web/src/views/core/AdminModeration.tsx
··· 25 25 EyeOff, 26 26 } from "lucide-react"; 27 27 import { Avatar, EmptyState, Skeleton, Button } from "../../components/ui"; 28 - import { Link } from "react-router-dom"; 29 28 30 29 const STATUS_COLORS: Record<string, string> = { 31 30 pending: ··· 302 301 <span className="text-surface-400 dark:text-surface-500 text-xs uppercase tracking-wider"> 303 302 Reported User 304 303 </span> 305 - <Link 306 - to={`/profile/${report.subject.did}`} 304 + <a 305 + href={`/profile/${report.subject.did}`} 307 306 className="block mt-1 text-primary-600 dark:text-primary-400 hover:underline font-medium" 308 307 > 309 308 @{report.subject.handle || report.subject.did} 310 - </Link> 309 + </a> 311 310 </div> 312 311 <div> 313 312 <span className="text-surface-400 dark:text-surface-500 text-xs uppercase tracking-wider"> 314 313 Reporter 315 314 </span> 316 - <Link 317 - to={`/profile/${report.reporter.did}`} 315 + <a 316 + href={`/profile/${report.reporter.did}`} 318 317 className="block mt-1 text-primary-600 dark:text-primary-400 hover:underline font-medium" 319 318 > 320 319 @{report.reporter.handle || report.reporter.did} 321 - </Link> 320 + </a> 322 321 </div> 323 322 </div> 324 323 ··· 509 508 {label.val} 510 509 </span> 511 510 {label.subject && ( 512 - <Link 513 - to={`/profile/${label.subject.did}`} 511 + <a 512 + href={`/profile/${label.subject.did}`} 514 513 className="text-sm font-medium text-surface-900 dark:text-white hover:text-primary-600 dark:hover:text-primary-400 truncate" 515 514 > 516 515 @{label.subject.handle || label.subject.did} 517 - </Link> 516 + </a> 518 517 )} 519 518 </div> 520 519 <p className="text-xs text-surface-500 dark:text-surface-400 truncate">
+1 -1
web/src/views/core/Discover.tsx
··· 77 77 78 78 return ( 79 79 <div className="mx-auto max-w-2xl xl:max-w-none"> 80 - <div className="sticky top-0 z-10 bg-white/95 dark:bg-surface-800/95 backdrop-blur-sm pb-3 mb-2 -mx-1 px-1 pt-1 space-y-2"> 80 + <div className="sticky top-0 z-10 bg-white/90 dark:bg-surface-800/90 backdrop-blur-md pb-3 mb-2 -mx-1 px-1 pt-2 space-y-2"> 81 81 <div className="flex items-center gap-2"> 82 82 <Tabs tabs={tabs} activeTab={activeTab} onChange={handleTabChange} /> 83 83 <LayoutToggle className="hidden sm:inline-flex ml-auto" />
+24 -22
web/src/views/core/Feed.tsx
··· 2 2 import { clsx } from "clsx"; 3 3 import { Bookmark, Highlighter, MessageSquareText } from "lucide-react"; 4 4 import { useState } from "react"; 5 - import { useSearchParams } from "react-router-dom"; 6 5 import FeedItems from "../../components/feed/FeedItems"; 7 6 import { Button, Tabs } from "../../components/ui"; 8 7 import LayoutToggle from "../../components/ui/LayoutToggle"; 9 8 import { $user } from "../../store/auth"; 10 9 import { $feedLayout } from "../../store/feedLayout"; 10 + import type { UserProfile } from "../../types"; 11 11 12 12 interface FeedProps { 13 13 initialType?: string; 14 + initialTag?: string; 15 + initialUser?: UserProfile | null; 14 16 motivation?: string; 15 17 showTabs?: boolean; 16 18 emptyMessage?: string; ··· 18 20 19 21 export default function Feed({ 20 22 initialType = "all", 23 + initialTag, 24 + initialUser, 21 25 motivation, 22 26 showTabs = true, 23 27 emptyMessage = "No items found.", 24 28 }: FeedProps) { 25 - const [searchParams, setSearchParams] = useSearchParams(); 26 - const tag = searchParams.get("tag") || undefined; 27 - const user = useStore($user); 29 + const [tag, setTag] = useState<string | undefined>( 30 + initialTag || 31 + (typeof window !== "undefined" 32 + ? new URLSearchParams(window.location.search).get("tag") || undefined 33 + : undefined), 34 + ); 35 + const storeUser = useStore($user); 36 + const user = storeUser || initialUser || null; 28 37 const layout = useStore($feedLayout); 29 38 const [activeTab, setActiveTab] = useState(initialType); 30 39 const [activeFilter, setActiveFilter] = useState<string | undefined>( 31 40 motivation, 32 41 ); 33 42 43 + const clearTag = () => { 44 + setTag(undefined); 45 + const url = new URL(window.location.href); 46 + url.searchParams.delete("tag"); 47 + window.history.replaceState({}, "", url.toString()); 48 + }; 49 + 34 50 const handleTabChange = (id: string) => { 35 51 if (id === activeTab) return; 36 52 setActiveTab(id); 37 - setSearchParams((prev) => { 38 - const newParams = new URLSearchParams(prev); 39 - newParams.delete("tag"); 40 - return newParams; 41 - }); 53 + clearTag(); 42 54 window.scrollTo({ top: 0, behavior: "smooth" }); 43 55 }; 44 56 ··· 46 58 const next = id === "all" ? undefined : id; 47 59 if (next === activeFilter) return; 48 60 setActiveFilter(next); 49 - setSearchParams((prev) => { 50 - const newParams = new URLSearchParams(prev); 51 - newParams.delete("tag"); 52 - return newParams; 53 - }); 61 + clearTag(); 54 62 window.scrollTo({ top: 0, behavior: "smooth" }); 55 63 }; 56 64 ··· 94 102 )} 95 103 96 104 {showTabs && ( 97 - <div className="sticky top-0 z-10 bg-white/95 dark:bg-surface-800/95 backdrop-blur-sm pb-3 mb-2 -mx-1 px-1 pt-1 space-y-2"> 105 + <div className="sticky top-0 z-10 bg-white/90 dark:bg-surface-800/90 backdrop-blur-md pb-3 mb-2 -mx-1 px-1 pt-2 space-y-2"> 98 106 {!tag && ( 99 107 <Tabs 100 108 tabs={tabs} ··· 113 121 </span> 114 122 </h2> 115 123 <button 116 - onClick={() => { 117 - setSearchParams((prev) => { 118 - const newParams = new URLSearchParams(prev); 119 - newParams.delete("tag"); 120 - return newParams; 121 - }); 122 - }} 124 + onClick={clearTag} 123 125 className="text-sm text-surface-500 hover:text-surface-900 dark:hover:text-white" 124 126 > 125 127 Clear filter
+21 -16
web/src/views/core/New.tsx
··· 1 1 import React, { useState } from "react"; 2 - import { useNavigate, useSearchParams, Link } from "react-router-dom"; 3 2 import { useStore } from "@nanostores/react"; 4 3 import { $user } from "../../store/auth"; 5 4 import Composer from "../../components/feed/Composer"; 6 5 import type { Selector } from "../../types"; 7 6 8 - export default function NewAnnotationPage() { 7 + interface NewAnnotationProps { 8 + initialUrl?: string; 9 + initialSelectorJson?: string; 10 + initialQuote?: string; 11 + } 12 + 13 + export default function NewAnnotationPage({ 14 + initialUrl: propUrl, 15 + initialSelectorJson, 16 + initialQuote, 17 + }: NewAnnotationProps) { 9 18 const user = useStore($user); 10 - const navigate = useNavigate(); 11 - const [searchParams] = useSearchParams(); 12 19 13 - const initialUrl = searchParams.get("url") || ""; 20 + const initialUrl = propUrl || ""; 14 21 15 22 let initialSelector: Selector | null = null; 16 - const selectorParam = searchParams.get("selector"); 17 - if (selectorParam) { 23 + if (initialSelectorJson) { 18 24 try { 19 - initialSelector = JSON.parse(selectorParam); 25 + initialSelector = JSON.parse(initialSelectorJson); 20 26 } catch (e) { 21 27 console.error("Failed to parse selector:", e); 22 28 } 23 29 } 24 30 25 - const legacyQuote = searchParams.get("quote") || ""; 26 - if (legacyQuote && !initialSelector) { 31 + if (initialQuote && !initialSelector) { 27 32 initialSelector = { 28 33 type: "TextQuoteSelector", 29 - exact: legacyQuote, 34 + exact: initialQuote, 30 35 }; 31 36 } 32 37 ··· 42 47 <p className="text-surface-500 dark:text-surface-400 text-sm mb-5"> 43 48 You need a Bluesky account 44 49 </p> 45 - <Link 46 - to="/login" 50 + <a 51 + href="/login" 47 52 className="block w-full py-2.5 px-4 bg-primary-600 hover:bg-primary-700 text-white font-medium rounded-lg transition-colors" 48 53 > 49 54 Sign in with Bluesky 50 - </Link> 55 + </a> 51 56 </div> 52 57 </div> 53 58 ); 54 59 } 55 60 56 61 const handleSuccess = () => { 57 - navigate("/home"); 62 + window.location.href = "/home"; 58 63 }; 59 64 60 65 return ( ··· 97 102 } 98 103 selector={initialSelector} 99 104 onSuccess={handleSuccess} 100 - onCancel={() => navigate(-1)} 105 + onCancel={() => window.history.back()} 101 106 /> 102 107 </div> 103 108 </div>
+15 -15
web/src/views/core/Notifications.tsx
··· 1 1 import React, { useEffect, useState } from "react"; 2 - import { Link } from "react-router-dom"; 2 + 3 3 import { getNotifications, markNotificationsRead } from "../../api/client"; 4 4 import type { NotificationItem, AnnotationItem } from "../../types"; 5 5 import { ··· 192 192 {parentUri && ( 193 193 <p className="text-surface-400 dark:text-surface-500 text-xs mt-1"> 194 194 in reply to{" "} 195 - <Link 196 - to={`/annotation/${encodeURIComponent(parentUri)}`} 195 + <a 196 + href={`/annotation/${encodeURIComponent(parentUri)}`} 197 197 className="hover:underline text-primary-500" 198 198 onClick={(e) => e.stopPropagation()} 199 199 > 200 200 {parentIsReply ? "a reply" : "an annotation"} 201 - </Link> 201 + </a> 202 202 </p> 203 203 )} 204 204 </> ··· 208 208 if (!preview) return null; 209 209 210 210 return ( 211 - <Link 212 - to={href} 211 + <a 212 + href={href} 213 213 className="block mt-2 pl-3 border-l-2 border-surface-200 dark:border-surface-700 hover:border-primary-400 dark:hover:border-primary-500 transition-colors group" 214 214 > 215 215 {preview} 216 - </Link> 216 + </a> 217 217 ); 218 218 } 219 219 ··· 300 300 </div> 301 301 <div className="flex-1 min-w-0"> 302 302 <div className="flex items-start gap-2 flex-wrap"> 303 - <Link to={`/profile/${n.actor.did}`} className="shrink-0"> 303 + <a href={`/profile/${n.actor.did}`} className="shrink-0"> 304 304 <Avatar src={n.actor.avatar} size="xs" /> 305 - </Link> 305 + </a> 306 306 <div className="flex-1 min-w-0"> 307 307 <span className="text-surface-500 dark:text-surface-400 text-sm"> 308 - <Link 309 - to={`/profile/${n.actor.did}`} 308 + <a 309 + href={`/profile/${n.actor.did}`} 310 310 className="font-semibold text-surface-900 dark:text-white hover:underline" 311 311 > 312 312 {n.actor.displayName || `@${n.actor.handle}`} 313 - </Link>{" "} 313 + </a>{" "} 314 314 {n.type !== "follow" && n.subjectUri ? ( 315 - <Link 316 - to={`/annotation/${encodeURIComponent(n.subjectUri)}`} 315 + <a 316 + href={`/annotation/${encodeURIComponent(n.subjectUri)}`} 317 317 className="hover:underline" 318 318 > 319 319 {verb} 320 - </Link> 320 + </a> 321 321 ) : ( 322 322 verb 323 323 )}
+9 -6
web/src/views/core/Search.tsx
··· 1 1 import React, { useState, useEffect, useCallback, useRef } from "react"; 2 - import { useSearchParams } from "react-router-dom"; 3 2 import { 4 3 Search as SearchIcon, 5 4 Loader2, ··· 18 17 import { $user } from "../../store/auth"; 19 18 import { $feedLayout } from "../../store/feedLayout"; 20 19 21 - export default function Search() { 22 - const [searchParams, setSearchParams] = useSearchParams(); 23 - const initialQuery = searchParams.get("q") || ""; 20 + interface SearchProps { 21 + initialQuery?: string; 22 + } 23 + 24 + export default function Search({ initialQuery = "" }: SearchProps) { 24 25 const user = useStore($user); 25 26 const layout = useStore($feedLayout); 26 27 ··· 85 86 const handleSubmit = (e: React.FormEvent) => { 86 87 e.preventDefault(); 87 88 if (query.trim()) { 88 - setSearchParams({ q: query.trim() }); 89 + const url = new URL(window.location.href); 90 + url.searchParams.set("q", query.trim()); 91 + window.history.replaceState({}, "", url.toString()); 89 92 doSearch(query.trim()); 90 93 } 91 94 }; ··· 130 133 </form> 131 134 132 135 {initialQuery && ( 133 - <div className="sticky top-0 z-10 bg-white/95 dark:bg-surface-800/95 backdrop-blur-sm pb-3 mb-2 -mx-1 px-1 pt-1 space-y-2"> 136 + <div className="sticky top-0 z-10 bg-white/90 dark:bg-surface-800/90 backdrop-blur-md pb-3 mb-2 -mx-1 px-1 pt-2 space-y-2"> 134 137 <div className="flex items-center gap-1.5 flex-wrap"> 135 138 {filters.map((f) => { 136 139 const isActive =
+6 -7
web/src/views/core/Settings.tsx
··· 59 59 Switch, 60 60 } from "../../components/ui"; 61 61 import { AppleIcon } from "../../components/common/Icons"; 62 - import { Link } from "react-router-dom"; 63 62 import { HighlightImporter } from "./HighlightImporter"; 64 63 import IOSShortcutModal from "../../components/modals/IOSShortcutModal"; 65 64 ··· 364 363 key={b.did} 365 364 className="flex items-center justify-between p-3 bg-surface-50 dark:bg-surface-800 rounded-xl group hover:bg-surface-100 dark:hover:bg-surface-700 transition-all" 366 365 > 367 - <Link 368 - to={`/profile/${b.did}`} 366 + <a 367 + href={`/profile/${b.did}`} 369 368 className="flex items-center gap-3 min-w-0 flex-1" 370 369 > 371 370 <Avatar ··· 385 384 </p> 386 385 )} 387 386 </div> 388 - </Link> 387 + </a> 389 388 <button 390 389 onClick={async () => { 391 390 await unblockUser(b.did); ··· 420 419 key={m.did} 421 420 className="flex items-center justify-between p-3 bg-surface-50 dark:bg-surface-800 rounded-xl group hover:bg-surface-100 dark:hover:bg-surface-700 transition-all" 422 421 > 423 - <Link 424 - to={`/profile/${m.did}`} 422 + <a 423 + href={`/profile/${m.did}`} 425 424 className="flex items-center gap-3 min-w-0 flex-1" 426 425 > 427 426 <Avatar ··· 441 440 </p> 442 441 )} 443 442 </div> 444 - </Link> 443 + </a> 445 444 <button 446 445 onClick={async () => { 447 446 await unmuteUser(m.did);
+3 -4
web/src/views/profile/Profile.tsx
··· 16 16 VolumeX, 17 17 } from "lucide-react"; 18 18 import { useEffect, useRef, useState } from "react"; 19 - import { Link } from "react-router-dom"; 20 19 import { 21 20 blockUser, 22 21 getCollections, ··· 607 606 ) : ( 608 607 <div className="grid grid-cols-1 gap-2"> 609 608 {collections.map((collection) => ( 610 - <Link 609 + <a 611 610 key={collection.id} 612 - to={`/${collection.creator?.handle || profile.handle}/collection/${(collection.uri || "").split("/").pop()}`} 611 + href={`/${collection.creator?.handle || profile.handle}/collection/${(collection.uri || "").split("/").pop()}`} 613 612 className="group card p-4 hover:ring-primary-300 dark:hover:ring-primary-600 transition-all flex items-center gap-4" 614 613 > 615 614 <div className="p-2.5 bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-xl"> ··· 624 623 {collection.itemCount === 1 ? "item" : "items"} 625 624 </p> 626 625 </div> 627 - </Link> 626 + </a> 628 627 ))} 629 628 </div> 630 629 )