Compare commits

...

7 commits

Author SHA1 Message Date
74d6bfbe77 remove embed from cat integration
Some checks are pending
/ build (push) Waiting to run
2025-04-06 11:45:17 -06:00
54775f79c5 Remove console log 2025-04-06 11:39:12 -06:00
32a86b7a72 Improve embeds 2025-04-06 11:33:03 -06:00
e03508f57c Fix album edit initial query 2025-04-06 10:54:09 -06:00
6cef84a358 Album edit endpoint 2025-04-06 10:30:19 -06:00
d3581eaeef Drop custom url column 2025-04-06 10:30:19 -06:00
13a7f99a5d Remove unused small download album column 2025-04-06 10:30:19 -06:00
34 changed files with 1461 additions and 133 deletions

View file

@ -20,20 +20,24 @@
"@types/react-dom": "^18.3.1",
"astro": "^5.3.0",
"astro-icon": "^1.1.1",
"astro-seo": "^0.8.4",
"better-auth": "^1.1.11",
"axios": "^1.8.1",
"clsx": "^2.1.1",
"decode-formdata": "^0.9.0",
"immer": "^10.1.1",
"nodemailer": "^6.10.0",
"prisma": "^6.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
"react-multi-select-component": "^4.3.4",
"react-svg-spinners": "^0.3.1",
"sharp": "^0.33.5",
"slugify": "^1.6.6",
"superstruct": "^2.0.2",
"tailwindcss": "^4.0.7",
"typescript": "^5.6.2"
"typescript": "^5.6.2",
"use-immer": "^0.11.0"
},
"devDependencies": {
"@inlang/paraglide-js": "1.11.2",

View file

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `small` on the `downloads` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE `downloads` DROP COLUMN `small`;

View file

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `custom` on the `links` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE `links` DROP COLUMN `custom`;

View file

@ -219,11 +219,10 @@ model discs {
}
model downloads {
id Int @id @default(autoincrement())
title String? @db.VarChar(255)
small Boolean?
id Int @id @default(autoincrement())
title String? @db.VarChar(255)
albumId Int
album albums? @relation(fields: [albumId], references: [id], onDelete: Cascade, map: "downloads_ibfk_1")
album albums? @relation(fields: [albumId], references: [id], onDelete: Cascade, map: "downloads_ibfk_1")
links links[]
}
@ -276,7 +275,6 @@ model links {
url String? @db.VarChar(255)
directUrl String? @db.VarChar(255)
provider String? @db.VarChar(255)
custom String? @db.VarChar(255)
downloadId Int
url2 String? @db.VarChar(255)
download downloads? @relation(fields: [downloadId], references: [id], map: "links_ibfk_1")

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View file

@ -2,26 +2,24 @@ import type { PropsWithChildren, JSX } from 'react'
import clsx from 'clsx'
import { BarsRotateFade } from 'react-svg-spinners'
export default function Button(
props: PropsWithChildren<{ className?: string; loading?: boolean }> & JSX.IntrinsicElements['button']
) {
const { children, className, loading = false, ...restProps } = props
export default function Button(props: PropsWithChildren<{ loading?: boolean }> & JSX.IntrinsicElements['button']) {
const { children, className, loading = false, type = 'button', ...restProps } = props
return (
<button
type={type}
className={clsx(
{ 'cursor-progress': loading },
'py-2 px-3.5 rounded-lg bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400',
{ 'cursor-progress': loading, loading },
'group py-2 px-3.5 rounded-lg bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400',
className
)}
{...restProps}
>
<div className='relative flex'>
<span className={clsx({ invisible: loading })}>{children}</span>
{loading ? (
<div className='absolute top-0 left-0 w-full flex justify-center'>
<BarsRotateFade color='white' />
</div>
) : null}
<span className='group-[.loading]:invisible'>{children}</span>
<div className='hidden group-[.loading]:flex absolute top-0 left-0 w-full justify-center'>
<BarsRotateFade color='white' />
</div>
</div>
</button>
)

View file

@ -0,0 +1,25 @@
---
import { SEO } from 'astro-seo'
---
<SEO
slot='head'
titleDefault='Sitting on Clouds'
title='Sitting on Clouds'
description='Largest Video Game & Animation Soundtrack サウンドトラック Archive'
openGraph={{
basic: {
title: 'Sitting on Clouds — High Quality soundtrack library',
type: 'website',
image: '/img/assets/clouds_thumb.png',
url: Astro.site
},
optional: {
description: 'Largest Video Game & Animation Soundtrack サウンドトラック Archive',
siteName: 'Sitting on Clouds'
}
}}
extend={{
meta: [{ name: 'theme-color', content: '#ffffff' }]
}}
/>

View file

@ -0,0 +1,57 @@
import { useImmer } from 'use-immer'
import { Prisma } from '@prisma/client'
import Button from 'components/Button'
import { InputArea } from 'components/form/Input'
type Disc = Prisma.discsGetPayload<{ select: { number: true; body: true } }>
interface Props {
defaultValue?: Disc[]
}
export default function DiscSection(props: Props) {
const { defaultValue = [{ number: 0, body: '' }] } = props
const [discs, setDiscs] = useImmer<Disc[]>(defaultValue)
return (
<>
<div className='flex gap-x-2'>
<Button
onClick={() => {
setDiscs((current) => {
current.push({ number: 0, body: '' })
})
}}
>
Add empty disc
</Button>
<Button
onClick={() => {
setDiscs((current) => current.filter((value, index) => (value.body?.length ?? 0) > 0 || index === 0))
}}
>
Remove empty discs
</Button>
</div>
<div className='grid grid-cols-3 gap-4'>
{discs.map((value, index) => (
<div key={index}>
<input hidden name={`discs.${index}.number`} value={index} readOnly type='number' />
<InputArea
dark
label={`Disc ${index + 1}`}
name={`discs.${index}.body`}
defaultValue={value.body}
onChange={(ev) => {
setDiscs((current) => {
current[index].body = ev.target.value
})
}}
/>
</div>
))}
</div>
</>
)
}

View file

@ -0,0 +1,126 @@
import { useImmer } from 'use-immer'
import type { Prisma } from '@prisma/client'
import Button from 'components/Button'
import { Input, InputSelect } from 'components/form/Input'
import { DownloadProvider } from 'utils/consts'
type Download = Prisma.downloadsGetPayload<{
select: { title: true; links: { select: { provider: true; url: true; url2: true; directUrl: true } } }
}>
const defaultLink = { provider: DownloadProvider.MEDIAFIRE, url: null, url2: null, directUrl: null }
const defaultSection: Download = { title: '', links: [defaultLink] }
interface Props {
defaultValue?: Download[]
}
export default function DownloadSection(props: Props) {
const { defaultValue = [defaultSection] } = props
const [downloads, setDownloads] = useImmer<Download[]>(defaultValue)
return (
<>
<div className='flex gap-x-2'>
<Button
onClick={() => {
setDownloads((current) => {
current.push(defaultSection)
})
}}
>
Add download section
</Button>
</div>
<div className='flex flex-col gap-y-1.5'>
{downloads.map((d, index) => (
<div key={index} className='border-2 border-white/60 rounded-md p-2'>
<div>
<Input
dark
label='Title'
name={`downloads.${index}.title`}
value={d.title ?? ''}
onChange={(ev) => {
setDownloads((current) => {
current[index].title = ev.target.value
})
}}
/>
</div>
{d.links.map((link, linkIndex) => (
<div key={linkIndex} className='flex'>
<div className='grid grid-cols-4 gap-4'>
<InputSelect
dark
name={`downloads.${index}.links.${linkIndex}.provider`}
label='Provider'
defaultValue={link.provider ?? ''}
>
{Object.values(DownloadProvider).map((provider) => (
<option key={provider} value={provider}>
{provider}
</option>
))}
</InputSelect>
<Input
dark
label='Ouo.io (Url)'
name={`downloads.${index}.links.${linkIndex}.url`}
defaultValue={link.url ?? ''}
/>
<Input
dark
label='Fly.inc (Url 2)'
name={`downloads.${index}.links.${linkIndex}.url2`}
defaultValue={link.url2 ?? ''}
/>
<Input
dark
label='Direct'
name={`downloads.${index}.links.${linkIndex}.directUrl`}
defaultValue={link.directUrl ?? ''}
/>
</div>
<div className='flex p-3'>
<Button
className='mt-auto bg-red-500 hover:bg-red-600'
onClick={() => {
setDownloads((current) => {
current[index].links.splice(linkIndex, 1)
})
}}
>
X
</Button>
</div>
</div>
))}
<div className='flex gap-x-2'>
<Button
onClick={() => {
setDownloads((current) => {
current[index].links.push(defaultLink)
})
}}
>
Add link
</Button>
<Button
onClick={() => {
setDownloads((current) => {
current.splice(index, 1)
})
}}
>
Remove section
</Button>
</div>
</div>
))}
</div>
</>
)
}

View file

@ -0,0 +1,73 @@
import { useImmer } from 'use-immer'
import type { Prisma } from '@prisma/client'
import Button from 'components/Button'
import { Input, InputSelect } from 'components/form/Input'
import { StoreProviders } from 'utils/consts'
type Store = Prisma.storesGetPayload<{ select: { url: true; provider: true } }>
interface Props {
defaultValue?: Store[]
}
const defaultStore: Store = { provider: StoreProviders.AMAZON, url: '' }
export default function StoresSection(props: Props) {
const { defaultValue = [defaultStore] } = props
const [stores, setStores] = useImmer<typeof defaultValue>(defaultValue)
return (
<>
<div className='flex gap-x-2'>
<Button
onClick={() => {
setStores((current) => {
current.push(defaultStore)
})
}}
>
Add store link
</Button>
</div>
<div className='flex flex-col gap-y-2.5'>
{stores.map((d, index) => (
<div key={index} className='flex gap-x-2 '>
<InputSelect dark name={`stores.${index}.provider`} label='Provider' defaultValue={d.provider ?? ''}>
{Object.values(StoreProviders).map((provider) => (
<option key={provider} value={provider}>
{provider}
</option>
))}
</InputSelect>
<Input
dark
label='Url'
name={`stores.${index}.url`}
value={d.url ?? ''}
onChange={(ev) => {
setStores((current) => {
current[index].url = ev.target.value
})
}}
/>
<div className='flex py-3'>
<Button
className='mt-auto bg-red-500 hover:bg-red-600'
onClick={() => {
setStores((current) => {
current.splice(index, 1)
})
}}
>
X
</Button>
</div>
</div>
))}
</div>
</>
)
}

View file

@ -0,0 +1,71 @@
import { useEffect, useState } from 'react'
import { MultiSelect, type Option } from 'react-multi-select-component'
interface Props {
url: string
nameColumn: string
className?: string
valueColumn?: string
defaultSelected?: Option[]
name: string
}
function toMapValue(data: any[], nameColumn: string, valueColumn: string) {
return data.map((item: { [nameColumn]: string; [valueColumn]: string }) => ({
label: item[nameColumn],
value: item[valueColumn]
}))
}
export default function AsyncMultiSelect(props: Props) {
const { url: defaultUrl, nameColumn, valueColumn = 'id', className, defaultSelected = [], name } = props
const [url, setUrl] = useState(defaultUrl)
const [loading, setLoading] = useState(false)
const [selected, setSelected] = useState<Option[]>(defaultSelected)
const [options, setOptions] = useState<Option[]>(defaultSelected)
useEffect(() => {
async function fetchData() {
try {
setLoading(true)
const res = await fetch(url)
if (!res.ok) return
const data = await res.json()
const dataOptions = toMapValue(data, nameColumn, valueColumn)
setOptions([...defaultSelected, ...dataOptions])
} catch (err) {
console.error(err)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
function filterOptions(options: Option[], search: string) {
if (search.length === 0) setUrl(defaultUrl)
else setUrl(`${defaultUrl}?q=${search}`)
return options
}
return (
<>
<MultiSelect
labelledBy={name}
isLoading={loading}
options={options}
value={selected}
onChange={setSelected}
filterOptions={filterOptions}
className={className}
/>
{selected.map((s, i) => (
<input key={i} hidden name={`${name}.${i}`} value={s.value} readOnly />
))}
</>
)
}

View file

@ -1,15 +1,67 @@
import type { ComponentProps, PropsWithChildren } from 'react'
import clsx from 'clsx'
export default function Input(props: PropsWithChildren<ComponentProps<'input'>>) {
const { name, className, children, ...attrs } = props
export function InputLabel(props: PropsWithChildren<{ dark: boolean; name: string }>) {
const { dark, name, children } = props
return (
<label htmlFor={name} className={clsx('font-medium', dark ? 'text-white' : 'text-black')}>
{children}:
</label>
)
}
interface CustomInputProps {
name: string
label: string
dark?: boolean
defaultValue?: string | number | null
}
export function Input(props: CustomInputProps & Omit<ComponentProps<'input'>, 'defaultValue'>) {
const { name, className, dark = false, defaultValue, label, ...attrs } = props
return (
<div className='flex flex-col'>
<label htmlFor={name} className='font-medium text-black'>
{children}:
</label>
<input {...attrs} name={name} className={clsx('bg-zinc-200 rounded-md p-2 mt-2 mb-3 text-black', className)} />
<div className={clsx('flex flex-col', className)}>
<InputLabel dark={dark} name={name}>
{label}
</InputLabel>
<input
{...attrs}
defaultValue={defaultValue ?? undefined}
name={name}
className='bg-zinc-200 rounded-md p-2 mt-2 mb-3 text-black'
/>
</div>
)
}
export function InputArea(props: CustomInputProps & Omit<ComponentProps<'textarea'>, 'defaultValue'>) {
const { name, className, dark = false, defaultValue, label, ...attrs } = props
return (
<div className={clsx('flex flex-col', className)}>
<InputLabel dark={dark} name={name}>
{label}
</InputLabel>
<textarea
{...attrs}
defaultValue={defaultValue ?? undefined}
name={name}
className='bg-zinc-200 rounded-md p-2 mt-2 mb-3 text-black'
/>
</div>
)
}
export function InputSelect(props: CustomInputProps & ComponentProps<'select'>) {
const { name, className, dark = false, label, ...attrs } = props
return (
<div className='flex flex-col'>
<InputLabel dark={dark} name={name}>
{label}
</InputLabel>
<select name={name} className='bg-zinc-200 rounded-md p-2 mt-2 h-full mb-3 text-black' {...attrs} />
</div>
)
}

View file

@ -0,0 +1,21 @@
import { useState } from 'react'
import { type SelectProps, type Option, MultiSelect } from 'react-multi-select-component'
interface Props extends Omit<SelectProps, 'value' | 'onChange'> {
name: string
defaultSelected?: Option[]
}
export default function MultiSelectWrapper(props: Props) {
const { defaultSelected = [], name, ...rest } = props
const [selected, setSelected] = useState<Option[]>(defaultSelected)
return (
<>
<MultiSelect value={selected} onChange={setSelected} {...rest} />
{selected.map((s, i) => (
<input key={i} name={`${name}.${i}`} value={s.value} hidden readOnly />
))}
</>
)
}

View file

@ -1,10 +1,11 @@
import { useState, type FormEvent } from 'react'
import toast from 'react-hot-toast'
import * as m from 'paraglide/messages.js'
import Button from 'components/Button'
import Modal from 'components/Modal'
import Input from 'components/form/Input'
import { Input } from 'components/form/Input'
import { signUp } from 'utils/auth-client'
export default function RegisterBtn() {
@ -46,19 +47,11 @@ export default function RegisterBtn() {
<form onSubmit={handleSubmit}>
<div className='px-4 pt-5 pb-4 gap-x-4 gap-y-1 flex flex-col'>
<div className='flex gap-x-4'>
<Input name='username' required>
{m.username()}
</Input>
<Input name='name' required>
{m.displayName()}
</Input>
<Input name='username' required label={m.username()} />
<Input name='name' required label={m.displayName()} />
</div>
<Input name='email' type='email' required>
{m.email()}
</Input>
<Input name='password' type='password' required>
{m.password()}
</Input>
<Input name='email' type='email' required label={m.email()} />
<Input name='password' type='password' required label={m.password()} />
<div className='mx-auto'>
<Button type='submit' loading={loading} disabled={loading}>
{m.register()}

View file

@ -1,7 +1,6 @@
import { Prisma } from '@prisma/client'
import axios from 'axios'
import prismaClient from 'utils/prisma-client'
import { getImage } from 'astro:assets'
import { WEBHOOK_URL } from 'astro:env/server'
@ -13,29 +12,11 @@ type AlbumArtistNames = Prisma.albumsGetPayload<typeof albumArtistNames>
async function postWebhook(album: AlbumArtistNames, userText = '') {
const url = `https://www.sittingonclouds.net/album/${album.id}`
const content = `${url}${userText}`
const artistNames = album.artists.map((a) => a.artist.name)
const coverImage = await getImage({
src: `https://cdn.sittingonclouds.net/album/${album.id}.png`,
height: 150,
width: 150
})
const embeds = [
{
title: album.title,
type: 'rich',
description: album.subTitle || artistNames.join(' - '),
url,
color: parseInt(album.headerColor.substring(1), 16),
thumbnail: { url: `https://www.sittingonclouds.net${coverImage.src}` }
}
]
const payload = { content }
const payload = { content, embeds }
await axios.post(WEBHOOK_URL, payload)
}
export const requestPOST = (operation: string, body: any) => axios.post(`http://localhost:7001/${operation}`, body)
export async function handleComplete(album: AlbumArtistNames, requestId?: number) {
if (requestId) {
const request = await prismaClient.requests.findUnique({
@ -44,7 +25,7 @@ export async function handleComplete(album: AlbumArtistNames, requestId?: number
})
if (!request || request.state === 'complete') return
await requestPOST('complete', { requestId: request.id })
await fetch('http://localhost:7001/complete', { method: 'POST', body: JSON.stringify({ requestId: request.id }) })
const userText =
request.userID || request.user

View file

@ -9,6 +9,7 @@ interface Props {
import Base from './base.astro'
import AlbumBox from 'components/AlbumBox.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
import FooterNav from 'components/lastAdded/FooterNav.astro'
const { albums, limitMD, limitXS, ...listProps } = Astro.props
@ -18,6 +19,7 @@ if (albums.length === 0) {
}
---
<DefaultSEO />
<Base>
<div class='flex flex-col w-full'>
<div class='w-full min-h-100vh mx-auto max-w-[1140px]'>

View file

@ -10,10 +10,6 @@ import 'styles/global.css'
<html lang={languageTag()} dir={Astro.locals.paraglide.dir} class='h-full'>
<head>
<title>Sitting on Clouds</title>
<meta property='og:type' content='website' />
<meta property='og:site_name' content='Sitting on Clouds' />
<link rel='preconnect' href='https://fonts.googleapis.com' />
<link rel='preconnect' href='https://fonts.gstatic.com' crossorigin />
<link
@ -21,12 +17,7 @@ import 'styles/global.css'
rel='stylesheet'
/>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='theme-color' content='#ffffff' />
<meta property='og:url' content='/' />
<meta property='og:title' content='Sitting on Clouds — High Quality soundtrack library' />
<meta property='og:description' content='Largest Video Game & Animation Soundtrack サウンドトラック Archive' />
<meta property='og:image' content='/img/assets/clouds_thumb.png' />
<slot name='head' />
<meta name='generator' content={Astro.generator} />
<meta charset='utf-8' />

View file

@ -0,0 +1,219 @@
---
import { AlbumStatus } from '@prisma/client'
import prismaClient from 'utils/prisma-client'
import Base from 'layouts/base.astro'
import DiscSection from 'components/adminAlbum/DiscSection'
import DownloadSections from 'components/adminAlbum/DownloadSections'
import AsyncMultiSelect from 'components/form/AsyncMultiSelect'
import { Input, InputArea, InputLabel, InputSelect } from 'components/form/Input'
import Button from 'components/Button'
import MultiSelectWrapper from 'components/form/MultiSelectWrapper'
import StoresSection from 'components/adminAlbum/StoresSection'
import DefaultSEO from 'components/DefaultSEO.astro'
const { user, permissions } = Astro.locals
if (!user || !permissions.includes('UPDATE')) return Astro.redirect('/404')
const albumId = parseInt(Astro.params.id as string)
const album = await prismaClient.albums.findUnique({
where: { id: albumId },
include: {
categories: true,
classifications: true,
platforms: { select: { platform: { select: { id: true, name: true } } } },
games: { select: { game: { select: { slug: true, name: true } } } },
animations: { select: { animation: { select: { id: true, title: true } } } },
artists: { select: { artist: { select: { name: true } } } },
relatedAlbums: {
select: {
relatedAlbum: { select: { id: true, title: true } }
}
},
stores: { select: { provider: true, url: true } },
downloads: {
select: { title: true, links: { select: { provider: true, url: true, url2: true, directUrl: true } } }
},
discs: { select: { number: true, body: true } }
}
})
if (!album) {
return Astro.redirect('/404')
}
---
<style>
hr {
margin-block: calc(var(--spacing) * 2);
}
</style>
<script>
import toast from 'react-hot-toast'
document.getElementById('editAlbum')?.addEventListener('submit', async (e: SubmitEvent) => {
e.preventDefault()
const formData = new FormData(e.target as HTMLFormElement)
document.getElementById('editAlbumSubmit')?.classList.add('loading')
const response = await fetch('/api/album/edit', { method: 'POST', body: formData })
document.getElementById('editAlbumSubmit')?.classList.remove('loading')
if (response.ok) {
toast.success('Album updated successfully')
} else {
toast.error(response.statusText)
}
})
</script>
<DefaultSEO />
<Base>
<div class='flex flex-col w-full'>
<div class='w-full min-h-100vh mx-auto max-w-[1140px]'>
<form id='editAlbum' class='my-4 p-4 bg-dark rounded-md mx-2 flex gap-y-2 flex-col'>
<input hidden readonly name='albumId' value={album.id} type='number' />
<div class='grid grid-cols-4 gap-x-4 gap-y-1'>
<Input dark defaultValue={album.title} name='title' label='Title' className='col-span-2' />
<Input dark defaultValue={album.subTitle} name='subTitle' label='Subtitle' className='col-span-2' />
<Input dark defaultValue={album.description} name='description' label='Description' className='col-span-3' />
<Input dark name='cover' label='Cover image' type='file' />
<InputSelect dark name='status' label='Status' value={album.status}>
<option value={AlbumStatus.HIDDEN}>Hidden</option>
<option value={AlbumStatus.SHOW}>Show</option>
</InputSelect>
<Input
dark
defaultValue={album.releaseDate?.toISOString().slice(0, 10)}
name='releaseDate'
type='date'
label='Release Date'
/>
<Input dark defaultValue={album.label} name='label' label='Label' />
<Input dark defaultValue={album.vgmdb} name='vgmdb' label='VGMDB' />
<InputArea
dark
defaultValue={album.artists.map((a) => a.artist.name).join(', ')}
name='artists'
label='Artists'
className='col-span-3'
/>
<div class='flex flex-col'>
<InputLabel dark name='classifications'>Classifications</InputLabel>
<MultiSelectWrapper
client:only='react'
name='classifications'
options={[
{ value: 'Arrangement', label: 'Arrangement' },
{ value: 'Drama', label: 'Drama' },
{ value: 'GameRip', label: 'GameRip' },
{ value: 'Live Event', label: 'Live Event' },
{ value: 'Original Soundtrack', label: 'Original Soundtrack' },
{ value: 'Vocal', label: 'Vocal' }
]}
defaultSelected={album.classifications?.map(({ classificationName }) => ({
value: classificationName,
label: classificationName
})) || []}
labelledBy='classifications'
className='rounded-md py-2 h-full'
/>
</div>
<div class='flex flex-col'>
<InputLabel dark name='categories'>Categories</InputLabel>
<MultiSelectWrapper
client:only='react'
options={[
{ value: 'Game', label: 'Game' },
{ value: 'Animation', label: 'Animation' }
]}
name='categories'
defaultSelected={album.categories?.map(({ categoryName }) => ({
value: categoryName,
label: categoryName
})) || []}
labelledBy='categories'
className='rounded-md py-2 h-full'
/>
</div>
<div class='flex flex-col'>
<InputLabel dark name='platforms'>Platforms</InputLabel>
<AsyncMultiSelect
client:only='react'
name='platforms'
url='/api/platform/find'
nameColumn='name'
defaultSelected={album.platforms?.map((p) => ({
value: p.platform.id,
label: p.platform.name as string
})) || []}
className='rounded-md py-2 h-full'
/>
</div>
<div class='flex flex-col'>
<InputLabel dark name='games'>Games</InputLabel>
<AsyncMultiSelect
client:only='react'
name='games'
url='/api/game/find'
valueColumn='slug'
nameColumn='name'
defaultSelected={album.games?.map((g) => ({
value: g.game.slug,
label: g.game.name as string
})) || []}
className='rounded-md py-2 h-full'
/>
</div>
<div class='flex flex-col'>
<InputLabel dark name='animations'>Animations</InputLabel>
<AsyncMultiSelect
client:only='react'
name='animations'
url='/api/anim/find'
nameColumn='title'
defaultSelected={album.animations?.map((g) => ({
value: g.animation.id,
label: g.animation.title as string
})) || []}
className='rounded-md py-2 h-full'
/>
</div>
<div class='flex flex-col'>
<InputLabel dark name='related'>Related albums</InputLabel>
<AsyncMultiSelect
client:only='react'
name='related'
url='/api/album/find'
nameColumn='title'
defaultSelected={album.relatedAlbums?.map((g) => ({
value: g.relatedAlbum.id,
label: g.relatedAlbum.title as string
})) || []}
className='rounded-md py-2 h-full'
/>
</div>
</div>
<hr />
<StoresSection client:only='react' defaultValue={album.stores} />
<hr />
<div class='flex flex-col gap-y-4'>
<DiscSection client:only='react' defaultValue={album.discs} />
</div>
<hr />
<div class='flex flex-col gap-y-4'>
<DownloadSections client:only='react' defaultValue={album.downloads} />
</div>
<hr />
<div class=''>
<Button type='submit' id='editAlbumSubmit'>Save changes</Button>
</div>
</form>
</div>
</div>
</Base>

View file

@ -1,8 +1,9 @@
---
import prismaClient from 'utils/prisma-client'
import * as m from 'paraglide/messages'
import { Image } from 'astro:assets'
import { getImage, Image } from 'astro:assets'
import { AlbumStatus } from '@prisma/client'
import { SEO } from 'astro-seo'
import BaseLayout from 'layouts/base.astro'
import TrackList from 'components/albumPage/TrackList'
@ -47,7 +48,13 @@ if (!album) {
Astro.response.status = 404
Astro.response.statusText = 'Not found'
}
const { currentLocale } = Astro
const coverImage = await getImage({
src: `https://cdn.sittingonclouds.net/album/${album?.id}.png`,
height: 150,
width: 150
})
---
<style>
@ -58,6 +65,27 @@ const { currentLocale } = Astro
}
</style>
<SEO
slot='head'
titleDefault='Sitting on Clouds'
title={album?.title ?? undefined}
description='Largest Video Game & Animation Soundtrack サウンドトラック Archive'
openGraph={{
basic: {
title: album?.title ?? '',
type: 'website',
image: `https://www.sittingonclouds.net${coverImage.src}`,
url: Astro.url.pathname
},
optional: {
description: album?.subTitle || album?.artists.map((a) => a.artist.name).join(' - '),
siteName: 'Sitting on Clouds'
}
}}
extend={{
meta: [{ name: 'theme-color', content: album?.headerColor }]
}}
/>
<BaseLayout>
<div
class={`w-full min-h-100vh bg-fixed bg-center bg-cover`}
@ -286,13 +314,13 @@ const { currentLocale } = Astro
/>
</div>
{url2 && (
<DownloadBtn href={url2} alt='fly' icon={flyIcon}>
{m.flyInc()}
<DownloadBtn href={url2} alt='fly inc' icon={flyIcon}>
Fly.inc
</DownloadBtn>
)}
{url ? (
<DownloadBtn href={url} alt='ouo' icon={ouoIcon}>
{m.ouoIO()}
ouo.io
</DownloadBtn>
) : null}

View file

@ -4,6 +4,7 @@ import prismaClient from 'utils/prisma-client'
import BaseLayout from 'layouts/base.astro'
import Sidebar from 'components/Sidebar.astro'
import LetterSection from 'components/albumList/letterSection.astro'
import DefaultSEO from 'components/DefaultSEO.astro'
const letters: { letter: string; count: BigInt }[] = await prismaClient.$queryRaw`
SELECT DISTINCT UPPER(LEFT(title, 1)) AS letter, COUNT(*) AS count
@ -14,6 +15,7 @@ const letters: { letter: string; count: BigInt }[] = await prismaClient.$queryRa
`
---
<DefaultSEO />
<BaseLayout>
<div class='flex flex-col md:flex-row flex-1 max-w-[2000px]'>
<div class='flex-1 px-5 bg-dark'>
@ -38,11 +40,11 @@ const letters: { letter: string; count: BigInt }[] = await prismaClient.$queryRa
<div class='flex uppercase border-y-2 text-4xl justify-center border-white py-1.5'>{l.letter}</div>
<div class='my-4'>
<LetterSection letter={l.letter} server:defer>
<Fragment slot="fallback">
<Fragment slot='fallback'>
<div class='flex flex-col gap-y-2'>
{Array.from({ length: Number(l.count) }).map(() => (
<div class='animate-pulse h-3.5 w-lg bg-gray/85' />
))}
{Array.from({ length: Number(l.count) }).map(() => (
<div class='animate-pulse h-3.5 w-lg bg-gray/85' />
))}
</div>
</Fragment>
</LetterSection>

View file

@ -1,23 +1,21 @@
import type { APIRoute } from 'astro'
import * as s from 'superstruct'
import prismaClient from 'utils/prisma-client'
import { AlbumStatus } from '@prisma/client'
import { Status, parseForm, slug } from 'utils/form'
import { writeImg, getImgColor } from 'utils/img'
import { handleCover } from 'utils/img'
import { handleComplete } from 'integrations/requestCat'
import { CreateAlbum } from 'schemas/album'
import { AlbumBase } from 'schemas/album'
export const POST: APIRoute = async ({ request, locals }) => {
const { session, permissions, user } = locals
if (!session || !user) return Status(401)
if (!permissions.includes('CREATE')) return Status(403)
const { permissions, user } = locals
if (!user || !permissions.includes('CREATE')) return Status(403)
let body
try {
const formData = await parseForm(request)
body = s.create(formData, CreateAlbum)
const formData = await parseForm(await request.formData())
body = s.create(formData, AlbumBase)
} catch (err) {
return Status(422, (err as Error).message)
}
@ -58,21 +56,13 @@ export const POST: APIRoute = async ({ request, locals }) => {
include: { artists: { include: { artist: { select: { name: true } } } } }
})
const handleCover = async () => {
const coverPath = await writeImg(body.cover, 'album', albumRow.id)
const headerColor = await getImgColor(coverPath)
await tx.albums.update({ where: { id: albumRow.id }, data: { headerColor } })
albumRow.headerColor = headerColor
}
await Promise.all([
handleCover(),
handleCover(body.cover, 'album', albumRow.id, tx),
Promise.all(
body.downloads.map((d) =>
tx.downloads.create({
data: {
title: d.title,
small: d.small,
albumId: albumRow.id,
links: { create: d.links }
}

View file

@ -0,0 +1,87 @@
import type { APIRoute } from 'astro'
import * as s from 'superstruct'
import prismaClient from 'utils/prisma-client'
import { Status, parseForm, slug } from 'utils/form'
import { handleCover } from 'utils/img'
import { EditAlbum } from 'schemas/album'
export const POST: APIRoute = async ({ request, locals }) => {
const { permissions, user } = locals
if (!user || !permissions.includes('UPDATE')) return Status(403)
let body
try {
const formData = await parseForm(await request.formData())
body = s.create(formData, EditAlbum)
} catch (err) {
return Status(422, (err as Error).message)
}
try {
await prismaClient.$transaction(async (tx) => {
const {
artists,
animations,
categories,
classifications,
games,
platforms,
discs,
downloads,
stores,
cover,
related,
albumId,
...rest
} = body
await tx.albums.update({
where: { id: body.albumId },
data: {
...rest,
artists: {
deleteMany: {},
create: artists
?.split(',')
.map((name: string) => ({ slug: slug(name.trim()), name: name.trim() }))
.map((a) => ({
artist: {
connectOrCreate: {
create: a,
where: { slug: a.slug }
}
}
}))
},
animations: { deleteMany: {}, create: animations?.map((id) => ({ animation: { connect: { id } } })) },
categories: { deleteMany: {}, create: categories?.map((c) => ({ category: { connect: { name: c } } })) },
games: { deleteMany: {}, create: games?.map((slug) => ({ game: { connect: { slug } } })) },
platforms: { deleteMany: {}, create: platforms?.map((id) => ({ platform: { connect: { id } } })) },
discs: { deleteMany: {}, createMany: { data: body.discs ?? [] } },
relatedAlbums: { deleteMany: {}, create: related?.map((id) => ({ relatedAlbum: { connect: { id } } })) }
}
})
await Promise.all([
cover ? handleCover(cover, 'album', albumId, tx) : undefined,
downloads
? tx.downloads.createMany({
data: downloads.map((d) => ({
title: d.title,
albumId: albumId,
links: { create: d.links }
}))
})
: undefined
])
})
// if (albumRow.status === AlbumStatus.SHOW) await handleComplete(albumRow, body.request)
return Status(200, body.albumId.toString())
} catch (err) {
console.error(err)
return Status(500, (err as Error).message)
}
}

View file

@ -0,0 +1,23 @@
import type { APIRoute } from 'astro'
import prismaClient from 'utils/prisma-client'
export const GET: APIRoute = async (context) => {
const { url } = context
const titleParam = url.searchParams.get('q')
const anims = await prismaClient.albums.findMany({
where: titleParam
? { OR: [{ title: { contains: titleParam } }, { subTitle: { contains: titleParam } }] }
: undefined,
select: { id: true, title: true },
take: 10,
orderBy: { createdAt: 'desc' }
})
return new Response(JSON.stringify(anims), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
}

View file

@ -0,0 +1,21 @@
import type { APIRoute } from 'astro'
import prismaClient from 'utils/prisma-client'
export const GET: APIRoute = async (context) => {
const { url } = context
const titleParam = url.searchParams.get('q')
const anims = await prismaClient.animation.findMany({
where: titleParam ? { title: { contains: titleParam } } : undefined,
select: { id: true, title: true },
take: 10,
orderBy: { createdAt: 'desc' }
})
return new Response(JSON.stringify(anims), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
}

View file

@ -0,0 +1,21 @@
import type { APIRoute } from 'astro'
import prismaClient from 'utils/prisma-client'
export const GET: APIRoute = async (context) => {
const { url } = context
const titleParam = url.searchParams.get('q')
const anims = await prismaClient.game.findMany({
where: titleParam ? { name: { contains: titleParam } } : undefined,
select: { slug: true, name: true },
take: 10,
orderBy: { createdAt: 'desc' }
})
return new Response(JSON.stringify(anims), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
}

View file

@ -0,0 +1,21 @@
import type { APIRoute } from 'astro'
import prismaClient from 'utils/prisma-client'
export const GET: APIRoute = async (context) => {
const { url } = context
const titleParam = url.searchParams.get('q')
const anims = await prismaClient.platform.findMany({
where: titleParam ? { name: { contains: titleParam } } : undefined,
select: { id: true, name: true },
take: 10,
orderBy: { createdAt: 'desc' }
})
return new Response(JSON.stringify(anims), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
}

View file

@ -1,4 +1,5 @@
---
import DefaultSEO from 'components/DefaultSEO.astro'
import ForgorForm from 'components/form/ForgorForm'
import BaseLayout from 'layouts/base.astro'
@ -7,4 +8,5 @@ const token = Astro.url.searchParams.get('token')
if (!token) return Astro.redirect('/404')
---
<DefaultSEO />
<BaseLayout><ForgorForm token={token} client:only='react' /></BaseLayout>

View file

@ -1,9 +1,9 @@
import * as s from 'superstruct'
import { AlbumStatus } from '@prisma/client'
import { DownloadProvider } from 'utils/consts'
const LinkInput = s.object({
provider: s.string(),
custom: s.optional(s.string()),
export const LinkInput = s.object({
provider: s.enums(Object.values(DownloadProvider)),
url: s.optional(s.string()),
url2: s.optional(s.string()),
directUrl: s.optional(s.string())
@ -11,28 +11,36 @@ const LinkInput = s.object({
export const DownloadInput = s.object({
title: s.string(),
small: s.defaulted(s.boolean(), false),
links: s.defaulted(s.array(LinkInput), [])
})
export const CreateAlbum = s.object({
const coerceInt = s.coerce(s.integer(), s.string(), (value) => parseInt(value))
export const StoreInput = s.object({ provider: s.string(), url: s.string() })
export const DiscInput = s.object({ number: coerceInt, body: s.string() })
export const AlbumBase = s.object({
cover: s.instance(File),
title: s.optional(s.string()),
subTitle: s.optional(s.string()),
releaseDate: s.optional(s.string()),
releaseDate: s.optional(s.date()),
label: s.optional(s.string()),
vgmdb: s.optional(s.string()),
description: s.optional(s.string()),
status: s.defaulted(s.enums(Object.values(AlbumStatus)), AlbumStatus.HIDDEN),
animations: s.defaulted(s.array(s.integer()), []),
animations: s.defaulted(s.array(coerceInt), []),
artists: s.defaulted(s.array(s.string()), []),
categories: s.defaulted(s.array(s.string()), []),
classifications: s.defaulted(s.array(s.string()), []),
games: s.defaulted(s.array(s.string()), []),
platforms: s.defaulted(s.array(s.integer()), []),
discs: s.defaulted(s.array(s.object({ number: s.integer(), body: s.string() })), []),
platforms: s.defaulted(s.array(coerceInt), []),
discs: s.defaulted(s.array(DiscInput), []),
downloads: s.defaulted(s.array(DownloadInput), []),
related: s.defaulted(s.array(s.number()), []),
stores: s.defaulted(s.array(s.object({ provider: s.string(), url: s.string() })), []),
request: s.optional(s.integer())
related: s.defaulted(s.array(coerceInt), []),
stores: s.defaulted(s.array(StoreInput), []),
request: s.optional(coerceInt)
})
export const EditAlbum = s.assign(
s.partial(AlbumBase),
s.object({ albumId: coerceInt, artists: s.optional(s.string()) })
)

View file

@ -42,4 +42,19 @@
color: var(--color-hover-link);
text-decoration-line: underline;
}
option {
color: var(--color-dark);
}
.rmsc .select-item .item-renderer span,
.rmsc .dropdown-heading-value span {
color: var(--color-dark);
}
.rmsc svg.gray,
.rmsc svg.gray line {
fill: var(--color-dark);
color: var(--color-dark);
}
}

22
src/utils/consts.ts Normal file
View file

@ -0,0 +1,22 @@
export enum DownloadProvider {
MEGA = 'MEGA',
MEDIAFIRE = 'MEDIAFIRE',
RANOZ = 'RANOZ',
TERABOX = 'TERABOX',
MIRROR = 'MIRROR'
}
export enum StoreProviders {
AMAZON = 'amazon',
AMAZON_JP = 'amazon_jp',
PLAY_ASIA = 'play_asia',
CD_JAPAN = 'cd_japan',
SPOTIFY = 'spotify',
GOOGLE_PLAY = 'google_play',
STEAM = 'steam',
MORA = 'mora',
APPLE_MUSIC = 'apple_music',
OTOTOY = 'ototoy',
BANDCAMP = 'bandcamp',
DEEZER = 'deezer'
}

View file

@ -1,9 +1,10 @@
import slugify from 'slugify'
import { decode } from 'decode-formdata'
export const Status = (status: number, statusText?: string) => new Response(null, { status, statusText })
export const slug = (text: string) => slugify(text, { lower: true, strict: true })
function formToObject(formData: FormData) {
export function formToObject(formData: FormData) {
const object: Record<string, any> = {}
for (const entry of formData.entries()) {
const [key, value] = entry
@ -13,13 +14,13 @@ function formToObject(formData: FormData) {
return object
}
export async function parseForm(request: Request) {
const formData = await request.formData()
const formObject = formToObject(formData)
const { data: dataInput, ...rest } = formObject
export async function parseForm(formData: FormData) {
const formObject = decode(formData, {
arrays: ['animations', 'classifications', 'categories', 'platforms', 'related', 'games', 'downloads', 'discs'],
dates: ['releaseDate']
})
const data = JSON.parse(dataInput)
return { ...data, ...rest }
return formObject
}
export function getRandom<T>(array: T[]): T {

View file

@ -1,6 +1,7 @@
import path from 'node:path'
import fs from 'node:fs/promises'
import sharp from 'sharp'
import type { PrismaClient } from '@prisma/client/extension'
function colorToHex(color: number) {
const hexadecimal = color.toString(16)
@ -16,11 +17,27 @@ export async function writeImg(file: File, folder: string, id: number | string)
const fullPath = path.join(pathString, `${id}.png`)
const fileArray = Buffer.from(await file.arrayBuffer())
await fs.writeFile(fullPath, fileArray)
await fs.mkdir(pathString, { recursive: true })
if (await fs.stat(fullPath).catch(() => false)) {
await fs.rm(fullPath)
}
await fs.writeFile(fullPath, fileArray)
return fullPath
}
export async function handleImg(file: File, folder: string, id: number | string) {
const coverPath = await writeImg(file, folder, id)
const headerColor = await getImgColor(coverPath)
return headerColor
}
export async function handleCover(file: File, folder: string, id: number | string, tx: PrismaClient) {
const headerColor = await handleImg(file, folder, id)
await tx.albums.update({ where: { id: id }, data: { headerColor } })
}
export async function getImgColor(filePath: string) {
const { dominant } = await sharp(filePath).stats()
const { r, g, b } = dominant

View file

@ -1,5 +1,5 @@
import { PrismaClient } from '@prisma/client'
const prismaClient = new PrismaClient()
const prismaClient = new PrismaClient({ log: ['error'] })
export default prismaClient
export default prismaClient

471
yarn.lock
View file

@ -23,6 +23,17 @@
resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-8.1.1.tgz#95b1947d292a9a2efffba2081796dcaa05ecedfb"
integrity sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==
"@astrojs/check@^0.5.4":
version "0.5.10"
resolved "https://registry.yarnpkg.com/@astrojs/check/-/check-0.5.10.tgz#1e6aa4d2392bb34ae9938f894b6765bd858363b4"
integrity sha512-vliHXM9cu/viGeKiksUM4mXfO816ohWtawTl2ADPgTsd4nUMjFiyAl7xFZhF34yy4hq4qf7jvK1F2PlR3b5I5w==
dependencies:
"@astrojs/language-server" "^2.8.4"
chokidar "^3.5.3"
fast-glob "^3.3.1"
kleur "^4.1.5"
yargs "^17.7.2"
"@astrojs/compiler@^2.0.0", "@astrojs/compiler@^2.10.3":
version "2.10.4"
resolved "https://registry.yarnpkg.com/@astrojs/compiler/-/compiler-2.10.4.tgz#883e469600d06d101d77829c91c4215b270cd503"
@ -33,6 +44,30 @@
resolved "https://registry.yarnpkg.com/@astrojs/internal-helpers/-/internal-helpers-0.5.1.tgz#c9253669cf9caea48744d27f5cc30a06eec8d3d9"
integrity sha512-M7rAge1n2+aOSxNvKUFa0u/KFn0W+sZy7EW91KOSERotm2Ti8qs+1K0xx3zbOxtAVrmJb5/J98eohVvvEqtNkw==
"@astrojs/language-server@^2.8.4":
version "2.15.4"
resolved "https://registry.yarnpkg.com/@astrojs/language-server/-/language-server-2.15.4.tgz#9c2eeb64e4b9df9a52f19c6bfdce5397b8dba094"
integrity sha512-JivzASqTPR2bao9BWsSc/woPHH7OGSGc9aMxXL4U6egVTqBycB3ZHdBJPuOCVtcGLrzdWTosAqVPz1BVoxE0+A==
dependencies:
"@astrojs/compiler" "^2.10.3"
"@astrojs/yaml2ts" "^0.2.2"
"@jridgewell/sourcemap-codec" "^1.4.15"
"@volar/kit" "~2.4.7"
"@volar/language-core" "~2.4.7"
"@volar/language-server" "~2.4.7"
"@volar/language-service" "~2.4.7"
fast-glob "^3.2.12"
muggle-string "^0.4.1"
volar-service-css "0.0.62"
volar-service-emmet "0.0.62"
volar-service-html "0.0.62"
volar-service-prettier "0.0.62"
volar-service-typescript "0.0.62"
volar-service-typescript-twoslash-queries "0.0.62"
volar-service-yaml "0.0.62"
vscode-html-languageservice "^5.2.0"
vscode-uri "^3.0.8"
"@astrojs/markdown-remark@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@astrojs/markdown-remark/-/markdown-remark-6.1.0.tgz#c451c4d802116d1b9aa1a22d71a672199318678d"
@ -104,6 +139,13 @@
is-wsl "^3.1.0"
which-pm-runs "^1.1.0"
"@astrojs/yaml2ts@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@astrojs/yaml2ts/-/yaml2ts-0.2.2.tgz#eabcb75a57a97c5a2f0422a0a03ca14f000f4f5e"
integrity sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==
dependencies:
yaml "^2.5.0"
"@babel/code-frame@^7.26.2":
version "7.26.2"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
@ -276,6 +318,50 @@
resolved "https://registry.yarnpkg.com/@better-fetch/fetch/-/fetch-1.1.15.tgz#d4170657315191cf7b15ab93e0f103776ee9b9ad"
integrity sha512-0Bl8YYj1f8qCTNHeSn5+1DWv2hy7rLBrQ8rS8Y9XYloiwZEfc3k4yspIG0llRxafxqhGCwlGRg+F8q1HZRCMXA==
"@emmetio/abbreviation@^2.3.3":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz#ed2b88fe37b972292d6026c7c540aaf887cecb6e"
integrity sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==
dependencies:
"@emmetio/scanner" "^1.0.4"
"@emmetio/css-abbreviation@^2.1.8":
version "2.1.8"
resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz#b785313486eba6cb7eb623ad39378c4e1063dc00"
integrity sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==
dependencies:
"@emmetio/scanner" "^1.0.4"
"@emmetio/css-parser@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@emmetio/css-parser/-/css-parser-0.4.0.tgz#96135093480c79703df0e4f178f7f8f2b669fbc2"
integrity sha512-z7wkxRSZgrQHXVzObGkXG+Vmj3uRlpM11oCZ9pbaz0nFejvCDmAiNDpY75+wgXOcffKpj4rzGtwGaZxfJKsJxw==
dependencies:
"@emmetio/stream-reader" "^2.2.0"
"@emmetio/stream-reader-utils" "^0.1.0"
"@emmetio/html-matcher@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz#43b7a71b91cdc511cb699cbe9c67bb5d4cab6754"
integrity sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==
dependencies:
"@emmetio/scanner" "^1.0.0"
"@emmetio/scanner@^1.0.0", "@emmetio/scanner@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.4.tgz#e9cdc67194fd91f8b7eb141014be4f2d086c15f1"
integrity sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==
"@emmetio/stream-reader-utils@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz#244cb02c77ec2e74f78a9bd318218abc9c500a61"
integrity sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==
"@emmetio/stream-reader@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz#46cffea119a0a003312a21c2d9b5628cb5fcd442"
integrity sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==
"@emnapi/runtime@^1.2.0":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.3.1.tgz#0fcaa575afc31f455fd33534c19381cfce6c6f60"
@ -949,7 +1035,7 @@
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
@ -1981,6 +2067,79 @@
"@types/babel__core" "^7.20.5"
react-refresh "^0.14.2"
"@volar/kit@~2.4.7":
version "2.4.12"
resolved "https://registry.yarnpkg.com/@volar/kit/-/kit-2.4.12.tgz#26ee33aef0272757f9fe3b6da12279bf56cd9224"
integrity sha512-f9JE8oy9C2rBcCWxUYKUF23hOXz4mwgVXFjk7nHhxzplaoVjEOsKpBm8NI2nBH7Cwu8DRxDwBsbIxMl/8wlLxw==
dependencies:
"@volar/language-service" "2.4.12"
"@volar/typescript" "2.4.12"
typesafe-path "^0.2.2"
vscode-languageserver-textdocument "^1.0.11"
vscode-uri "^3.0.8"
"@volar/language-core@2.4.12", "@volar/language-core@~2.4.7":
version "2.4.12"
resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.12.tgz#98c8424f8d81a9cad1760a587b1c6db27d05f0cc"
integrity sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==
dependencies:
"@volar/source-map" "2.4.12"
"@volar/language-server@~2.4.7":
version "2.4.12"
resolved "https://registry.yarnpkg.com/@volar/language-server/-/language-server-2.4.12.tgz#71856dce6432aec906dbf3a242def9b1133a82bb"
integrity sha512-KC0YqTXCZMaImMWyAKC+dLB2BXjfz80kqesJkV6oXxJsGEQPfmdqug299idwtrT6FVSmZ7q5UrPfvgKwA0S3JA==
dependencies:
"@volar/language-core" "2.4.12"
"@volar/language-service" "2.4.12"
"@volar/typescript" "2.4.12"
path-browserify "^1.0.1"
request-light "^0.7.0"
vscode-languageserver "^9.0.1"
vscode-languageserver-protocol "^3.17.5"
vscode-languageserver-textdocument "^1.0.11"
vscode-uri "^3.0.8"
"@volar/language-service@2.4.12", "@volar/language-service@~2.4.7":
version "2.4.12"
resolved "https://registry.yarnpkg.com/@volar/language-service/-/language-service-2.4.12.tgz#b9064e40f638c364bec01c382abb00b6a69a6f97"
integrity sha512-nifOPGYYPnCmxja6/ML/Gl2EgFkUdw4gLbYqbh8FjqX3gSpXSZl/0ebqORjKo1KW56YWHWRZd1jFutEtCiRYhA==
dependencies:
"@volar/language-core" "2.4.12"
vscode-languageserver-protocol "^3.17.5"
vscode-languageserver-textdocument "^1.0.11"
vscode-uri "^3.0.8"
"@volar/source-map@2.4.12":
version "2.4.12"
resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.12.tgz#7cc8c6b1b134a2215f06c91ad011d94eef81b0ed"
integrity sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==
"@volar/typescript@2.4.12":
version "2.4.12"
resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.12.tgz#8c638c23cab89ab131cdcd2d6f2a51768caaa015"
integrity sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==
dependencies:
"@volar/language-core" "2.4.12"
path-browserify "^1.0.1"
vscode-uri "^3.0.8"
"@vscode/emmet-helper@^2.9.3":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz#7a53e4fdb17329cc2ed88036905c78d811d231d6"
integrity sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==
dependencies:
emmet "^2.4.3"
jsonc-parser "^2.3.0"
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "^3.15.1"
vscode-uri "^3.0.8"
"@vscode/l10n@^0.0.18":
version "0.0.18"
resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.18.tgz#916d3a5e960dbab47c1c56f58a7cb5087b135c95"
integrity sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@ -2009,6 +2168,16 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^8.11.0:
version "8.17.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6"
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
dependencies:
fast-deep-equal "^3.1.3"
fast-uri "^3.0.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
ansi-align@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
@ -2185,6 +2354,13 @@ astro-icon@^1.1.1:
"@iconify/types" "^2.0.0"
"@iconify/utils" "^2.1.30"
astro-seo@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/astro-seo/-/astro-seo-0.8.4.tgz#b4e156dca08ac46037168479f133cedcbb8b884b"
integrity sha512-Ou1vzQSXAxa0K8rtNtXNvSpYqOGEgMhh0immMxJeXmbVZac3UKCNWAoXWyOQDFYsZvBugCRSg0N1phBqPMVgCw==
dependencies:
"@astrojs/check" "^0.5.4"
astro@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/astro/-/astro-5.3.0.tgz#20a2772c019448f6797396f9dbabe72615877842"
@ -2295,15 +2471,6 @@ axios@^1.6.0, axios@^1.7.4, axios@^1.7.9:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axios@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.1.tgz#7c118d2146e9ebac512b7d1128771cdd738d11e3"
integrity sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
axobject-query@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
@ -2537,7 +2704,7 @@ cheerio@1.0.0:
undici "^6.19.5"
whatwg-mimetype "^4.0.0"
chokidar@^3.6.0:
chokidar@^3.5.3, chokidar@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@ -2807,6 +2974,11 @@ debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3
dependencies:
ms "^2.1.3"
decode-formdata@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/decode-formdata/-/decode-formdata-0.9.0.tgz#fa9c0c0ea0a279d6d1ea825c156534d2d5fa6721"
integrity sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==
decode-named-character-reference@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e"
@ -2989,6 +3161,14 @@ electron-to-chromium@^1.5.73:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz#81a452ace8e2c3fa7fba904ea4fed25052c53d3f"
integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==
emmet@^2.4.3:
version "2.4.11"
resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.4.11.tgz#b331f572df37a252360ebee7dc4462c8d2e32f5c"
integrity sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==
dependencies:
"@emmetio/abbreviation" "^2.3.3"
"@emmetio/css-abbreviation" "^2.1.8"
emoji-regex-xs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
@ -3510,7 +3690,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.3.2, fast-glob@^3.3.3:
fast-glob@^3.2.12, fast-glob@^3.3.1, fast-glob@^3.3.2, fast-glob@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
@ -3531,6 +3711,11 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-uri@^3.0.1:
version "3.0.6"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.6.tgz#88f130b77cfaea2378d56bf970dea21257a68748"
integrity sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==
fast-xml-parser@^4.5.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.2.tgz#f535f77b1396b859498203a2fd1994c4230a0ff9"
@ -4032,6 +4217,11 @@ ignore@^5.2.0, ignore@^5.3.1, ignore@^5.3.2:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
immer@^10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
import-fresh@^3.2.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf"
@ -4359,6 +4549,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@ -4369,6 +4564,16 @@ json5@2.2.3, json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonc-parser@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==
jsonc-parser@^3.0.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4"
integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==
jsonwebtoken@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
@ -4607,7 +4812,7 @@ lodash.once@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash@^4.17.21:
lodash@4.17.21, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -5186,6 +5391,11 @@ ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
muggle-string@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328"
integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==
murmurhash3js@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/murmurhash3js/-/murmurhash3js-3.0.1.tgz#3e983e5b47c2a06f43a713174e7e435ca044b998"
@ -5504,6 +5714,11 @@ parse5@^7.0.0, parse5@^7.1.2:
dependencies:
entities "^4.5.0"
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@ -5636,6 +5851,11 @@ prettier-config-standard@^7.0.0:
resolved "https://registry.yarnpkg.com/prettier-config-standard/-/prettier-config-standard-7.0.0.tgz#23fbc6f7240dd007b49d0d4c084ba58fd2ce57c2"
integrity sha512-NgZy4TYupJR6aMMuV/Aqs0ONnVhlFT8PXVkYRskxREq8EUhJHOddVfBxPV6fWpgcASpJSgvvhVLk0CBO5M3Hvw==
prettier@2.8.7:
version "2.8.7"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450"
integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==
prettier@^3.3.3:
version "3.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7"
@ -5750,6 +5970,11 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-multi-select-component@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/react-multi-select-component/-/react-multi-select-component-4.3.4.tgz#4f4b354bfa1f0353fa9c3bccf8178c87c9780450"
integrity sha512-Ui/bzCbROF4WfKq3OKWyQJHmy/bd1mW7CQM+L83TfiltuVvHElhKEyPM3JzO9urIcWplBUKv+kyxqmEnd9jPcA==
react-refresh@^0.14.2:
version "0.14.2"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
@ -5914,11 +6139,26 @@ remark-stringify@^11.0.0:
mdast-util-to-markdown "^2.0.0"
unified "^11.0.0"
request-light@^0.5.7:
version "0.5.8"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27"
integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==
request-light@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a"
integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -6082,7 +6322,7 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.8, semver@^7.5.4, semver@^7.6.0, semver@^7.6.3, semver@^7.7.1:
semver@^7.3.8, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2, semver@^7.6.3, semver@^7.7.1:
version "7.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
@ -6612,6 +6852,18 @@ typed-array-length@^1.0.7:
possible-typed-array-names "^1.0.0"
reflect.getprototypeof "^1.0.6"
typesafe-path@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/typesafe-path/-/typesafe-path-0.2.2.tgz#91a436681b2f514badb114061b6a5e5c2b8943b1"
integrity sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==
typescript-auto-import-cache@^0.3.3:
version "0.3.5"
resolved "https://registry.yarnpkg.com/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.5.tgz#402f98995037734ef3fc208180331adfd5e495fc"
integrity sha512-fAIveQKsoYj55CozUiBoj4b/7WpN0i4o74wiGY5JVUEoD0XiqDk1tJqTEjgzL2/AizKQrXxyRosSebyDzBZKjw==
dependencies:
semver "^7.3.8"
typescript-eslint@^8.15.0:
version "8.24.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.24.1.tgz#ce85d791392608a2a9f80c4b2530a214d16a2a47"
@ -6798,6 +7050,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
use-immer@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/use-immer/-/use-immer-0.11.0.tgz#8dd46cbc913cbc3298e60b8f2d9f58d82fc22e08"
integrity sha512-RNAqi3GqsWJ4bcCd4LMBgdzvPmTABam24DUaFiKfX9s3MSorNRz9RDZYJkllJoMHUxVLMDetwAuCDeyWNrp1yA==
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@ -6848,6 +7105,164 @@ vitefu@^1.0.5:
resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-1.0.5.tgz#eab501e07da167bbb68e957685823e6b425e7ce2"
integrity sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==
volar-service-css@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-css/-/volar-service-css-0.0.62.tgz#4866091bd217b548470f24706f53feba7a57345b"
integrity sha512-JwNyKsH3F8PuzZYuqPf+2e+4CTU8YoyUHEHVnoXNlrLe7wy9U3biomZ56llN69Ris7TTy/+DEX41yVxQpM4qvg==
dependencies:
vscode-css-languageservice "^6.3.0"
vscode-languageserver-textdocument "^1.0.11"
vscode-uri "^3.0.8"
volar-service-emmet@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-emmet/-/volar-service-emmet-0.0.62.tgz#451c60f73cb2c84c5ce2e4b70901de09c38920af"
integrity sha512-U4dxWDBWz7Pi4plpbXf4J4Z/ss6kBO3TYrACxWNsE29abu75QzVS0paxDDhI6bhqpbDFXlpsDhZ9aXVFpnfGRQ==
dependencies:
"@emmetio/css-parser" "^0.4.0"
"@emmetio/html-matcher" "^1.3.0"
"@vscode/emmet-helper" "^2.9.3"
vscode-uri "^3.0.8"
volar-service-html@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-html/-/volar-service-html-0.0.62.tgz#791c2b05f5e97bc4c35fac4dbae1cb57cc66570a"
integrity sha512-Zw01aJsZRh4GTGUjveyfEzEqpULQUdQH79KNEiKVYHZyuGtdBRYCHlrus1sueSNMxwwkuF5WnOHfvBzafs8yyQ==
dependencies:
vscode-html-languageservice "^5.3.0"
vscode-languageserver-textdocument "^1.0.11"
vscode-uri "^3.0.8"
volar-service-prettier@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-prettier/-/volar-service-prettier-0.0.62.tgz#aae89a26b27ad048f4452482888533ed7123f5c4"
integrity sha512-h2yk1RqRTE+vkYZaI9KYuwpDfOQRrTEMvoHol0yW4GFKc75wWQRrb5n/5abDrzMPrkQbSip8JH2AXbvrRtYh4w==
dependencies:
vscode-uri "^3.0.8"
volar-service-typescript-twoslash-queries@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-typescript-twoslash-queries/-/volar-service-typescript-twoslash-queries-0.0.62.tgz#9bf63fcf89688fae12f492168d3b447be3bdf385"
integrity sha512-KxFt4zydyJYYI0kFAcWPTh4u0Ha36TASPZkAnNY784GtgajerUqM80nX/W1d0wVhmcOFfAxkVsf/Ed+tiYU7ng==
dependencies:
vscode-uri "^3.0.8"
volar-service-typescript@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-typescript/-/volar-service-typescript-0.0.62.tgz#d99c42e2e08742f27b9bb186180dac93ce730ee6"
integrity sha512-p7MPi71q7KOsH0eAbZwPBiKPp9B2+qrdHAd6VY5oTo9BUXatsOAdakTm9Yf0DUj6uWBAaOT01BSeVOPwucMV1g==
dependencies:
path-browserify "^1.0.1"
semver "^7.6.2"
typescript-auto-import-cache "^0.3.3"
vscode-languageserver-textdocument "^1.0.11"
vscode-nls "^5.2.0"
vscode-uri "^3.0.8"
volar-service-yaml@0.0.62:
version "0.0.62"
resolved "https://registry.yarnpkg.com/volar-service-yaml/-/volar-service-yaml-0.0.62.tgz#143aaab83cae8c7c82f68502100d300ec687b59e"
integrity sha512-k7gvv7sk3wa+nGll3MaSKyjwQsJjIGCHFjVkl3wjaSP2nouKyn9aokGmqjrl39mi88Oy49giog2GkZH526wjig==
dependencies:
vscode-uri "^3.0.8"
yaml-language-server "~1.15.0"
vscode-css-languageservice@^6.3.0:
version "6.3.4"
resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.3.4.tgz#0b58d261441ced4667a1d5fe7f710af65bfaadf5"
integrity sha512-qutdhFg4hnlf6IsOynwtfsN8W0Xc7g3SZd+KK9F2moUEjHtkcZoj5p8uH7BSwHx9hSEXjwKgSRRyHTXThfwAkQ==
dependencies:
"@vscode/l10n" "^0.0.18"
vscode-languageserver-textdocument "^1.0.12"
vscode-languageserver-types "3.17.5"
vscode-uri "^3.1.0"
vscode-html-languageservice@^5.2.0, vscode-html-languageservice@^5.3.0:
version "5.3.3"
resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.3.3.tgz#42f7f22411a26b327ac4c4a5d26a0369fa7c529e"
integrity sha512-AK/jJM0VIWRrlfqkDBMZxNMnxYT5I2uoMVRoNJ5ePSplnSaT9mbYjqJlxxeLvUrOW7MEH0vVIDzU48u44QZE0w==
dependencies:
"@vscode/l10n" "^0.0.18"
vscode-languageserver-textdocument "^1.0.12"
vscode-languageserver-types "^3.17.5"
vscode-uri "^3.1.0"
vscode-json-languageservice@4.1.8:
version "4.1.8"
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz#397a39238d496e3e08a544a8b93df2cd13347d0c"
integrity sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==
dependencies:
jsonc-parser "^3.0.0"
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "^3.16.0"
vscode-nls "^5.0.0"
vscode-uri "^3.0.2"
vscode-jsonrpc@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e"
integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==
vscode-jsonrpc@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==
vscode-languageserver-protocol@3.16.0:
version "3.16.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821"
integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==
dependencies:
vscode-jsonrpc "6.0.0"
vscode-languageserver-types "3.16.0"
vscode-languageserver-protocol@3.17.5, vscode-languageserver-protocol@^3.17.5:
version "3.17.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea"
integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==
dependencies:
vscode-jsonrpc "8.2.0"
vscode-languageserver-types "3.17.5"
vscode-languageserver-textdocument@^1.0.1, vscode-languageserver-textdocument@^1.0.11, vscode-languageserver-textdocument@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz#457ee04271ab38998a093c68c2342f53f6e4a631"
integrity sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==
vscode-languageserver-types@3.16.0:
version "3.16.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
vscode-languageserver-types@3.17.5, vscode-languageserver-types@^3.15.1, vscode-languageserver-types@^3.16.0, vscode-languageserver-types@^3.17.5:
version "3.17.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
vscode-languageserver@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0"
integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==
dependencies:
vscode-languageserver-protocol "3.16.0"
vscode-languageserver@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz#500aef82097eb94df90d008678b0b6b5f474015b"
integrity sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==
dependencies:
vscode-languageserver-protocol "3.17.5"
vscode-nls@^5.0.0, vscode-nls@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.2.0.tgz#3cb6893dd9bd695244d8a024bdf746eea665cc3f"
integrity sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==
vscode-uri@^3.0.2, vscode-uri@^3.0.8, vscode-uri@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c"
integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==
web-namespaces@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
@ -6996,6 +7411,34 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml-language-server@~1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/yaml-language-server/-/yaml-language-server-1.15.0.tgz#3bd36f1f7fd74e63b591e5148df992c7327be05a"
integrity sha512-N47AqBDCMQmh6mBLmI6oqxryHRzi33aPFPsJhYy3VTUGCdLHYjGh4FZzpUjRlphaADBBkDmnkM/++KNIOHi5Rw==
dependencies:
ajv "^8.11.0"
lodash "4.17.21"
request-light "^0.5.7"
vscode-json-languageservice "4.1.8"
vscode-languageserver "^7.0.0"
vscode-languageserver-textdocument "^1.0.1"
vscode-languageserver-types "^3.16.0"
vscode-nls "^5.0.0"
vscode-uri "^3.0.2"
yaml "2.2.2"
optionalDependencies:
prettier "2.8.7"
yaml@2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073"
integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==
yaml@^2.5.0:
version "2.7.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.1.tgz#44a247d1b88523855679ac7fa7cda6ed7e135cf6"
integrity sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"