finished navbar and blog. todo: finishing touches
This commit is contained in:
121
template/INTEGRATION_COMPLETE.md
Normal file
121
template/INTEGRATION_COMPLETE.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Navbar Integration Complete ✅
|
||||
|
||||
This document summarizes the completed integration between the Sanity navbar schema and the SvelteKit frontend component.
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### 1. Navigation Data Service (`src/lib/navigation.ts`)
|
||||
- ✅ Fetches navbar documents from Sanity
|
||||
- ✅ Processes different sublink types (auto, tag, manual)
|
||||
- ✅ Resolves internal links using the link helper
|
||||
- ✅ Implements 5-minute caching for performance
|
||||
- ✅ Provides fallback navigation when no document exists
|
||||
- ✅ Uses generated TypeScript types for type safety
|
||||
|
||||
### 2. Updated Layout Integration
|
||||
- ✅ Modified `+layout.server.ts` to load navigation data
|
||||
- ✅ Updated `+layout.svelte` to pass navigation to navbar component
|
||||
- ✅ Added proper TypeScript return types
|
||||
- ✅ Server-side rendering for better SEO and performance
|
||||
|
||||
### 3. Enhanced Navbar Component
|
||||
- ✅ Removed hardcoded navigation items
|
||||
- ✅ Uses dynamic data from Sanity
|
||||
- ✅ Handles empty/undefined navigation gracefully
|
||||
- ✅ Maintains all existing UI functionality (mobile menu, animations, etc.)
|
||||
- ✅ Supports nested submenus from Sanity schema
|
||||
|
||||
### 4. Link Processing Integration
|
||||
- ✅ Uses existing `deconstructLink` helper for all link types
|
||||
- ✅ Supports static, external, email, phone, and internal links
|
||||
- ✅ Resolves internal page references to proper URLs
|
||||
- ✅ Handles different page types (custom, blog) correctly
|
||||
|
||||
## Schema Features Implemented
|
||||
|
||||
### Main Navigation Links
|
||||
- ✅ Text and link fields
|
||||
- ✅ All link types supported via link helper
|
||||
- ✅ Optional sublinks array
|
||||
|
||||
### Auto Sublinks
|
||||
- ✅ Fetches 5 most recent pages by type (custom/blog)
|
||||
- ✅ Automatic URL generation based on page type
|
||||
- ✅ Proper GROQ query with ordering and limits
|
||||
|
||||
### Tag-based Sublinks
|
||||
- ✅ Filters pages by selected tag reference
|
||||
- ✅ Configurable page type for tag filtering
|
||||
- ✅ Automatic page fetching and URL generation
|
||||
|
||||
### Manual Sublinks
|
||||
- ✅ Custom text and link configuration
|
||||
- ✅ Full link helper integration
|
||||
- ✅ Fallback handling for invalid links
|
||||
|
||||
## Performance Features
|
||||
|
||||
### Caching System
|
||||
- ✅ 5-minute in-memory cache
|
||||
- ✅ Reduces database queries on each page load
|
||||
- ✅ Automatic cache invalidation
|
||||
- ✅ Development-friendly cache clearing
|
||||
|
||||
### Server-Side Processing
|
||||
- ✅ All link resolution happens server-side
|
||||
- ✅ Better SEO with pre-rendered navigation
|
||||
- ✅ Faster client-side hydration
|
||||
- ✅ Reduced client-side JavaScript
|
||||
|
||||
## Developer Experience
|
||||
|
||||
### Type Safety
|
||||
- ✅ Generated TypeScript types from Sanity schema
|
||||
- ✅ Proper interface definitions for navigation items
|
||||
- ✅ Type-safe data flow from Sanity to component
|
||||
- ✅ IntelliSense support throughout the stack
|
||||
|
||||
### Error Handling
|
||||
- ✅ Graceful degradation when Sanity is unavailable
|
||||
- ✅ Console logging for debugging
|
||||
- ✅ Fallback navigation for empty documents
|
||||
- ✅ Safe handling of missing or invalid data
|
||||
|
||||
### Setup Tools
|
||||
- ✅ Automated setup script (`scripts/setup-navbar.js`)
|
||||
- ✅ Package.json script integration
|
||||
- ✅ Comprehensive documentation
|
||||
- ✅ Environment variable configuration
|
||||
|
||||
## File Changes Summary
|
||||
|
||||
### New Files Created
|
||||
- `src/lib/navigation.ts` - Navigation data service
|
||||
- `scripts/setup-navbar.js` - Initial document creation script
|
||||
- `NAVBAR_INTEGRATION.md` - Technical documentation
|
||||
- `NAVBAR_SETUP.md` - User setup guide
|
||||
- `INTEGRATION_COMPLETE.md` - This summary
|
||||
|
||||
### Modified Files
|
||||
- `+layout.server.ts` - Added navigation data loading
|
||||
- `+layout.svelte` - Pass navigation to navbar component
|
||||
- `navbar.svelte` - Use dynamic data instead of hardcoded
|
||||
- `package.json` - Added setup script and dependencies
|
||||
|
||||
### Generated Files
|
||||
- `sanity.types.ts` - Updated with Navbar type definitions
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Setup
|
||||
```bash
|
||||
# Generate types
|
||||
cd apps/studio && bun run generate
|
||||
|
||||
# Create navbar document
|
||||
SANITY_PROJECT_ID=your-id SANITY_TOKEN=your-token bun run setup:navbar
|
||||
```
|
||||
|
||||
### Component Usage
|
||||
```svelte
|
||||
<!-- Navigation is automatically loaded an
|
||||
98
template/ROUTING_GUIDE.md
Normal file
98
template/ROUTING_GUIDE.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# SvelteKit Routing Guide
|
||||
|
||||
This guide explains how the dynamic routing system works in this Sanity + SvelteKit template, including URL patterns and content management.
|
||||
|
||||
## URL Structure
|
||||
|
||||
The template uses the following URL patterns:
|
||||
|
||||
### Custom Pages
|
||||
- **Pattern**: `/{slug}`
|
||||
- **Example**: `/about`, `/contact`, `/services`
|
||||
- **Content Type**: `custom` pages from Sanity
|
||||
- **Route File**: `src/routes/[slug]/+page.svelte`
|
||||
|
||||
### Blog Posts
|
||||
- **Pattern**: `/blog/{slug}`
|
||||
- **Example**: `/blog/my-first-post`, `/blog/web-development-tips`
|
||||
- **Content Type**: `blog` posts from Sanity
|
||||
- **Route File**: `src/routes/blog/[slug]/+page.svelte`
|
||||
|
||||
### Blog Index
|
||||
- **Pattern**: `/blog`
|
||||
- **Shows**: List of all blog posts
|
||||
- **Route File**: `src/routes/blog/+page.svelte`
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/routes/
|
||||
├── [slug]/ # Custom pages at root level
|
||||
│ ├── +page.server.ts # Server-side data loading
|
||||
│ └── +page.svelte # Custom page component
|
||||
├── blog/
|
||||
│ ├── +page.server.ts # Blog index server load
|
||||
│ ├── +page.svelte # Blog index component
|
||||
│ └── [slug]/ # Individual blog posts
|
||||
│ ├── +page.server.ts # Blog post server load
|
||||
│ └── +page.svelte # Blog post component
|
||||
├── +layout.server.ts # Global layout data
|
||||
└── +layout.svelte # Global layout component
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. URL Resolution Order
|
||||
|
||||
SvelteKit resolves URLs in this order:
|
||||
1. **Exact routes** (e.g., `/blog`)
|
||||
2. **Parameterized routes** (e.g., `/blog/[slug]`, `/[slug]`)
|
||||
|
||||
This means:
|
||||
- `/blog` → Blog index page
|
||||
- `/blog/some-post` → Blog post page
|
||||
- `/about` → Custom page (if exists)
|
||||
- `/nonexistent` → 404 error
|
||||
|
||||
### 2. Data Loading Flow
|
||||
|
||||
#### Custom Pages (`/[slug]`)
|
||||
|
||||
```typescript
|
||||
// +page.server.ts
|
||||
const CUSTOM_QUERY = `*[_type == "custom" && slug.current == $slug][0]`;
|
||||
const custom = await serverClient.fetch(CUSTOM_QUERY, { slug });
|
||||
```
|
||||
|
||||
#### Blog Posts (`/blog/[slug]`)
|
||||
|
||||
```typescript
|
||||
// +page.server.ts
|
||||
const BLOG_QUERY = `*[_type == "blog" && slug.current == $slug][0]`;
|
||||
const blog = await serverClient.fetch(BLOG_QUERY, { slug });
|
||||
```
|
||||
|
||||
### 3. Content Types
|
||||
|
||||
#### Custom Page Schema
|
||||
```typescript
|
||||
type Custom = {
|
||||
_id: string;
|
||||
_type: 'custom';
|
||||
title?: string;
|
||||
slug?: Slug;
|
||||
tags?: Array<string>;
|
||||
publishedAt?: string;
|
||||
body?: BlockContent;
|
||||
seoTitle?: string;
|
||||
seoDescription?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### Blog Post Schema
|
||||
```typescript
|
||||
type Blog = {
|
||||
_id: string;
|
||||
_type: 'blog';
|
||||
title?: string;
|
||||
slug
|
||||
@@ -1,12 +1,14 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
|
||||
@import "tw-animate-css";
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--font-sans: 'Geist', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-sans:
|
||||
'Geist', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -129,3 +131,12 @@
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.line-clamp-3 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<body data-sveltekit-preload-data="false">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<blockquote
|
||||
class="my-6 rounded-r-lg border-l-4 border-primary bg-muted/30 py-4 pl-6 text-lg text-muted-foreground italic"
|
||||
>
|
||||
{@render children()}
|
||||
</blockquote>
|
||||
@@ -0,0 +1,53 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
let { portableText, children } = $props();
|
||||
const { value } = portableText;
|
||||
|
||||
const getHeadingClass = (style: string) => {
|
||||
const baseClasses = 'font-bold tracking-tight';
|
||||
|
||||
switch (style) {
|
||||
case 'h1':
|
||||
return cn(baseClasses, 'text-4xl md:text-5xl lg:text-6xl mb-6 mt-8');
|
||||
case 'h2':
|
||||
return cn(baseClasses, 'text-3xl md:text-4xl mb-4 mt-8');
|
||||
case 'h3':
|
||||
return cn(baseClasses, 'text-2xl md:text-3xl mb-3 mt-6');
|
||||
case 'h4':
|
||||
return cn(baseClasses, 'text-xl md:text-2xl mb-3 mt-6');
|
||||
case 'h5':
|
||||
return cn(baseClasses, 'text-lg md:text-xl mb-2 mt-4');
|
||||
case 'h6':
|
||||
return cn(baseClasses, 'text-base md:text-lg mb-2 mt-4');
|
||||
default:
|
||||
return cn(baseClasses, 'text-xl mb-3 mt-6');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if value.style === 'h1'}
|
||||
<h1 class={getHeadingClass(value.style)}>
|
||||
{@render children()}
|
||||
</h1>
|
||||
{:else if value.style === 'h2'}
|
||||
<h2 class={getHeadingClass(value.style)}>
|
||||
{@render children()}
|
||||
</h2>
|
||||
{:else if value.style === 'h3'}
|
||||
<h3 class={getHeadingClass(value.style)}>
|
||||
{@render children()}
|
||||
</h3>
|
||||
{:else if value.style === 'h4'}
|
||||
<h4 class={getHeadingClass(value.style)}>
|
||||
{@render children()}
|
||||
</h4>
|
||||
{:else if value.style === 'h5'}
|
||||
<h5 class={getHeadingClass(value.style)}>
|
||||
{@render children()}
|
||||
</h5>
|
||||
{:else if value.style === 'h6'}
|
||||
<h6 class={getHeadingClass(value.style)}>
|
||||
{@render children()}
|
||||
</h6>
|
||||
{/if}
|
||||
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<p class="mb-4 leading-relaxed text-foreground">
|
||||
{@render children()}
|
||||
</p>
|
||||
58
template/apps/client/src/lib/components/cover-image.svelte
Normal file
58
template/apps/client/src/lib/components/cover-image.svelte
Normal file
@@ -0,0 +1,58 @@
|
||||
<script lang="ts">
|
||||
import { getImageDimensions } from '@sanity/asset-utils';
|
||||
import { client } from '$lib/sanity';
|
||||
import imageUrlBuilder from '@sanity/image-url';
|
||||
|
||||
let { image, alt = '', width = 1200, height = 600, class: className = '' } = $props();
|
||||
|
||||
const { projectId, dataset } = client.config();
|
||||
const builder = imageUrlBuilder({ projectId: projectId ?? '', dataset: dataset ?? '' });
|
||||
|
||||
function generateCoverImageUrl(
|
||||
imageAsset: any,
|
||||
targetWidth: number,
|
||||
targetHeight: number
|
||||
): string {
|
||||
if (!imageAsset || !projectId || !dataset) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const imageRef = imageAsset.asset?._ref || imageAsset.asset?._id || imageAsset.asset;
|
||||
|
||||
if (!imageRef) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const imageBuilder = builder
|
||||
.image(imageRef)
|
||||
.fit('crop')
|
||||
.crop('center')
|
||||
.width(targetWidth)
|
||||
.height(targetHeight)
|
||||
.format('webp')
|
||||
.auto('format');
|
||||
|
||||
return imageBuilder.url();
|
||||
} catch (error) {
|
||||
console.error('Error generating cover image URL:', error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
const imageUrl = $derived(generateCoverImageUrl(image, width, height));
|
||||
const dimensions = $derived(
|
||||
image?.asset ? getImageDimensions(image) : { width: width, height: height }
|
||||
);
|
||||
</script>
|
||||
|
||||
{#if imageUrl}
|
||||
<img
|
||||
src={imageUrl}
|
||||
{alt}
|
||||
{width}
|
||||
{height}
|
||||
loading="lazy"
|
||||
class="h-full w-full object-cover object-center {className}"
|
||||
/>
|
||||
{/if}
|
||||
@@ -45,17 +45,7 @@
|
||||
items?: NavigationItem[];
|
||||
}
|
||||
|
||||
let {
|
||||
settings,
|
||||
items = [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Test', url: '/test' },
|
||||
{
|
||||
name: 'Test Subitems',
|
||||
subitems: [{ name: 'Test Page', url: '#' }]
|
||||
}
|
||||
]
|
||||
}: Props = $props();
|
||||
let { settings, items = [] }: Props = $props();
|
||||
|
||||
let activeIndex = $state(0);
|
||||
let navigationList: HTMLElement;
|
||||
@@ -74,7 +64,7 @@
|
||||
const currentPath = $page.url.pathname;
|
||||
let newActiveIndex = -1;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
items?.forEach((item, index) => {
|
||||
if (item.url === currentPath) {
|
||||
newActiveIndex = index;
|
||||
} else if (item.subitems) {
|
||||
@@ -125,15 +115,17 @@
|
||||
function toggleSubmenu(itemName: string) {
|
||||
expandedSubmenu = expandedSubmenu === itemName ? null : itemName;
|
||||
}
|
||||
|
||||
const shouldReload = $derived($page.url.pathname.startsWith('/blog/'));
|
||||
</script>
|
||||
|
||||
<nav class="bg-primary py-3">
|
||||
<nav class="relative z-[50] bg-primary py-3">
|
||||
<div class="container px-4 md:mx-auto md:px-0">
|
||||
<div class="relative flex items-center">
|
||||
<div class="flex items-center">
|
||||
{#if settings}
|
||||
<a href="/">
|
||||
<Button variant="ghost" class="px-1">
|
||||
<Button variant="ghost">
|
||||
<Logo {settings} class="text-white" />
|
||||
</Button>
|
||||
</a>
|
||||
@@ -143,35 +135,56 @@
|
||||
</div>
|
||||
|
||||
<div class="absolute left-1/2 hidden -translate-x-1/2 transform md:flex">
|
||||
<NavigationMenuRoot class="" viewport={false}>
|
||||
<NavigationMenuRoot class="relative z-[50]" viewport={false}>
|
||||
<div bind:this={navigationList}>
|
||||
<NavigationMenuList class="relative">
|
||||
<div
|
||||
class="pointer-events-none absolute top-0 z-10 h-full rounded-md bg-white/10"
|
||||
class="pointer-events-none absolute top-1/2 z-10 h-9 -translate-y-1/2 rounded-md bg-white/10"
|
||||
style="left: {$indicatorPosition.left}px; width: {$indicatorPosition.width}px;"
|
||||
></div>
|
||||
{#each items as item}
|
||||
{#each items || [] as item}
|
||||
<NavigationMenuItem data-navigation-menu-item>
|
||||
{#if item.url && !item.subitems}
|
||||
<NavigationMenuLink
|
||||
class={cn('relative z-20 text-white transition-colors hover:text-white/80')}
|
||||
href={item.url}
|
||||
data-sveltekit-reload={item.url?.startsWith('/blog/') && shouldReload
|
||||
? 'true'
|
||||
: undefined}
|
||||
>
|
||||
{item.name}
|
||||
</NavigationMenuLink>
|
||||
{:else if item.subitems}
|
||||
{#if item.url}
|
||||
<NavigationMenuTrigger
|
||||
class="relative z-20 text-white transition-colors hover:text-white/80"
|
||||
class="relative z-20 cursor-pointer px-2 text-white transition-colors hover:text-white/80"
|
||||
>
|
||||
<NavigationMenuLink
|
||||
href={item.url}
|
||||
data-sveltekit-reload={item.url?.startsWith('/blog/') && shouldReload
|
||||
? 'true'
|
||||
: undefined}
|
||||
>
|
||||
{item.name}
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuTrigger>
|
||||
{:else}
|
||||
<NavigationMenuTrigger
|
||||
class="relative z-20 cursor-pointer text-white transition-colors hover:text-white/80"
|
||||
>
|
||||
{item.name}
|
||||
</NavigationMenuTrigger>
|
||||
{/if}
|
||||
<NavigationMenuContent
|
||||
class="bg-primary absolute left-1/2 z-50 mt-2 min-w-full -translate-x-1/2 rounded-md border border-white/20 shadow-lg"
|
||||
class="absolute left-1/2 z-[50] mt-2 min-w-max -translate-x-1/2 rounded-md border border-white/20 bg-primary shadow-lg"
|
||||
>
|
||||
{#each item.subitems as subitem}
|
||||
<NavigationMenuLink
|
||||
href={subitem.url}
|
||||
class="block rounded-md px-4 py-2 text-white transition-colors hover:text-white/80"
|
||||
data-sveltekit-reload={subitem.url?.startsWith('/blog/') && shouldReload
|
||||
? 'true'
|
||||
: undefined}
|
||||
>
|
||||
{subitem.name}
|
||||
</NavigationMenuLink>
|
||||
@@ -191,7 +204,7 @@
|
||||
<MoreHorizontal size={24} />
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent side="top" class="bg-primary border-primary h-full w-full text-white">
|
||||
<SheetContent side="top" class="h-full w-full border-primary bg-primary text-white">
|
||||
<SheetHeader class="border-b border-white/20 pb-4">
|
||||
<SheetTitle class="flex items-center justify-start">
|
||||
{#if settings}
|
||||
@@ -206,7 +219,7 @@
|
||||
</SheetTitle>
|
||||
</SheetHeader>
|
||||
<div class="relative flex flex-col space-y-2 pt-6">
|
||||
{#each items as item, index}
|
||||
{#each items || [] as item, index}
|
||||
{#if item.url && !item.subitems}
|
||||
<div class="relative">
|
||||
<a
|
||||
@@ -216,6 +229,9 @@
|
||||
index === activeIndex && 'bg-white/20'
|
||||
)}
|
||||
onclick={closeMobileMenu}
|
||||
data-sveltekit-reload={item.url?.startsWith('/blog/') && shouldReload
|
||||
? 'true'
|
||||
: undefined}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
@@ -224,7 +240,7 @@
|
||||
<div class="relative space-y-1">
|
||||
{#if index === activeIndex}
|
||||
<div
|
||||
class="absolute bottom-0 left-0 top-0 w-1 rounded-r-md bg-white/20"
|
||||
class="absolute top-0 bottom-0 left-0 w-1 rounded-r-md bg-white/20"
|
||||
></div>
|
||||
{/if}
|
||||
<Button
|
||||
@@ -246,11 +262,26 @@
|
||||
class="ml-4 space-y-1"
|
||||
transition:slide={{ duration: 300, easing: quintOut }}
|
||||
>
|
||||
{#if item.url}
|
||||
<a
|
||||
href={item.url}
|
||||
class="block rounded-md px-6 py-2 text-sm text-white/80 transition-colors hover:text-white"
|
||||
onclick={closeMobileMenu}
|
||||
data-sveltekit-reload={item.url?.startsWith('/blog/') && shouldReload
|
||||
? 'true'
|
||||
: undefined}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
{/if}
|
||||
{#each item.subitems as subitem}
|
||||
<a
|
||||
href={subitem.url}
|
||||
class="block rounded-md px-6 py-2 text-sm text-white/80 transition-colors hover:text-white"
|
||||
onclick={closeMobileMenu}
|
||||
data-sveltekit-reload={subitem.url?.startsWith('/blog/') && shouldReload
|
||||
? 'true'
|
||||
: undefined}
|
||||
>
|
||||
{subitem.name}
|
||||
</a>
|
||||
|
||||
@@ -6,11 +6,22 @@
|
||||
import SanityButton from './blocks/sanity-button.svelte';
|
||||
import Callout from './blocks/callout.svelte';
|
||||
import LinkMark from './blocks/link-mark.svelte';
|
||||
import Heading from './blocks/heading.svelte';
|
||||
import Blockquote from './blocks/blockquote.svelte';
|
||||
import Paragraph from './blocks/paragraph.svelte';
|
||||
|
||||
let { body }: { body: BlockContent } = $props();
|
||||
</script>
|
||||
|
||||
<div class="prose prose-lg max-w-none">
|
||||
<div
|
||||
class="prose prose-lg prose-strong:font-bold
|
||||
prose-em:italic prose-code:bg-muted
|
||||
prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono prose-a:text-primary
|
||||
prose-a:hover:underline prose-ul:list-disc
|
||||
prose-ul:ml-6 prose-ul:space-y-2 prose-ol:list-decimal
|
||||
prose-ol:ml-6 prose-ol:space-y-2 prose-li:leading-relaxed
|
||||
max-w-none"
|
||||
>
|
||||
<PortableText
|
||||
value={body}
|
||||
components={{
|
||||
@@ -21,6 +32,16 @@
|
||||
file: SanityFile,
|
||||
button: SanityButton
|
||||
},
|
||||
block: {
|
||||
h1: Heading,
|
||||
h2: Heading,
|
||||
h3: Heading,
|
||||
h4: Heading,
|
||||
h5: Heading,
|
||||
h6: Heading,
|
||||
blockquote: Blockquote,
|
||||
normal: Paragraph
|
||||
},
|
||||
marks: {
|
||||
link: LinkMark
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-action"
|
||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn("[.border-t]:pt-6 flex items-center px-6", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-title"
|
||||
class={cn("font-semibold leading-none", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
template/apps/client/src/lib/components/ui/card/card.svelte
Normal file
23
template/apps/client/src/lib/components/ui/card/card.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
25
template/apps/client/src/lib/components/ui/card/index.ts
Normal file
25
template/apps/client/src/lib/components/ui/card/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Root from "./card.svelte";
|
||||
import Content from "./card-content.svelte";
|
||||
import Description from "./card-description.svelte";
|
||||
import Footer from "./card-footer.svelte";
|
||||
import Header from "./card-header.svelte";
|
||||
import Title from "./card-title.svelte";
|
||||
import Action from "./card-action.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import Root from "./pagination.svelte";
|
||||
import Content from "./pagination-content.svelte";
|
||||
import Item from "./pagination-item.svelte";
|
||||
import Link from "./pagination-link.svelte";
|
||||
import PrevButton from "./pagination-prev-button.svelte";
|
||||
import NextButton from "./pagination-next-button.svelte";
|
||||
import Ellipsis from "./pagination-ellipsis.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Item,
|
||||
Link,
|
||||
PrevButton,
|
||||
NextButton,
|
||||
Ellipsis,
|
||||
//
|
||||
Root as Pagination,
|
||||
Content as PaginationContent,
|
||||
Item as PaginationItem,
|
||||
Link as PaginationLink,
|
||||
PrevButton as PaginationPrevButton,
|
||||
NextButton as PaginationNextButton,
|
||||
Ellipsis as PaginationEllipsis,
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLUListElement>> = $props();
|
||||
</script>
|
||||
|
||||
<ul
|
||||
bind:this={ref}
|
||||
data-slot="pagination-content"
|
||||
class={cn("flex flex-row items-center gap-1", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</ul>
|
||||
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildren<WithElementRef<HTMLAttributes<HTMLSpanElement>>> = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={ref}
|
||||
aria-hidden="true"
|
||||
data-slot="pagination-ellipsis"
|
||||
class={cn("flex size-9 items-center justify-center", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<EllipsisIcon class="size-4" />
|
||||
<span class="sr-only">More pages</span>
|
||||
</span>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLLiAttributes } from "svelte/elements";
|
||||
import type { WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLLiAttributes> = $props();
|
||||
</script>
|
||||
|
||||
<li bind:this={ref} data-slot="pagination-item" {...restProps}>
|
||||
{@render children?.()}
|
||||
</li>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { type Props, buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
size = "icon",
|
||||
isActive,
|
||||
page,
|
||||
children,
|
||||
...restProps
|
||||
}: PaginationPrimitive.PageProps &
|
||||
Props & {
|
||||
isActive: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
{#snippet Fallback()}
|
||||
{page.value}
|
||||
{/snippet}
|
||||
|
||||
<PaginationPrimitive.Page
|
||||
bind:ref
|
||||
{page}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
data-slot="pagination-link"
|
||||
data-active={isActive}
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? "outline" : "ghost",
|
||||
size,
|
||||
}),
|
||||
className
|
||||
)}
|
||||
children={children || Fallback}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: PaginationPrimitive.NextButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#snippet Fallback()}
|
||||
<span>Next</span>
|
||||
<ChevronRightIcon class="size-4" />
|
||||
{/snippet}
|
||||
|
||||
<PaginationPrimitive.NextButton
|
||||
bind:ref
|
||||
aria-label="Go to next page"
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
size: "default",
|
||||
variant: "ghost",
|
||||
class: "gap-1 px-2.5 sm:pr-2.5",
|
||||
}),
|
||||
className
|
||||
)}
|
||||
children={children || Fallback}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
import ChevronLeftIcon from "@lucide/svelte/icons/chevron-left";
|
||||
import { buttonVariants } from "$lib/components/ui/button/index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: PaginationPrimitive.PrevButtonProps = $props();
|
||||
</script>
|
||||
|
||||
{#snippet Fallback()}
|
||||
<ChevronLeftIcon class="size-4" />
|
||||
<span>Previous</span>
|
||||
{/snippet}
|
||||
|
||||
<PaginationPrimitive.PrevButton
|
||||
bind:ref
|
||||
aria-label="Go to previous page"
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
size: "default",
|
||||
variant: "ghost",
|
||||
class: "gap-1 px-2.5 sm:pl-2.5",
|
||||
}),
|
||||
className
|
||||
)}
|
||||
children={children || Fallback}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Pagination as PaginationPrimitive } from "bits-ui";
|
||||
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
count = 0,
|
||||
perPage = 10,
|
||||
page = $bindable(1),
|
||||
siblingCount = 1,
|
||||
...restProps
|
||||
}: PaginationPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<PaginationPrimitive.Root
|
||||
bind:ref
|
||||
bind:page
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
data-slot="pagination"
|
||||
class={cn("mx-auto flex w-full justify-center", className)}
|
||||
{count}
|
||||
{perPage}
|
||||
{siblingCount}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -50,6 +50,8 @@ export async function deconstructLink(
|
||||
href =
|
||||
resolved.type === 'custom'
|
||||
? `/${resolved.slug || ''}`
|
||||
: resolved.type === 'blog'
|
||||
? `/blog/${resolved.slug || ''}`
|
||||
: `/${resolved.type}/${resolved.slug || ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
182
template/apps/client/src/lib/helper/navigation.ts
Normal file
182
template/apps/client/src/lib/helper/navigation.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import type { NavigationItem } from '$lib/components/navbar.svelte';
|
||||
import type { Navbar } from '$lib/sanity.types';
|
||||
import { serverClient } from '$lib/server/sanity';
|
||||
import { deconstructLink } from './link';
|
||||
|
||||
interface PageResult {
|
||||
_id: string;
|
||||
_type: string;
|
||||
title: string;
|
||||
slug: {
|
||||
current: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchNavigation(): Promise<NavigationItem[]> {
|
||||
try {
|
||||
const navbar = await serverClient.fetch<Navbar>(`
|
||||
*[_type == "navbar"][0]{
|
||||
_id,
|
||||
_type,
|
||||
title,
|
||||
links[]{
|
||||
_key,
|
||||
text,
|
||||
link,
|
||||
sublinks[]{
|
||||
_key,
|
||||
type,
|
||||
text,
|
||||
link,
|
||||
pageType,
|
||||
tagFilter->{
|
||||
_id,
|
||||
title,
|
||||
slug
|
||||
},
|
||||
tagPageType
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
if (!navbar?.links || navbar.links.length === 0) {
|
||||
// Return fallback navigation when no navbar document exists
|
||||
return [
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'About', url: '/about' },
|
||||
{ name: 'Contact', url: '/contact' }
|
||||
];
|
||||
}
|
||||
|
||||
const navigationItems: NavigationItem[] = [];
|
||||
|
||||
for (const link of navbar.links) {
|
||||
if (!link.text) continue;
|
||||
|
||||
const deconstructedLink = await deconstructLink(link.link);
|
||||
|
||||
const navigationItem: NavigationItem = {
|
||||
name: link.text,
|
||||
url: deconstructedLink?.href || '#'
|
||||
};
|
||||
|
||||
// Process sublinks if they exist
|
||||
if (link.sublinks && link.sublinks.length > 0) {
|
||||
const subitems = [];
|
||||
|
||||
for (const sublink of link.sublinks) {
|
||||
switch (sublink.type) {
|
||||
case 'manual': {
|
||||
if (sublink.text && sublink.link) {
|
||||
const deconstructedSublink = await deconstructLink(sublink.link);
|
||||
if (deconstructedSublink?.href) {
|
||||
subitems.push({
|
||||
name: sublink.text,
|
||||
url: deconstructedSublink.href
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'auto': {
|
||||
const autoPages = await fetchAutoPages(sublink.pageType || 'custom');
|
||||
for (const page of autoPages) {
|
||||
const pageUrl =
|
||||
page._type === 'custom'
|
||||
? `/${page.slug.current}`
|
||||
: `/${page._type}/${page.slug.current}`;
|
||||
|
||||
subitems.push({
|
||||
name: page.title,
|
||||
url: pageUrl
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'tag': {
|
||||
if (sublink.tagFilter?._ref && sublink.tagPageType) {
|
||||
const tagPages = await fetchPagesByTag(sublink.tagFilter._ref, sublink.tagPageType);
|
||||
for (const page of tagPages) {
|
||||
const pageUrl =
|
||||
page._type === 'custom'
|
||||
? `/${page.slug.current}`
|
||||
: `/${page._type}/${page.slug.current}`;
|
||||
|
||||
subitems.push({
|
||||
name: page.title,
|
||||
url: pageUrl
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subitems.length > 0) {
|
||||
navigationItem.subitems = subitems;
|
||||
}
|
||||
}
|
||||
|
||||
navigationItems.push(navigationItem);
|
||||
}
|
||||
|
||||
return navigationItems;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch navigation:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAutoPages(pageType: string): Promise<PageResult[]> {
|
||||
const query = `*[_type == $pageType && defined(slug.current)] | order(_createdAt desc)[0...5]{
|
||||
_id,
|
||||
_type,
|
||||
title,
|
||||
slug
|
||||
}`;
|
||||
|
||||
try {
|
||||
return await serverClient.fetch<PageResult[]>(query, { pageType });
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch auto pages for type ${pageType}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPagesByTag(tagId: string, pageType: string): Promise<PageResult[]> {
|
||||
const query = `*[_type == $pageType && defined(slug.current) && $tagId in tags[]._ref] | order(_createdAt desc)[0...5]{
|
||||
_id,
|
||||
_type,
|
||||
title,
|
||||
slug
|
||||
}`;
|
||||
|
||||
try {
|
||||
return await serverClient.fetch<PageResult[]>(query, { tagId, pageType });
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch pages by tag ${tagId} for type ${pageType}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the navigation data for better performance
|
||||
let navigationCache: NavigationItem[] | null = null;
|
||||
let cacheTimestamp: number = 0;
|
||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
export async function getCachedNavigation(): Promise<NavigationItem[]> {
|
||||
const now = Date.now();
|
||||
|
||||
if (navigationCache && now - cacheTimestamp < CACHE_DURATION) {
|
||||
return navigationCache;
|
||||
}
|
||||
|
||||
navigationCache = await fetchNavigation();
|
||||
cacheTimestamp = now;
|
||||
|
||||
return navigationCache;
|
||||
}
|
||||
@@ -20,9 +20,92 @@ export type Home = {
|
||||
_updatedAt: string;
|
||||
_rev: string;
|
||||
title?: string;
|
||||
publishedAt?: string;
|
||||
headerSection?: CtaSection;
|
||||
};
|
||||
|
||||
export type Blog = {
|
||||
_id: string;
|
||||
_type: 'blog';
|
||||
_createdAt: string;
|
||||
_updatedAt: string;
|
||||
_rev: string;
|
||||
title?: string;
|
||||
slug?: Slug;
|
||||
author?: string;
|
||||
publishedAt?: string;
|
||||
tags?: Array<string>;
|
||||
excerpt?: string;
|
||||
mainImage?: {
|
||||
asset?: {
|
||||
_ref: string;
|
||||
_type: 'reference';
|
||||
_weak?: boolean;
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset';
|
||||
};
|
||||
media?: unknown;
|
||||
hotspot?: SanityImageHotspot;
|
||||
crop?: SanityImageCrop;
|
||||
alt?: string;
|
||||
_type: 'imageWithAlt';
|
||||
};
|
||||
body?: Array<
|
||||
| {
|
||||
children?: Array<{
|
||||
marks?: Array<string>;
|
||||
text?: string;
|
||||
_type: 'span';
|
||||
_key: string;
|
||||
}>;
|
||||
style?: 'normal' | 'h1' | 'h2' | 'h3' | 'h4' | 'blockquote';
|
||||
listItem?: 'bullet';
|
||||
markDefs?: Array<
|
||||
| {
|
||||
href?: Link;
|
||||
_type: 'link';
|
||||
_key: string;
|
||||
}
|
||||
| ({
|
||||
_key: string;
|
||||
} & TextColor)
|
||||
| ({
|
||||
_key: string;
|
||||
} & HighlightColor)
|
||||
>;
|
||||
level?: number;
|
||||
_type: 'block';
|
||||
_key: string;
|
||||
}
|
||||
| ({
|
||||
_key: string;
|
||||
} & Button)
|
||||
| {
|
||||
asset?: {
|
||||
_ref: string;
|
||||
_type: 'reference';
|
||||
_weak?: boolean;
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.imageAsset';
|
||||
};
|
||||
media?: unknown;
|
||||
hotspot?: SanityImageHotspot;
|
||||
crop?: SanityImageCrop;
|
||||
_type: 'image';
|
||||
_key: string;
|
||||
}
|
||||
| {
|
||||
asset?: {
|
||||
_ref: string;
|
||||
_type: 'reference';
|
||||
_weak?: boolean;
|
||||
[internalGroqTypeReferenceTo]?: 'sanity.fileAsset';
|
||||
};
|
||||
media?: unknown;
|
||||
_type: 'file';
|
||||
_key: string;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
export type FaqSection = {
|
||||
_type: 'faqSection';
|
||||
sectionTitle?: string;
|
||||
@@ -200,6 +283,45 @@ export type Button = {
|
||||
link?: Link;
|
||||
};
|
||||
|
||||
export type Navbar = {
|
||||
_id: string;
|
||||
_type: 'navbar';
|
||||
_createdAt: string;
|
||||
_updatedAt: string;
|
||||
_rev: string;
|
||||
title?: string;
|
||||
links?: Array<{
|
||||
text?: string;
|
||||
link?: Link;
|
||||
sublinks?: Array<{
|
||||
type?: 'auto' | 'tag' | 'manual';
|
||||
pageType?: 'custom' | 'blog';
|
||||
text?: string;
|
||||
link?: Link;
|
||||
tagFilter?: {
|
||||
_ref: string;
|
||||
_type: 'reference';
|
||||
_weak?: boolean;
|
||||
[internalGroqTypeReferenceTo]?: 'tag';
|
||||
};
|
||||
tagPageType?: 'custom' | 'blog';
|
||||
_key: string;
|
||||
}>;
|
||||
_key: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type Tag = {
|
||||
_id: string;
|
||||
_type: 'tag';
|
||||
_createdAt: string;
|
||||
_updatedAt: string;
|
||||
_rev: string;
|
||||
title?: string;
|
||||
slug?: Slug;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
_id: string;
|
||||
_type: 'settings';
|
||||
@@ -401,6 +523,8 @@ export type Custom = {
|
||||
_rev: string;
|
||||
title?: string;
|
||||
slug?: Slug;
|
||||
tags?: Array<string>;
|
||||
publishedAt?: string;
|
||||
body?: BlockContent;
|
||||
};
|
||||
|
||||
@@ -543,12 +667,15 @@ export type SanityAssetSourceData = {
|
||||
|
||||
export type AllSanitySchemaTypes =
|
||||
| Home
|
||||
| Blog
|
||||
| FaqSection
|
||||
| CtaSection
|
||||
| ContactSection
|
||||
| ImageWithAlt
|
||||
| Faq
|
||||
| Button
|
||||
| Navbar
|
||||
| Tag
|
||||
| Settings
|
||||
| BlockContent
|
||||
| HighlightColor
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
|
||||
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
|
||||
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
||||
|
||||
export function formatDate(date: Date): string {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
106
template/apps/client/src/lib/utils/client-blog-fetch.ts
Normal file
106
template/apps/client/src/lib/utils/client-blog-fetch.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { serverClient } from '$lib/server/sanity';
|
||||
import type { Blog } from '$lib/sanity.types';
|
||||
|
||||
const BLOGS_PER_PAGE = 12;
|
||||
|
||||
const BLOGS_QUERY = `*[_type == "blog" && defined(slug.current)] | order(publishedAt desc, _createdAt desc)[$start...$end]{
|
||||
_id,
|
||||
_type,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
title,
|
||||
slug,
|
||||
author,
|
||||
publishedAt,
|
||||
tags,
|
||||
excerpt,
|
||||
mainImage,
|
||||
body[0...2]
|
||||
}`;
|
||||
|
||||
const TOTAL_BLOGS_QUERY = `count(*[_type == "blog" && defined(slug.current)])`;
|
||||
|
||||
export interface BlogPaginationData {
|
||||
blogs: Blog[];
|
||||
pagination: {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
totalBlogs: number;
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchBlogsClientSide(page: number = 1): Promise<BlogPaginationData> {
|
||||
if (!browser) {
|
||||
throw new Error('This function should only be called on the client side');
|
||||
}
|
||||
|
||||
const start = (page - 1) * BLOGS_PER_PAGE;
|
||||
const end = start + BLOGS_PER_PAGE;
|
||||
|
||||
try {
|
||||
const [blogs, totalBlogs]: [Blog[], number] = await Promise.all([
|
||||
serverClient.fetch(BLOGS_QUERY, { start, end }),
|
||||
serverClient.fetch(TOTAL_BLOGS_QUERY)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(totalBlogs / BLOGS_PER_PAGE);
|
||||
|
||||
// Process blogs to extract descriptions if excerpt doesn't exist
|
||||
const processedBlogs = blogs.map((blog) => {
|
||||
let description = blog.excerpt;
|
||||
|
||||
if (!description && blog.body && Array.isArray(blog.body)) {
|
||||
const firstBlock = blog.body[0];
|
||||
if (
|
||||
firstBlock &&
|
||||
firstBlock._type === 'block' &&
|
||||
'children' in firstBlock &&
|
||||
Array.isArray(firstBlock.children)
|
||||
) {
|
||||
description = firstBlock.children
|
||||
.filter((child: any) => child.text)
|
||||
.map((child: any) => child.text)
|
||||
.join(' ')
|
||||
.slice(0, 160);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...blog,
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
blogs: processedBlogs,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalBlogs,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPreviousPage: page > 1
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching blogs client-side:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative: Create an API endpoint for client-side fetching
|
||||
export async function fetchBlogsViaAPI(page: number = 1): Promise<BlogPaginationData> {
|
||||
if (!browser) {
|
||||
throw new Error('This function should only be called on the client side');
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/blogs?page=${page}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch blogs');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
@@ -1,14 +1,24 @@
|
||||
import type {LayoutServerLoad} from './$types'
|
||||
import { fetchSettings } from '$lib/settings'
|
||||
import { getImage } from '$lib/helper/asset-to-url'
|
||||
import { fetchSettings } from '$lib/settings';
|
||||
import { getImage } from '$lib/helper/asset-to-url';
|
||||
import { getCachedNavigation } from '$lib/helper/navigation';
|
||||
import type { NavigationItem } from '$lib/components/navbar.svelte';
|
||||
|
||||
export const load: LayoutServerLoad = async ({locals: {preview}}) => {
|
||||
const settings = await fetchSettings()
|
||||
const logo = settings?.logo?.asset?._ref ? await getImage(settings.logo.asset._ref) : null
|
||||
export const load = async ({
|
||||
locals: { preview }
|
||||
}): Promise<{
|
||||
preview: boolean;
|
||||
settings: any;
|
||||
logo: any;
|
||||
navigation: NavigationItem[];
|
||||
}> => {
|
||||
const settings = await fetchSettings();
|
||||
const logo = settings?.logo?.asset?._ref ? await getImage(settings.logo.asset._ref) : null;
|
||||
const navigation = await getCachedNavigation();
|
||||
|
||||
return {
|
||||
preview,
|
||||
settings,
|
||||
logo
|
||||
}
|
||||
}
|
||||
logo,
|
||||
navigation
|
||||
};
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class="scroll-smooth font-sans antialiased">
|
||||
<Navbar settings={data.settings} />
|
||||
<Navbar settings={data.settings} items={data.navigation} />
|
||||
<main class="min-h-[calc(100vh-11.3rem)] md:min-h-[calc(100vh-9.05rem)]">
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
66
template/apps/client/src/routes/[slug]/+page.server.ts
Normal file
66
template/apps/client/src/routes/[slug]/+page.server.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { serverClient } from '$lib/server/sanity';
|
||||
import type { Custom } from '$lib/sanity.types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
const CUSTOM_QUERY = `*[_type == "custom" && slug.current == $slug][0]{
|
||||
_id,
|
||||
_type,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
_rev,
|
||||
title,
|
||||
slug,
|
||||
tags,
|
||||
publishedAt,
|
||||
body,
|
||||
|
||||
}`;
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const { slug } = params;
|
||||
|
||||
if (!slug) {
|
||||
throw error(404, 'Slug not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const custom: Custom = await serverClient.fetch(CUSTOM_QUERY, { slug });
|
||||
|
||||
if (!custom) {
|
||||
throw error(404, 'Page not found');
|
||||
}
|
||||
|
||||
// Extract description from first block
|
||||
let description: string | undefined;
|
||||
if (!description && custom.body && Array.isArray(custom.body)) {
|
||||
const firstBlock = custom.body[0];
|
||||
if (
|
||||
firstBlock &&
|
||||
firstBlock._type === 'block' &&
|
||||
'children' in firstBlock &&
|
||||
Array.isArray(firstBlock.children)
|
||||
) {
|
||||
description = firstBlock.children
|
||||
.filter((child: any) => child.text)
|
||||
.map((child: any) => child.text)
|
||||
.join(' ')
|
||||
.slice(0, 160);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
custom,
|
||||
meta: {
|
||||
title: custom.title,
|
||||
description,
|
||||
url: `/${custom.slug?.current}`,
|
||||
publishedAt: custom.publishedAt,
|
||||
tags: custom.tags
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error fetching custom page:', err);
|
||||
throw error(500, 'Failed to load page');
|
||||
}
|
||||
};
|
||||
42
template/apps/client/src/routes/[slug]/+page.svelte
Normal file
42
template/apps/client/src/routes/[slug]/+page.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import SanityBlock from '$lib/components/sanity-block.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { custom, meta } = data;
|
||||
|
||||
const firstWord = custom.title ? custom.title.split(' ')[0] : '';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{meta.title || custom.title || 'Page'}</title>
|
||||
<meta name="description" content={meta.description || ''} />
|
||||
<meta property="og:title" content={meta.title || custom.title || ''} />
|
||||
<meta property="og:description" content={meta.description || ''} />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content={meta.url || ''} />
|
||||
{#if meta.publishedAt}
|
||||
<meta property="article:published_time" content={meta.publishedAt} />
|
||||
{/if}
|
||||
{#if meta.tags}
|
||||
{#each meta.tags as tag}
|
||||
<meta property="article:tag" content={tag} />
|
||||
{/each}
|
||||
{/if}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={meta.title || custom.title || ''} />
|
||||
<meta name="twitter:description" content={meta.description || ''} />
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto min-h-screen max-w-3xl md:max-w-4xl p-8 flex flex-col gap-4">
|
||||
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold mb-4 sm:mb-8 font-serif">
|
||||
{custom.title}
|
||||
</h1>
|
||||
|
||||
{#if custom.body}
|
||||
<div class="items-start mt-2 mb-8 text-left" style="max-width: 100%;">
|
||||
<SanityBlock body={custom.body} />
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
81
template/apps/client/src/routes/api/blogs/+server.ts
Normal file
81
template/apps/client/src/routes/api/blogs/+server.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { serverClient } from '$lib/server/sanity';
|
||||
import type { Blog } from '$lib/sanity.types';
|
||||
|
||||
const BLOGS_PER_PAGE = 12;
|
||||
|
||||
const BLOGS_QUERY = `*[_type == "blog" && defined(slug.current)] | order(publishedAt desc, _createdAt desc)[$start...$end]{
|
||||
_id,
|
||||
_type,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
title,
|
||||
slug,
|
||||
author,
|
||||
publishedAt,
|
||||
tags,
|
||||
excerpt,
|
||||
mainImage,
|
||||
body[0...2]
|
||||
}`;
|
||||
|
||||
const TOTAL_BLOGS_QUERY = `count(*[_type == "blog" && defined(slug.current)])`;
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
try {
|
||||
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
||||
const start = (page - 1) * BLOGS_PER_PAGE;
|
||||
const end = start + BLOGS_PER_PAGE;
|
||||
|
||||
const [blogs, totalBlogs]: [Blog[], number] = await Promise.all([
|
||||
serverClient.fetch(BLOGS_QUERY, { start, end }),
|
||||
serverClient.fetch(TOTAL_BLOGS_QUERY)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(totalBlogs / BLOGS_PER_PAGE);
|
||||
|
||||
// Process blogs to extract descriptions if excerpt doesn't exist
|
||||
const processedBlogs = blogs.map((blog) => {
|
||||
let description = blog.excerpt;
|
||||
|
||||
if (!description && blog.body && Array.isArray(blog.body)) {
|
||||
const firstBlock = blog.body[0];
|
||||
if (
|
||||
firstBlock &&
|
||||
firstBlock._type === 'block' &&
|
||||
'children' in firstBlock &&
|
||||
Array.isArray(firstBlock.children)
|
||||
) {
|
||||
description = firstBlock.children
|
||||
.filter((child: any) => child.text)
|
||||
.map((child: any) => child.text)
|
||||
.join(' ')
|
||||
.slice(0, 160);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...blog,
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
return json({
|
||||
blogs: processedBlogs,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalBlogs,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPreviousPage: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching blogs via API:', error);
|
||||
return json(
|
||||
{ error: 'Failed to fetch blogs' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
};
|
||||
95
template/apps/client/src/routes/blog/+page.server.ts
Normal file
95
template/apps/client/src/routes/blog/+page.server.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { serverClient } from '$lib/server/sanity';
|
||||
import type { Blog } from '$lib/sanity.types';
|
||||
|
||||
const BLOGS_PER_PAGE = 12;
|
||||
|
||||
const BLOGS_QUERY = `*[_type == "blog" && defined(slug.current)] | order(publishedAt desc, _createdAt desc)[$start...$end]{
|
||||
_id,
|
||||
_type,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
title,
|
||||
slug,
|
||||
author,
|
||||
publishedAt,
|
||||
tags,
|
||||
excerpt,
|
||||
mainImage,
|
||||
body[0...2]
|
||||
}`;
|
||||
|
||||
const TOTAL_BLOGS_QUERY = `count(*[_type == "blog" && defined(slug.current)])`;
|
||||
|
||||
export const load: PageServerLoad = async ({ url, depends }) => {
|
||||
depends('blog:pagination', url.searchParams.toString());
|
||||
try {
|
||||
const page = parseInt(url.searchParams.get('page') || '1', 10);
|
||||
const start = (page - 1) * BLOGS_PER_PAGE;
|
||||
const end = start + BLOGS_PER_PAGE;
|
||||
|
||||
const [blogs, totalBlogs]: [Blog[], number] = await Promise.all([
|
||||
serverClient.fetch(BLOGS_QUERY, { start, end }),
|
||||
serverClient.fetch(TOTAL_BLOGS_QUERY)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(totalBlogs / BLOGS_PER_PAGE);
|
||||
|
||||
// Process blogs to extract descriptions if excerpt doesn't exist
|
||||
const processedBlogs = blogs.map((blog) => {
|
||||
let description = blog.excerpt;
|
||||
|
||||
if (!description && blog.body && Array.isArray(blog.body)) {
|
||||
const firstBlock = blog.body[0];
|
||||
if (
|
||||
firstBlock &&
|
||||
firstBlock._type === 'block' &&
|
||||
'children' in firstBlock &&
|
||||
Array.isArray(firstBlock.children)
|
||||
) {
|
||||
description = firstBlock.children
|
||||
.filter((child: any) => child.text)
|
||||
.map((child: any) => child.text)
|
||||
.join(' ')
|
||||
.slice(0, 160);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...blog,
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
blogs: processedBlogs,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalBlogs,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPreviousPage: page > 1
|
||||
},
|
||||
meta: {
|
||||
title: 'Blog',
|
||||
description: 'Read our latest blog posts and articles.'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching blogs:', error);
|
||||
return {
|
||||
blogs: [],
|
||||
pagination: {
|
||||
currentPage: 1,
|
||||
totalPages: 0,
|
||||
totalBlogs: 0,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false
|
||||
},
|
||||
meta: {
|
||||
title: 'Blog',
|
||||
description: 'Read our latest blog posts and articles.'
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
248
template/apps/client/src/routes/blog/+page.svelte
Normal file
248
template/apps/client/src/routes/blog/+page.svelte
Normal file
@@ -0,0 +1,248 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import CoverImage from '$lib/components/cover-image.svelte';
|
||||
import { formatDate } from '$lib/utils';
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { blogs, pagination, meta } = data;
|
||||
|
||||
function createPageUrl(pageNum: number): string {
|
||||
const url = new URL($page.url);
|
||||
if (pageNum === 1) {
|
||||
url.searchParams.delete('page');
|
||||
} else {
|
||||
url.searchParams.set('page', pageNum.toString());
|
||||
}
|
||||
return url.pathname + url.search;
|
||||
}
|
||||
|
||||
function getVisiblePages(): (number | 'ellipsis')[] {
|
||||
const { currentPage, totalPages } = pagination;
|
||||
const pages: (number | 'ellipsis')[] = [];
|
||||
|
||||
if (totalPages <= 7) {
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push(1);
|
||||
|
||||
if (currentPage <= 4) {
|
||||
for (let i = 2; i <= 5; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push('ellipsis');
|
||||
pages.push(totalPages);
|
||||
} else if (currentPage >= totalPages - 3) {
|
||||
pages.push('ellipsis');
|
||||
for (let i = totalPages - 4; i <= totalPages; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
pages.push('ellipsis');
|
||||
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push('ellipsis');
|
||||
pages.push(totalPages);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
function navigateToPost(slug: string | undefined) {
|
||||
if (slug) {
|
||||
goto(`/blog/${slug}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{meta.title}</title>
|
||||
<meta name="description" content={meta.description} />
|
||||
<meta property="og:title" content={meta.title} />
|
||||
<meta property="og:description" content={meta.description} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="/blog" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={meta.title} />
|
||||
<meta name="twitter:description" content={meta.description} />
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto min-h-screen max-w-6xl p-8">
|
||||
<header class="mb-12">
|
||||
<h1 class="mb-4 font-serif text-3xl font-bold sm:text-4xl md:text-5xl">Blog</h1>
|
||||
<p class="text-lg text-muted-foreground">{meta.description}</p>
|
||||
{#if pagination.totalBlogs > 0}
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
Showing {blogs.length} of {pagination.totalBlogs} posts
|
||||
</p>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
{#if blogs.length === 0}
|
||||
<div class="py-16 text-center">
|
||||
<p class="text-lg text-muted-foreground">No blog posts found.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each blogs as blog}
|
||||
{@const publishedDate = blog.publishedAt
|
||||
? new Date(blog.publishedAt)
|
||||
: new Date(blog._createdAt)}
|
||||
{@const formattedDate = formatDate(publishedDate)}
|
||||
{@const extendedBlog = blog as any}
|
||||
|
||||
<Card
|
||||
class="group cursor-pointer overflow-hidden transition-all focus-within:ring-2 focus-within:ring-primary/20 hover:shadow-md"
|
||||
onclick={() => navigateToPost(blog.slug?.current)}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
navigateToPost(blog.slug?.current);
|
||||
}
|
||||
}}
|
||||
tabindex={0}
|
||||
role="button"
|
||||
aria-label="Read blog post: {blog.title}"
|
||||
>
|
||||
{#if blog.mainImage?.asset}
|
||||
<div
|
||||
class="aspect-video overflow-hidden [&>img]:transition-transform [&>img]:duration-300 group-hover:[&>img]:scale-105"
|
||||
>
|
||||
<CoverImage
|
||||
image={blog.mainImage}
|
||||
alt={blog.mainImage.alt || blog.title || 'Blog cover image'}
|
||||
width={800}
|
||||
height={450}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex aspect-video items-center justify-center bg-muted">
|
||||
<div class="text-center">
|
||||
<div class="mb-2 text-4xl text-muted-foreground">📝</div>
|
||||
<p class="text-sm text-muted-foreground">No cover image</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle
|
||||
class="font-serif text-xl leading-tight transition-colors group-hover:text-primary"
|
||||
>
|
||||
{blog.title}
|
||||
</CardTitle>
|
||||
|
||||
<div class="flex flex-col gap-1 text-sm text-muted-foreground">
|
||||
{#if blog.author}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">By {blog.author}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{formattedDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if blog.tags && blog.tags.length > 0}
|
||||
<div class="mt-3 flex flex-wrap gap-1">
|
||||
{#each blog.tags.slice(0, 3) as tag}
|
||||
<span
|
||||
class="inline-block rounded bg-muted px-2 py-1 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
{/each}
|
||||
{#if blog.tags.length > 3}
|
||||
<span class="text-xs text-muted-foreground">+{blog.tags.length - 3} more</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="pt-0">
|
||||
{#if blog.excerpt || extendedBlog.description}
|
||||
<p class="mb-4 line-clamp-3 text-sm leading-relaxed text-muted-foreground">
|
||||
{blog.excerpt || extendedBlog.description}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="inline-flex items-center text-sm font-medium text-primary transition-colors group-hover:text-primary/80"
|
||||
>
|
||||
Read more
|
||||
<svg class="ml-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if pagination.totalPages > 1}
|
||||
<nav class="mt-12 flex justify-center" aria-label="Pagination">
|
||||
<div class="flex items-center gap-1">
|
||||
{#if pagination.hasPreviousPage}
|
||||
<a href={createPageUrl(pagination.currentPage - 1)} data-sveltekit-reload>
|
||||
<Button variant="outline" size="sm">
|
||||
<svg class="mr-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 19l-7-7 7-7"
|
||||
></path>
|
||||
</svg>
|
||||
Previous
|
||||
</Button>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#each getVisiblePages() as pageItem}
|
||||
{#if pageItem === 'ellipsis'}
|
||||
<span class="px-3 py-2 text-muted-foreground">...</span>
|
||||
{:else if pageItem === pagination.currentPage}
|
||||
<Button variant="default" size="sm" disabled>
|
||||
{pageItem}
|
||||
</Button>
|
||||
{:else}
|
||||
<a href={createPageUrl(pageItem)} data-sveltekit-reload>
|
||||
<Button variant="ghost" size="sm">
|
||||
{pageItem}
|
||||
</Button>
|
||||
</a>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if pagination.hasNextPage}
|
||||
<a href={createPageUrl(pagination.currentPage + 1)} data-sveltekit-reload>
|
||||
<Button variant="outline" size="sm">
|
||||
Next
|
||||
<svg class="ml-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 5l7 7-7 7"
|
||||
></path>
|
||||
</svg>
|
||||
</Button>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
||||
{/if}
|
||||
{/if}
|
||||
</main>
|
||||
3
template/apps/client/src/routes/blog/+page.ts
Normal file
3
template/apps/client/src/routes/blog/+page.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const ssr = true;
|
||||
export const csr = true;
|
||||
export const prerender = false;
|
||||
69
template/apps/client/src/routes/blog/[slug]/+page.server.ts
Normal file
69
template/apps/client/src/routes/blog/[slug]/+page.server.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { serverClient } from '$lib/server/sanity';
|
||||
import type { Blog } from '$lib/sanity.types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
const BLOG_QUERY = `*[_type == "blog" && slug.current == $slug][0]{
|
||||
_id,
|
||||
_type,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
_rev,
|
||||
title,
|
||||
slug,
|
||||
author,
|
||||
publishedAt,
|
||||
tags,
|
||||
body,
|
||||
excerpt,
|
||||
mainImage
|
||||
}`;
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const { slug } = params;
|
||||
|
||||
if (!slug) {
|
||||
throw error(404, 'Slug not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const blog: Blog = await serverClient.fetch(BLOG_QUERY, { slug });
|
||||
|
||||
if (!blog) {
|
||||
throw error(404, 'Blog post not found');
|
||||
}
|
||||
|
||||
// Extract description from first block if no excerpt exists
|
||||
let description = blog.excerpt;
|
||||
if (!description && blog.body && Array.isArray(blog.body)) {
|
||||
const firstBlock = blog.body[0];
|
||||
if (
|
||||
firstBlock &&
|
||||
firstBlock._type === 'block' &&
|
||||
'children' in firstBlock &&
|
||||
Array.isArray(firstBlock.children)
|
||||
) {
|
||||
description = firstBlock.children
|
||||
.filter((child: any) => child.text)
|
||||
.map((child: any) => child.text)
|
||||
.join(' ')
|
||||
.slice(0, 160);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blog,
|
||||
meta: {
|
||||
title: blog.title,
|
||||
description,
|
||||
url: `/blog/${blog.slug?.current}`,
|
||||
publishedAt: blog.publishedAt,
|
||||
author: blog.author,
|
||||
tags: blog.tags
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error fetching blog post:', err);
|
||||
throw error(500, 'Failed to load blog post');
|
||||
}
|
||||
};
|
||||
86
template/apps/client/src/routes/blog/[slug]/+page.svelte
Normal file
86
template/apps/client/src/routes/blog/[slug]/+page.svelte
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import SanityBlock from '$lib/components/sanity-block.svelte';
|
||||
import CoverImage from '$lib/components/cover-image.svelte';
|
||||
import { formatDate } from '$lib/utils';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const { blog, meta } = data;
|
||||
|
||||
const publishedDate = blog.publishedAt ? new Date(blog.publishedAt) : new Date(blog._createdAt);
|
||||
const formattedDate = formatDate ? formatDate(publishedDate) : publishedDate.toLocaleDateString();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{meta.title || blog.title || 'Blog Post'}</title>
|
||||
<meta name="description" content={meta.description || ''} />
|
||||
<meta property="og:title" content={meta.title || blog.title || ''} />
|
||||
<meta property="og:description" content={meta.description || ''} />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content={meta.url || ''} />
|
||||
{#if meta.publishedAt}
|
||||
<meta property="article:published_time" content={meta.publishedAt} />
|
||||
{/if}
|
||||
{#if meta.author}
|
||||
<meta property="article:author" content={meta.author} />
|
||||
{/if}
|
||||
{#if meta.tags}
|
||||
{#each meta.tags as tag}
|
||||
<meta property="article:tag" content={tag} />
|
||||
{/each}
|
||||
{/if}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={meta.title || blog.title || ''} />
|
||||
<meta name="twitter:description" content={meta.description || ''} />
|
||||
</svelte:head>
|
||||
|
||||
<main class="container mx-auto flex min-h-screen max-w-3xl flex-col gap-4 p-8 md:max-w-4xl">
|
||||
<article>
|
||||
{#if blog.mainImage?.asset}
|
||||
<div class="-mx-8 mb-8 overflow-hidden md:mx-0 md:rounded-lg">
|
||||
<div class="h-64 w-full md:h-96">
|
||||
<CoverImage
|
||||
image={blog.mainImage}
|
||||
alt={blog.mainImage.alt || blog.title || 'Blog cover image'}
|
||||
width={1200}
|
||||
height={600}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<header class="mb-8">
|
||||
<h1 class="mb-4 font-serif text-2xl font-bold sm:text-3xl md:text-4xl">
|
||||
{blog.title}
|
||||
</h1>
|
||||
|
||||
<div
|
||||
class="mb-6 flex flex-col gap-2 text-sm text-muted-foreground sm:flex-row sm:items-center sm:gap-4"
|
||||
>
|
||||
{#if blog.author}
|
||||
<span>By {blog.author}</span>
|
||||
{/if}
|
||||
<span>{formattedDate}</span>
|
||||
</div>
|
||||
|
||||
{#if blog.tags && blog.tags.length > 0}
|
||||
<div class="mb-6 flex flex-wrap gap-2">
|
||||
{#each blog.tags as tag}
|
||||
<span
|
||||
class="inline-block rounded-full bg-muted px-3 py-1 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
{#if blog.body}
|
||||
<div class="prose prose-lg max-w-none">
|
||||
<SanityBlock body={blog.body} />
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
</main>
|
||||
3
template/apps/client/src/routes/blog/[slug]/+page.ts
Normal file
3
template/apps/client/src/routes/blog/[slug]/+page.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const ssr = true;
|
||||
export const csr = true;
|
||||
export const prerender = false;
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"generates": "../client-svelte/src/lib/sanity.types.ts",
|
||||
"generates": "../client/src/lib/sanity.types.ts",
|
||||
"path": "./schemaTypes/*.ts"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import {defineConfig} from 'sanity'
|
||||
import {structureTool} from 'sanity/structure'
|
||||
import {ClipboardIcon, HomeIcon, MenuIcon, WrenchIcon} from '@sanity/icons'
|
||||
import {ClipboardIcon, EditIcon, HomeIcon, MenuIcon, WrenchIcon} from '@sanity/icons'
|
||||
import {schemaTypes} from './schemaTypes'
|
||||
import {presentationTool} from 'sanity/presentation'
|
||||
import {linkField} from 'sanity-plugin-link-field'
|
||||
@@ -41,11 +41,36 @@ export default defineConfig({
|
||||
.title('Custom pages')
|
||||
.icon(ClipboardIcon)
|
||||
.child(S.documentTypeList('custom').title('Content')),
|
||||
S.listItem()
|
||||
.title('Blog')
|
||||
.icon(EditIcon)
|
||||
.child(S.documentTypeList('blog').title('Blog')),
|
||||
]),
|
||||
}),
|
||||
// @ts-ignore
|
||||
linkField({
|
||||
linkableSchemaTypes: ['custom'],
|
||||
linkableSchemaTypes: ['custom', 'blog'],
|
||||
customLinkTypes: [
|
||||
{
|
||||
title: 'Static pages',
|
||||
value: 'static',
|
||||
icon: ClipboardIcon,
|
||||
options: [
|
||||
{
|
||||
title: 'Home',
|
||||
value: '/',
|
||||
},
|
||||
{
|
||||
title: 'Second page',
|
||||
value: '/second',
|
||||
},
|
||||
{
|
||||
title: 'Blog',
|
||||
value: '/blog',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
presentationTool({
|
||||
previewUrl: {
|
||||
|
||||
@@ -41,6 +41,13 @@
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"publishedAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"headerSection": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
@@ -51,6 +58,524 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "blog",
|
||||
"type": "document",
|
||||
"attributes": {
|
||||
"_id": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "blog"
|
||||
}
|
||||
},
|
||||
"_createdAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_updatedAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_rev": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "slug"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"author": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"publishedAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"tags": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"excerpt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"mainImage": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"asset": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_ref": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "reference"
|
||||
}
|
||||
},
|
||||
"_weak": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dereferencesTo": "sanity.imageAsset"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"media": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "unknown"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"hotspot": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "sanity.imageHotspot"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"crop": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "sanity.imageCrop"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"alt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "imageWithAlt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"body": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"children": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"marks": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"text": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "span"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"style": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "string",
|
||||
"value": "normal"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "h1"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "h2"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "h3"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "h4"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "blockquote"
|
||||
}
|
||||
]
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"listItem": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "string",
|
||||
"value": "bullet"
|
||||
}
|
||||
]
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"markDefs": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"href": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "link"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "link"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "inline",
|
||||
"name": "textColor"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "inline",
|
||||
"name": "highlightColor"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"level": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "number"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "block"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "inline",
|
||||
"name": "button"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"asset": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_ref": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "reference"
|
||||
}
|
||||
},
|
||||
"_weak": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dereferencesTo": "sanity.imageAsset"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"media": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "unknown"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"hotspot": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "sanity.imageHotspot"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"crop": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "sanity.imageCrop"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "image"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"asset": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_ref": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "reference"
|
||||
}
|
||||
},
|
||||
"_weak": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dereferencesTo": "sanity.fileAsset"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"media": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "unknown"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "file"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "faqSection",
|
||||
"type": "type",
|
||||
@@ -1151,6 +1676,270 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "navbar",
|
||||
"type": "document",
|
||||
"attributes": {
|
||||
"_id": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "navbar"
|
||||
}
|
||||
},
|
||||
"_createdAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_updatedAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_rev": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"links": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"text": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"link": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "link"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"sublinks": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "string",
|
||||
"value": "auto"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "tag"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "manual"
|
||||
}
|
||||
]
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"pageType": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "string",
|
||||
"value": "custom"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "blog"
|
||||
}
|
||||
]
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"text": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"link": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "link"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"tagFilter": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_ref": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "reference"
|
||||
}
|
||||
},
|
||||
"_weak": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dereferencesTo": "tag"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"tagPageType": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "union",
|
||||
"of": [
|
||||
{
|
||||
"type": "string",
|
||||
"value": "custom"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"value": "blog"
|
||||
}
|
||||
]
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"rest": {
|
||||
"type": "object",
|
||||
"attributes": {
|
||||
"_key": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tag",
|
||||
"type": "document",
|
||||
"attributes": {
|
||||
"_id": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_type": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string",
|
||||
"value": "tag"
|
||||
}
|
||||
},
|
||||
"_createdAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_updatedAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"_rev": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"slug": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "inline",
|
||||
"name": "slug"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"description": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "settings",
|
||||
"type": "document",
|
||||
@@ -2318,6 +3107,23 @@
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"tags": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "array",
|
||||
"of": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"publishedAt": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"optional": true
|
||||
},
|
||||
"body": {
|
||||
"type": "objectAttribute",
|
||||
"value": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LinkIcon, MenuIcon } from "@sanity/icons"
|
||||
import { defineField, defineType, type Rule, type StringRule } from "sanity"
|
||||
import { requiredLinkField } from "sanity-plugin-link-field"
|
||||
import {LinkIcon, MenuIcon} from '@sanity/icons'
|
||||
import {defineField, defineType, type Rule, type StringRule} from 'sanity'
|
||||
import {requiredLinkField} from 'sanity-plugin-link-field'
|
||||
|
||||
export default defineType({
|
||||
name: 'navbar',
|
||||
@@ -27,14 +27,14 @@ export default defineType({
|
||||
link: {
|
||||
type: 'url',
|
||||
value: '/',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Second Page',
|
||||
link: {
|
||||
type: 'url',
|
||||
value: '/second',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Blog',
|
||||
@@ -46,10 +46,10 @@ export default defineType({
|
||||
{
|
||||
type: 'auto',
|
||||
pageType: 'blog',
|
||||
autoTitle: 'Latest Blog Posts'
|
||||
}
|
||||
]
|
||||
}
|
||||
autoTitle: 'Latest Blog Posts',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
of: [
|
||||
{
|
||||
@@ -67,7 +67,7 @@ export default defineType({
|
||||
name: 'link',
|
||||
title: 'Link',
|
||||
type: 'link',
|
||||
validation: (rule: Rule) => rule.custom((field: Rule) => requiredLinkField(field))
|
||||
validation: (rule: Rule) => rule.custom((field: Rule) => requiredLinkField(field)),
|
||||
}),
|
||||
defineField({
|
||||
name: 'sublinks',
|
||||
@@ -89,15 +89,15 @@ export default defineType({
|
||||
list: [
|
||||
{
|
||||
title: 'Last Pages',
|
||||
value: 'auto'
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
title: 'Pages by Tag',
|
||||
value: 'tag'
|
||||
value: 'tag',
|
||||
},
|
||||
{
|
||||
title: 'Manual Link',
|
||||
value: 'manual'
|
||||
value: 'manual',
|
||||
},
|
||||
],
|
||||
layout: 'radio',
|
||||
@@ -109,17 +109,18 @@ export default defineType({
|
||||
title: 'Page Type',
|
||||
type: 'string',
|
||||
initialValue: 'custom',
|
||||
description: 'Automatically displays the 5 most recently published pages from the selected type',
|
||||
description:
|
||||
'Automatically displays the 5 most recently published pages from the selected type',
|
||||
options: {
|
||||
list: [
|
||||
{ title: 'Custom Pages', value: 'custom' },
|
||||
{ title: 'Blog Posts', value: 'blog' },
|
||||
{title: 'Custom Pages', value: 'custom'},
|
||||
{title: 'Blog Posts', value: 'blog'},
|
||||
],
|
||||
},
|
||||
hidden: ({ parent }) => parent?.type !== 'auto',
|
||||
hidden: ({parent}) => parent?.type !== 'auto',
|
||||
validation: (Rule: StringRule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
const parent = context.parent as {type?: string}
|
||||
if (parent?.type === 'auto' && !value) {
|
||||
return 'A page type must be selected'
|
||||
}
|
||||
@@ -130,10 +131,10 @@ export default defineType({
|
||||
name: 'text',
|
||||
title: 'Text',
|
||||
type: 'string',
|
||||
hidden: ({ parent }) => parent?.type !== 'manual',
|
||||
hidden: ({parent}) => parent?.type !== 'manual',
|
||||
validation: (Rule: StringRule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
const parent = context.parent as {type?: string}
|
||||
if (parent?.type === 'manual' && !value) {
|
||||
return 'Text is required for manual links'
|
||||
}
|
||||
@@ -144,10 +145,10 @@ export default defineType({
|
||||
name: 'link',
|
||||
title: 'Link',
|
||||
type: 'link',
|
||||
hidden: ({ parent }) => parent?.type !== 'manual',
|
||||
hidden: ({parent}) => parent?.type !== 'manual',
|
||||
validation: (rule: Rule) =>
|
||||
rule.custom((field, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
const parent = context.parent as {type?: string}
|
||||
if (parent?.type === 'manual') {
|
||||
return requiredLinkField(field)
|
||||
}
|
||||
@@ -158,13 +159,14 @@ export default defineType({
|
||||
name: 'tagFilter',
|
||||
title: 'Tag Filter',
|
||||
type: 'reference',
|
||||
to: [{ type: 'tag' }],
|
||||
description: 'Select a tag to filter pages by. The last 5 published pages with this tag will be shown.',
|
||||
hidden: ({ parent }) => parent?.type !== 'tag',
|
||||
to: [{type: 'tag'}],
|
||||
description:
|
||||
'Select a tag to filter pages by. The last 5 published pages with this tag will be shown.',
|
||||
hidden: ({parent}) => parent?.type !== 'tag',
|
||||
validation: (Rule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
if (parent?.type === 'tag' && (!value)) {
|
||||
const parent = context.parent as {type?: string}
|
||||
if (parent?.type === 'tag' && !value) {
|
||||
return 'A tag is required when using tag-based filtering'
|
||||
}
|
||||
return true
|
||||
@@ -177,15 +179,15 @@ export default defineType({
|
||||
description: 'Select which type of pages to search for the tag',
|
||||
options: {
|
||||
list: [
|
||||
{ title: 'Custom Pages', value: 'custom' },
|
||||
{ title: 'Blog Posts', value: 'blog' },
|
||||
{title: 'Custom Pages', value: 'custom'},
|
||||
{title: 'Blog Posts', value: 'blog'},
|
||||
],
|
||||
},
|
||||
hidden: ({ parent }) => parent?.type !== 'tag',
|
||||
hidden: ({parent}) => parent?.type !== 'tag',
|
||||
initialValue: 'custom',
|
||||
validation: (Rule: StringRule) =>
|
||||
Rule.custom((value, context) => {
|
||||
const parent = context.parent as { type?: string }
|
||||
const parent = context.parent as {type?: string}
|
||||
if (parent?.type === 'tag' && !value) {
|
||||
return 'A page type must be selected for tag filtering'
|
||||
}
|
||||
@@ -202,7 +204,7 @@ export default defineType({
|
||||
pageType: 'pageType',
|
||||
tagPageType: 'tagPageType',
|
||||
},
|
||||
prepare({ title, type, tagTitles, autoTitle, pageType, tagPageType }) {
|
||||
prepare({title, type, tagTitles, autoTitle, pageType, tagPageType}) {
|
||||
let displayTitle = title
|
||||
let subtitle = ''
|
||||
|
||||
@@ -210,9 +212,11 @@ export default defineType({
|
||||
const typeMap: Record<string, string> = {
|
||||
home: 'Home',
|
||||
custom: 'Custom',
|
||||
blog: 'Blog'
|
||||
blog: 'Blog',
|
||||
}
|
||||
return typeMap[pageType] || pageType.charAt(0).toUpperCase() + pageType.slice(1)
|
||||
return (
|
||||
typeMap[pageType] || pageType.charAt(0).toUpperCase() + pageType.slice(1)
|
||||
)
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DocumentIcon } from '@sanity/icons'
|
||||
import { defineField, defineType, type SlugRule, type StringRule } from 'sanity'
|
||||
import {DocumentIcon} from '@sanity/icons'
|
||||
import {defineField, defineType, type SlugRule, type StringRule} from 'sanity'
|
||||
|
||||
export default defineType({
|
||||
name: 'blog',
|
||||
@@ -11,7 +11,7 @@ export default defineType({
|
||||
name: 'title',
|
||||
title: 'Title',
|
||||
type: 'string',
|
||||
validation: (Rule: StringRule) => Rule.required().error('Title is required')
|
||||
validation: (Rule: StringRule) => Rule.required().error('Title is required'),
|
||||
}),
|
||||
defineField({
|
||||
name: 'slug',
|
||||
@@ -34,14 +34,10 @@ export default defineType({
|
||||
type: 'datetime',
|
||||
}),
|
||||
defineField({
|
||||
name: 'tags',
|
||||
title: 'Tags',
|
||||
type: 'array',
|
||||
of: [{ type: 'string' }],
|
||||
description: 'Add tags to categorize this post. Tags can be used to filter and group related content in navigation menus.',
|
||||
options: {
|
||||
layout: 'tags',
|
||||
},
|
||||
name: 'tagFilter',
|
||||
title: 'Tag Filter',
|
||||
type: 'reference',
|
||||
to: [{type: 'tag'}],
|
||||
}),
|
||||
defineField({
|
||||
name: 'excerpt',
|
||||
@@ -67,7 +63,7 @@ export default defineType({
|
||||
author: 'author',
|
||||
media: 'mainImage',
|
||||
},
|
||||
prepare({ title, author, media }) {
|
||||
prepare({title, author, media}) {
|
||||
return {
|
||||
title: title || 'Untitled',
|
||||
subtitle: author && `by ${author}`,
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
"@repo/sanity-connection": "workspace:*",
|
||||
"@repo/ui": "workspace:*",
|
||||
"@sanity/document-internationalization": "^4.0.0",
|
||||
"@sanity/vision": "4.5.0",
|
||||
"@sanity/vision": "^4.5.0",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"sanity": "4.5.0",
|
||||
"sanity": "^4.5.0",
|
||||
"sanity-plugin-link-field": "^1.4.0",
|
||||
"sanity-plugin-seo": "^1.3.3",
|
||||
"sanity-plugin-simpler-color-input": "^3.1.1",
|
||||
@@ -420,57 +420,57 @@
|
||||
|
||||
"@emotion/unitless": ["@emotion/unitless@0.8.1", "", {}, "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="],
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="],
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="],
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="],
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="],
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="],
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="],
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="],
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="],
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="],
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="],
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="],
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="],
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="],
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="],
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="],
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="],
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="],
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="],
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="],
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="],
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="],
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="],
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="],
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="],
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="],
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
|
||||
|
||||
@@ -1472,7 +1472,7 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="],
|
||||
"esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||
|
||||
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||
|
||||
@@ -2654,7 +2654,7 @@
|
||||
|
||||
"validate-npm-package-name": ["validate-npm-package-name@3.0.0", "", { "dependencies": { "builtins": "^1.0.3" } }, "sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw=="],
|
||||
|
||||
"vite": ["vite@7.0.6", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "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-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg=="],
|
||||
"vite": ["vite@7.1.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "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-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw=="],
|
||||
|
||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
|
||||
|
||||
@@ -2846,8 +2846,6 @@
|
||||
|
||||
"@portabletext/editor/xstate": ["xstate@5.20.2", "", {}, "sha512-GZmLmc+WPKfFRxuTDAxCg0cUhS/ZnWaRD86DO8MKizeK4a050jd5k7UNnIQ2jJDWRig2/r0tmVXeezUNIhoz5Q=="],
|
||||
|
||||
"@sanity/cli/esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||
|
||||
"@sanity/codegen/@babel/core": ["@babel/core@7.28.3", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.3", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.3", "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ=="],
|
||||
|
||||
"@sanity/codegen/groq": ["groq@4.5.0", "", {}, "sha512-gAvcn4Y6KUwH/DVS59vIBw5kxzFlV8KDaKqa0M+BOa6EdzuoTeXSpGIH1wmKT+Yv6w3XqPVw5ArKQWsG810hog=="],
|
||||
@@ -2884,8 +2882,6 @@
|
||||
|
||||
"@sanity/runtime-cli/tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite": ["vite@7.1.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "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-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw=="],
|
||||
|
||||
"@sanity/runtime-cli/xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
"@sanity/sdk/@sanity/diff-patch": ["@sanity/diff-patch@6.0.0", "", { "dependencies": { "@sanity/diff-match-patch": "^3.2.0" } }, "sha512-oJ5kZQV6C/DAlcpRLEU7AcVWXrSPuJb3Z1TQ9tm/qZOVWJENwWln45jtepQEYolTIuGx9jUlhYUi3hGIkOt8RA=="],
|
||||
@@ -3110,12 +3106,8 @@
|
||||
|
||||
"sanity/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"sanity/esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||
|
||||
"sanity/tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
|
||||
|
||||
"sanity/vite": ["vite@7.1.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "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-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw=="],
|
||||
|
||||
"sanity/xstate": ["xstate@5.20.2", "", {}, "sha512-GZmLmc+WPKfFRxuTDAxCg0cUhS/ZnWaRD86DO8MKizeK4a050jd5k7UNnIQ2jJDWRig2/r0tmVXeezUNIhoz5Q=="],
|
||||
|
||||
"sanity-plugin-internationalized-array/@sanity/ui": ["@sanity/ui@2.16.12", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.5", "@juggle/resize-observer": "^3.4.0", "@sanity/color": "^3.0.6", "@sanity/icons": "^3.7.4", "csstype": "^3.1.3", "framer-motion": "^12.23.12", "react-compiler-runtime": "19.1.0-rc.2", "react-refractor": "^2.2.0", "use-effect-event": "^2.0.3" }, "peerDependencies": { "react": "^18 || >=19.0.0-0", "react-dom": "^18 || >=19.0.0-0", "react-is": "^18 || >=19.0.0-0", "styled-components": "^5.2 || ^6" } }, "sha512-aAlsoYPM2MyvhsUKCvYvQ65oFFQH4KktB4crN0JL81qu915XKSYoXF/E2rge8EJCjaml18X3zFJLmwuP+XaCsw=="],
|
||||
@@ -3144,6 +3136,8 @@
|
||||
|
||||
"tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
|
||||
|
||||
"vite/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
|
||||
|
||||
"yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
|
||||
@@ -3230,58 +3224,6 @@
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"@sanity/cli/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"@sanity/codegen/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||
|
||||
"@sanity/codegen/@babel/core/@babel/helpers": ["@babel/helpers@7.28.3", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.2" } }, "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw=="],
|
||||
@@ -3328,10 +3270,6 @@
|
||||
|
||||
"@sanity/runtime-cli/ora/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"@sanity/sdk/@sanity/message-protocol/@sanity/comlink": ["@sanity/comlink@2.0.5", "", { "dependencies": { "rxjs": "^7.8.1", "uuid": "^11.0.4", "xstate": "^5.19.1" } }, "sha512-6Rbg71hkeoGInk/9hBsCUBCZ33IHSs2fZynAR85ANkXDM+WYiwRDlker7OngBkfbK8TF9+G797VjNMQQgJINiQ=="],
|
||||
|
||||
"@sanity/sdk/@sanity/mutate/@sanity/client": ["@sanity/client@6.29.1", "", { "dependencies": { "@sanity/eventsource": "^5.0.2", "get-it": "^8.6.7", "rxjs": "^7.0.0" } }, "sha512-BQRCMeDlBxwnMbFtB61HUxFf9aSb4HNVrpfrC7IFVqFf4cwcc3o5H8/nlrL9U3cDFedbe4W0AXt1mQzwbY/ljw=="],
|
||||
@@ -3542,6 +3480,8 @@
|
||||
|
||||
"sanity-plugin-link-field/sanity/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/rollup": ["rollup@4.45.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.45.3", "@rollup/rollup-android-arm64": "4.45.3", "@rollup/rollup-darwin-arm64": "4.45.3", "@rollup/rollup-darwin-x64": "4.45.3", "@rollup/rollup-freebsd-arm64": "4.45.3", "@rollup/rollup-freebsd-x64": "4.45.3", "@rollup/rollup-linux-arm-gnueabihf": "4.45.3", "@rollup/rollup-linux-arm-musleabihf": "4.45.3", "@rollup/rollup-linux-arm64-gnu": "4.45.3", "@rollup/rollup-linux-arm64-musl": "4.45.3", "@rollup/rollup-linux-loongarch64-gnu": "4.45.3", "@rollup/rollup-linux-ppc64-gnu": "4.45.3", "@rollup/rollup-linux-riscv64-gnu": "4.45.3", "@rollup/rollup-linux-riscv64-musl": "4.45.3", "@rollup/rollup-linux-s390x-gnu": "4.45.3", "@rollup/rollup-linux-x64-gnu": "4.45.3", "@rollup/rollup-linux-x64-musl": "4.45.3", "@rollup/rollup-win32-arm64-msvc": "4.45.3", "@rollup/rollup-win32-ia32-msvc": "4.45.3", "@rollup/rollup-win32-x64-msvc": "4.45.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA=="],
|
||||
@@ -3560,60 +3500,6 @@
|
||||
|
||||
"sanity/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"sanity/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"sanity/vite/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
||||
|
||||
"wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
|
||||
@@ -3694,58 +3580,6 @@
|
||||
|
||||
"@sanity/runtime-cli/ora/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="],
|
||||
|
||||
"@sanity/runtime-cli/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="],
|
||||
|
||||
"decompress-tar/tar-stream/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||
|
||||
"decompress-tar/tar-stream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
@@ -3820,6 +3654,58 @@
|
||||
|
||||
"sanity-plugin-link-field/sanity/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/rollup/@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.45.3", "", { "os": "android", "cpu": "arm" }, "sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA=="],
|
||||
|
||||
"sanity-plugin-link-field/sanity/rollup/@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.45.3", "", { "os": "android", "cpu": "arm64" }, "sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ=="],
|
||||
|
||||
Reference in New Issue
Block a user