From 21bb5b0a64dca3ef9227aec474afbbe448ee4caa Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:09:45 -0400 Subject: [PATCH 01/42] Add cover image to video publishing --- src/pages/CreatePost/CreatePostBuilder.tsx | 138 +++++++++++++++++++-- src/pages/CreatePost/CreatePostMinimal.tsx | 136 ++++++++++++++++++-- 2 files changed, 248 insertions(+), 26 deletions(-) diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index 4791ce7..2f76b9c 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -10,7 +10,7 @@ import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' import ImageUploader from '../../components/common/ImageUploader' import AudiotrackIcon from '@mui/icons-material/Audiotrack' import DeleteIcon from '@mui/icons-material/Delete' -import { Button, Box, useTheme } from '@mui/material' +import { Button, Box, useTheme, Dialog, DialogTitle, DialogContent, Tabs, Tab, Typography } from '@mui/material' import { styled } from '@mui/system' import { Descendant } from 'slate' import EditIcon from '@mui/icons-material/Edit' @@ -129,6 +129,25 @@ export const CreatePostBuilder = ({ const [isOpenAddTextModal, setIsOpenAddTextModal] = React.useState(false) const [paddingValue, onChangePadding] = React.useState(5) + const [coverPickerOpen, setCoverPickerOpen] = React.useState(false) + const [coverTab, setCoverTab] = React.useState<'upload' | 'existing'>('upload') + const [pendingVideo, setPendingVideo] = React.useState(null) + const [selectedPoster, setSelectedPoster] = React.useState('') + const [existingImages, setExistingImages] = React.useState([]) + const qdnImageUrl = React.useCallback( + (name: string, identifier: string) => `/arbitrary/IMAGE/${name}/${identifier}`, + [] + ) + const fetchExistingImages = React.useCallback(async () => { + if (!user?.name) return + try { + const res = await fetch(`/arbitrary/resources?&service=IMAGE&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`) + const data = await res.json() + setExistingImages(Array.isArray(data) ? data : []) + } catch (e) { + setExistingImages([]) + } + }, [user]) const [isEditNavOpen, setIsEditNavOpen] = React.useState(false) const dispatch = useDispatch() const [navbarConfig, setNavbarConfig] = React.useState(null) @@ -723,6 +742,7 @@ export const CreatePostBuilder = ({ title: string description: string mimeType?: string + poster?: string } const addVideo = ({ @@ -730,7 +750,8 @@ export const CreatePostBuilder = ({ identifier, service, title, - description + description, + poster }: IaddVideo) => { const section = { type: 'video', @@ -740,7 +761,8 @@ export const CreatePostBuilder = ({ identifier: identifier, service: service, title, - description + description, + ...(poster ? { poster } : {}) }, id: uid() } @@ -823,7 +845,7 @@ export const CreatePostBuilder = ({ } } const editVideo = ( - { name, identifier, service, description, title }: IaddVideo, + { name, identifier, service, description, title, poster }: IaddVideo, section: any ) => { const newSection = { @@ -833,7 +855,8 @@ export const CreatePostBuilder = ({ identifier: identifier, service: service, description, - title + title, + poster: poster ?? section.content?.poster } } const findSectionIndex = newPostContent.findIndex( @@ -897,14 +920,12 @@ export const CreatePostBuilder = ({ ) const onSelectVideo = React.useCallback((video: any) => { - addVideo({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) + setPendingVideo(video) + setSelectedPoster('') + setCoverTab('upload') + setCoverPickerOpen(true) + fetchExistingImages() + }, [fetchExistingImages]) const onSelectAudio = React.useCallback((video: any) => { addAudio({ @@ -1156,6 +1177,7 @@ export const CreatePostBuilder = ({ name={section.content.name} service={section.content.service} identifier={section.content.identifier} + poster={section.content.poster} from="create" /> @@ -1183,6 +1205,21 @@ export const CreatePostBuilder = ({ ) } /> + { + setPendingVideo({ + name: section.content.name, + identifier: section.content.identifier, + service: section.content.service, + metadata: { title: section.content.title, description: section.content.description } + }) + setSelectedPoster(section.content?.poster || '') + setCoverTab('upload') + setCoverPickerOpen(true) + fetchExistingImages() + }} + sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} + /> @@ -1380,6 +1417,81 @@ export const CreatePostBuilder = ({ /> )} + setCoverPickerOpen(false)} maxWidth="sm" fullWidth> + Select a cover image + + setCoverTab(v)}> + + + + + {coverTab === 'upload' && ( + + setSelectedPoster(base64)}> + + + {selectedPoster && ( + + + + )} + + )} + + {coverTab === 'existing' && ( + + {existingImages.map((img: any) => { + const url = qdnImageUrl(img.name, img.identifier) + const isActive = selectedPoster === url + return ( + setSelectedPoster(url)} + > + + + {img?.metadata?.title || img.identifier} + + + ) + })} + {!existingImages.length && ( + No images found. + )} + + )} + + + + + + + ) } diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index d180b6d..0ac2487 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -9,7 +9,7 @@ import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' import ImageUploader from '../../components/common/ImageUploader' import AudiotrackIcon from '@mui/icons-material/Audiotrack' import AddCircleRoundedIcon from '@mui/icons-material/AddCircleRounded' -import { Button, Box, useTheme } from '@mui/material' +import { Button, Box, useTheme, Dialog, DialogTitle, DialogContent, Tabs, Tab, Typography } from '@mui/material' import { styled } from '@mui/system' import { Descendant } from 'slate' import EditIcon from '@mui/icons-material/Edit' @@ -128,7 +128,26 @@ export const CreatePostMinimal = ({ React.useState(false) const [paddingValue, onChangePadding] = React.useState(5) + const [coverPickerOpen, setCoverPickerOpen] = React.useState(false) + const [coverTab, setCoverTab] = React.useState<'upload' | 'existing'>('upload') + const [pendingVideo, setPendingVideo] = React.useState(null) + const [selectedPoster, setSelectedPoster] = React.useState('') + const [existingImages, setExistingImages] = React.useState([]) const dispatch = useDispatch() + const qdnImageUrl = React.useCallback( + (name: string, identifier: string) => `/arbitrary/IMAGE/${name}/${identifier}`, + [] + ) + const fetchExistingImages = React.useCallback(async () => { + if (!user?.name) return + try { + const res = await fetch(`/arbitrary/resources?&service=IMAGE&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`) + const data = await res.json() + setExistingImages(Array.isArray(data) ? data : []) + } catch (e) { + setExistingImages([]) + } + }, [user]) const addPostSection = React.useCallback((content: any) => { const id = uid() const type = 'editor' @@ -606,13 +625,15 @@ export const CreatePostMinimal = ({ title: string description: string mimeType?: string + poster?: string } const addVideo = ({ name, identifier, service, title, - description + description, + poster }: IaddVideo) => { const id = uid() const type = 'video' @@ -624,7 +645,8 @@ export const CreatePostMinimal = ({ identifier: identifier, service: service, title, - description + description, + ...(poster ? { poster } : {}) }, id } @@ -767,7 +789,7 @@ export const CreatePostMinimal = ({ } } const editVideo = ( - { name, identifier, service, description, title }: IaddVideo, + { name, identifier, service, description, title, poster }: IaddVideo, section: any ) => { const newSection = { @@ -777,7 +799,8 @@ export const CreatePostMinimal = ({ identifier: identifier, service: service, description, - title + title, + poster: poster ?? section.content?.poster } } const findSectionIndex = newPostContent.findIndex( @@ -845,14 +868,12 @@ export const CreatePostMinimal = ({ ) const onSelectVideo = React.useCallback((video: any) => { - addVideo({ - name: video.name, - identifier: video.identifier, - service: video.service, - title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) + setPendingVideo(video) + setSelectedPoster('') + setCoverTab('upload') + setCoverPickerOpen(true) + fetchExistingImages() + }, [fetchExistingImages]) const onSelectAudio = React.useCallback((video: any) => { addAudio({ @@ -1149,6 +1170,7 @@ export const CreatePostMinimal = ({ name={section.content.name} service={section.content.service} identifier={section.content.identifier} + poster={section.content.poster} from="create" customStyle={{ height: '50vh' @@ -1179,6 +1201,21 @@ export const CreatePostMinimal = ({ ) } /> + { + setPendingVideo({ + name: section.content.name, + identifier: section.content.identifier, + service: section.content.service, + metadata: { title: section.content.title, description: section.content.description } + }) + setSelectedPoster(section.content?.poster || '') + setCoverTab('upload') + setCoverPickerOpen(true) + fetchExistingImages() + }} + sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} + /> @@ -1385,6 +1422,79 @@ export const CreatePostMinimal = ({ /> )} + setCoverPickerOpen(false)} maxWidth="sm" fullWidth> + Select a cover image + + setCoverTab(v)}> + + + + {coverTab === 'upload' ? ( + + + Pick an image file. It will be embedded in this post as a data URL. + + setSelectedPoster(base64)}> + + + {selectedPoster && ( + + + + )} + + ) : ( + + {existingImages.map((img: any) => { + const url = qdnImageUrl(img.name, img.identifier) + const isSelected = selectedPoster === url + return ( + setSelectedPoster(url)} + sx={{ + border: isSelected ? '2px solid #1976d2' : '1px solid rgba(0,0,0,0.2)', + borderRadius: 1, + p: 0.5, + cursor: 'pointer' + }} + > + + + {img?.metadata?.title || img.identifier} + + + ) + })} + {!existingImages.length && No published images found.} + + )} + + + + + + ) } -- 2.43.0 From 42f4097402d3f4509b8bc38d310ab923dd2bc018 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:09:55 -0400 Subject: [PATCH 02/42] Display cover image --- src/components/common/VideoPlayer.tsx | 2 +- src/pages/BlogIndividualPost/BlogIndividualPost.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/common/VideoPlayer.tsx b/src/components/common/VideoPlayer.tsx index 64bf633..28e8a68 100644 --- a/src/components/common/VideoPlayer.tsx +++ b/src/components/common/VideoPlayer.tsx @@ -622,7 +622,7 @@ setIsMuted(false) justifyContent="center" alignItems="center" zIndex={500} - bgcolor="rgba(0, 0, 0, 0.6)" + bgcolor={poster ? 'transparent' : 'rgba(0, 0, 0, 0.6)'} onClick={() => { if (from === 'create') return diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index 293b369..aff4673 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -489,6 +489,7 @@ export const BlogIndividualPost = () => { name={section.content.name} service={section.content.service} identifier={section.content.identifier} + poster={section.content.poster} setCount={handleCount} user={user} postId={fullPostId} @@ -754,6 +755,7 @@ export const BlogIndividualPost = () => { name={section.content.name} service={section.content.service} identifier={section.content.identifier} + poster={section.content.poster} customStyle={{ height: '50vh' }} -- 2.43.0 From 913fe6b75a279bd5cc2f43b487ecc8fbff1c16fa Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:04 -0400 Subject: [PATCH 03/42] Edit existing video cover image --- src/pages/CreatePost/CreatePostBuilder.tsx | 50 ++++++++++++------- src/pages/CreatePost/CreatePostMinimal.tsx | 57 ++++++++++++++++++---- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index 2f76b9c..c112a83 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -134,6 +134,8 @@ export const CreatePostBuilder = ({ const [pendingVideo, setPendingVideo] = React.useState(null) const [selectedPoster, setSelectedPoster] = React.useState('') const [existingImages, setExistingImages] = React.useState([]) + const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add') + const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) const qdnImageUrl = React.useCallback( (name: string, identifier: string) => `/arbitrary/IMAGE/${name}/${identifier}`, [] @@ -920,6 +922,8 @@ export const CreatePostBuilder = ({ ) const onSelectVideo = React.useCallback((video: any) => { + setCoverPickerMode('add') + setCoverPickerTarget(null) setPendingVideo(video) setSelectedPoster('') setCoverTab('upload') @@ -1207,12 +1211,9 @@ export const CreatePostBuilder = ({ /> { - setPendingVideo({ - name: section.content.name, - identifier: section.content.identifier, - service: section.content.service, - metadata: { title: section.content.title, description: section.content.description } - }) + setCoverPickerMode('edit') + setCoverPickerTarget(section) + setPendingVideo(null) setSelectedPoster(section.content?.poster || '') setCoverTab('upload') setCoverPickerOpen(true) @@ -1471,23 +1472,38 @@ export const CreatePostBuilder = ({ diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index 0ac2487..b76675a 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -133,6 +133,8 @@ export const CreatePostMinimal = ({ const [pendingVideo, setPendingVideo] = React.useState(null) const [selectedPoster, setSelectedPoster] = React.useState('') const [existingImages, setExistingImages] = React.useState([]) + const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add') + const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) const dispatch = useDispatch() const qdnImageUrl = React.useCallback( (name: string, identifier: string) => `/arbitrary/IMAGE/${name}/${identifier}`, @@ -868,6 +870,8 @@ export const CreatePostMinimal = ({ ) const onSelectVideo = React.useCallback((video: any) => { + setCoverPickerMode('add') + setCoverPickerTarget(null) setPendingVideo(video) setSelectedPoster('') setCoverTab('upload') @@ -1203,12 +1207,9 @@ export const CreatePostMinimal = ({ /> { - setPendingVideo({ - name: section.content.name, - identifier: section.content.identifier, - service: section.content.service, - metadata: { title: section.content.title, description: section.content.description } - }) + setCoverPickerMode('edit') + setCoverPickerTarget(section) + setPendingVideo(null) setSelectedPoster(section.content?.poster || '') setCoverTab('upload') setCoverPickerOpen(true) @@ -1473,10 +1474,10 @@ export const CreatePostMinimal = ({ + -- 2.43.0 From fbe2f086f3a9b1564b0ef5c500b50805bea4a817 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:12 -0400 Subject: [PATCH 04/42] Fix button to clear cover image --- src/pages/CreatePost/CreatePostBuilder.tsx | 46 ++++++++++++--- src/pages/CreatePost/CreatePostMinimal.tsx | 69 ++++++++++++---------- 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index c112a83..d6347bd 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -850,16 +850,23 @@ export const CreatePostBuilder = ({ { name, identifier, service, description, title, poster }: IaddVideo, section: any ) => { + const nextContent: any = { + name, + identifier, + service, + description, + title + } + if (poster === undefined) { + if (typeof section.content?.poster !== 'undefined') { + nextContent.poster = section.content.poster + } + } else if (poster) { + nextContent.poster = poster + } const newSection = { ...section, - content: { - name: name, - identifier: identifier, - service: service, - description, - title, - poster: poster ?? section.content?.poster - } + content: nextContent } const findSectionIndex = newPostContent.findIndex( (s) => s.id === section.id @@ -1470,6 +1477,29 @@ export const CreatePostBuilder = ({ + + - -- 2.43.0 From 8563bbe8eb544320596b2423b4cbce54aa99973b Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:20 -0400 Subject: [PATCH 05/42] Add services to published images --- src/pages/CreatePost/CreatePostBuilder.tsx | 26 ++++++++++++++++------ src/pages/CreatePost/CreatePostMinimal.tsx | 26 ++++++++++++++++------ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index d6347bd..47ff73b 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -136,16 +136,27 @@ export const CreatePostBuilder = ({ const [existingImages, setExistingImages] = React.useState([]) const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add') const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) - const qdnImageUrl = React.useCallback( - (name: string, identifier: string) => `/arbitrary/IMAGE/${name}/${identifier}`, + const qdnResourceUrl = React.useCallback( + (service: string, name: string, identifier: string) => `/arbitrary/${service}/${name}/${identifier}`, [] ) const fetchExistingImages = React.useCallback(async () => { if (!user?.name) return try { - const res = await fetch(`/arbitrary/resources?&service=IMAGE&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`) - const data = await res.json() - setExistingImages(Array.isArray(data) ? data : []) + const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE'] + const lists = await Promise.all( + SERVICES.map(async (svc) => { + const res = await fetch( + `/arbitrary/resources?service=${svc}&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true` + ) + const data = await res.json() + return Array.isArray(data) + ? data.map((it: any) => ({ ...it, service: it.service || svc })) + : [] + }) + ) + const merged = lists.flat() + setExistingImages(merged) } catch (e) { setExistingImages([]) } @@ -1449,11 +1460,12 @@ export const CreatePostBuilder = ({ {coverTab === 'existing' && ( {existingImages.map((img: any) => { - const url = qdnImageUrl(img.name, img.identifier) + const service = img.service || 'IMAGE' + const url = qdnResourceUrl(service, img.name, img.identifier) const isActive = selectedPoster === url return ( ('add') const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) const dispatch = useDispatch() - const qdnImageUrl = React.useCallback( - (name: string, identifier: string) => `/arbitrary/IMAGE/${name}/${identifier}`, + const qdnResourceUrl = React.useCallback( + (service: string, name: string, identifier: string) => `/arbitrary/${service}/${name}/${identifier}`, [] ) const fetchExistingImages = React.useCallback(async () => { if (!user?.name) return try { - const res = await fetch(`/arbitrary/resources?&service=IMAGE&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`) - const data = await res.json() - setExistingImages(Array.isArray(data) ? data : []) + const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE'] + const lists = await Promise.all( + SERVICES.map(async (svc) => { + const res = await fetch( + `/arbitrary/resources?service=${svc}&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true` + ) + const data = await res.json() + return Array.isArray(data) + ? data.map((it: any) => ({ ...it, service: it.service || svc })) + : [] + }) + ) + const merged = lists.flat() + setExistingImages(merged) } catch (e) { setExistingImages([]) } @@ -1454,11 +1465,12 @@ export const CreatePostMinimal = ({ ) : ( {existingImages.map((img: any) => { - const url = qdnImageUrl(img.name, img.identifier) + const service = img.service || 'IMAGE' + const url = qdnResourceUrl(service, img.name, img.identifier) const isSelected = selectedPoster === url return ( setSelectedPoster(url)} sx={{ border: isSelected ? '2px solid #1976d2' : '1px solid rgba(0,0,0,0.2)', -- 2.43.0 From e86a812bee10e59b904ae9303add40b43d3afaed Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:29 -0400 Subject: [PATCH 06/42] Add loading msg to published images --- src/pages/CreatePost/CreatePostBuilder.tsx | 11 +++++++++-- src/pages/CreatePost/CreatePostMinimal.tsx | 11 ++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index 47ff73b..347a172 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -134,6 +134,7 @@ export const CreatePostBuilder = ({ const [pendingVideo, setPendingVideo] = React.useState(null) const [selectedPoster, setSelectedPoster] = React.useState('') const [existingImages, setExistingImages] = React.useState([]) + const [existingImagesLoading, setExistingImagesLoading] = React.useState(false) const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add') const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) const qdnResourceUrl = React.useCallback( @@ -142,6 +143,7 @@ export const CreatePostBuilder = ({ ) const fetchExistingImages = React.useCallback(async () => { if (!user?.name) return + setExistingImagesLoading(true) try { const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE'] const lists = await Promise.all( @@ -159,6 +161,8 @@ export const CreatePostBuilder = ({ setExistingImages(merged) } catch (e) { setExistingImages([]) + } finally { + setExistingImagesLoading(false) } }, [user]) const [isEditNavOpen, setIsEditNavOpen] = React.useState(false) @@ -1481,8 +1485,11 @@ export const CreatePostBuilder = ({ ) })} - {!existingImages.length && ( - No images found. + {existingImagesLoading && ( + Loading... + )} + {!existingImagesLoading && !existingImages.length && ( + No published images found. )} )} diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index 4d76e90..06be06e 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -133,6 +133,7 @@ export const CreatePostMinimal = ({ const [pendingVideo, setPendingVideo] = React.useState(null) const [selectedPoster, setSelectedPoster] = React.useState('') const [existingImages, setExistingImages] = React.useState([]) + const [existingImagesLoading, setExistingImagesLoading] = React.useState(false) const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add') const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) const dispatch = useDispatch() @@ -142,6 +143,7 @@ export const CreatePostMinimal = ({ ) const fetchExistingImages = React.useCallback(async () => { if (!user?.name) return + setExistingImagesLoading(true) try { const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE'] const lists = await Promise.all( @@ -159,6 +161,8 @@ export const CreatePostMinimal = ({ setExistingImages(merged) } catch (e) { setExistingImages([]) + } finally { + setExistingImagesLoading(false) } }, [user]) const addPostSection = React.useCallback((content: any) => { @@ -1486,7 +1490,12 @@ export const CreatePostMinimal = ({ ) })} - {!existingImages.length && No published images found.} + {existingImagesLoading && ( + Loading... + )} + {!existingImagesLoading && !existingImages.length && ( + No published images found. + )} )} -- 2.43.0 From f8593cc73114387b55db6180d166f8708722e626 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:37 -0400 Subject: [PATCH 07/42] Update app link --- src/pages/BlogIndividualPost/BlogIndividualPost.tsx | 2 +- src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx | 2 +- src/pages/BlogList/BlogList.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index aff4673..3bf1747 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -383,7 +383,7 @@ export const BlogIndividualPost = () => { }} > { dispatch( setNotification({ diff --git a/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx b/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx index a551e4c..969962b 100644 --- a/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx +++ b/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx @@ -263,7 +263,7 @@ export const BlogIndividualProfile = () => { name={blogPost.user} service="BLOG_POST" identifier={blogPost.id} - link={`qortal://APP/Q-Blog/${blogPost.user}/${blogId}/${str2}`} + link={`qortal://APP/qblog/${blogPost.user}/${blogId}/${str2}`} > { diff --git a/src/pages/BlogList/BlogList.tsx b/src/pages/BlogList/BlogList.tsx index 02d1bef..dbf15ba 100644 --- a/src/pages/BlogList/BlogList.tsx +++ b/src/pages/BlogList/BlogList.tsx @@ -183,7 +183,7 @@ export const BlogList = ({ mode }: BlogListProps) => { name={blogPost.user} service="BLOG_POST" identifier={blogPost.id} - link={`qortal://APP/Q-Blog/${blogPost.user}/${blogId}/${str2}`} + link={`qortal://APP/qblog/${blogPost.user}/${blogId}/${str2}`} > { -- 2.43.0 From abedd8171b0d6ac989305d6371679bdf08815def Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:46 -0400 Subject: [PATCH 08/42] Add list view --- src/components/layout/Navbar/Navbar.tsx | 43 +++- src/index.css | 23 ++ .../BlogIndividualProfile.tsx | 210 ++++++++++++------ src/pages/BlogList/BlogList.tsx | 210 ++++++++++++------ src/pages/BlogList/PostPreview.tsx | 5 +- 5 files changed, 341 insertions(+), 150 deletions(-) diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index e595aa8..65b8bc6 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -107,16 +107,10 @@ const NavBar: React.FC = ({ const searchValRef = useRef('') const inputRef = useRef(null) const stripBlogId = removePrefix(visitingBlog?.blogId || '') - if (visitingBlog?.navbarConfig && location?.pathname?.includes(stripBlogId)) { - return ( - - ) - } + const showUserNav = + visitingBlog?.navbarConfig && + stripBlogId && + location.pathname.includes(stripBlogId) const handleClick = (event: React.MouseEvent) => { const target = event.currentTarget as unknown as HTMLButtonElement | null @@ -141,7 +135,26 @@ const NavBar: React.FC = ({ const openPopover = Boolean(anchorElNotification) const idNotification = openPopover ? 'simple-popover-notification' : undefined - return ( + const [viewMode, setViewMode] = useState<'tile' | 'list'>(() => { + const saved = localStorage.getItem('qblog_view') + return saved === 'list' ? 'list' : 'tile' + }) + + const toggleViewMode = () => { + const next = viewMode === 'tile' ? 'list' : 'tile' + setViewMode(next) + localStorage.setItem('qblog_view', next) + window.dispatchEvent(new CustomEvent('qblog:viewmode', { detail: next })) + } + + return showUserNav ? ( + + ) : ( = ({ inputRef.current.value = '' }} /> + { } } catch (error) {} } + + const [isListView, setIsListView] = React.useState( + () => (localStorage.getItem('qblog_view') || 'tile') === 'list' + ) + + React.useEffect(() => { + const onViewMode = (e: Event) => { + const ce = e as CustomEvent + setIsListView(ce.detail === 'list') + } + window.addEventListener('qblog:viewmode', onViewMode as EventListener) + return () => window.removeEventListener('qblog:viewmode', onViewMode as EventListener) + }, []) + if (!userBlog) return null return ( <> @@ -228,75 +242,133 @@ export const BlogIndividualProfile = () => { )} - - {blogPosts.map((post, index) => { - let existingPost = hashMapPosts[post.id + "-" + post.user] - let blogPost = post - if (existingPost) { - blogPost = existingPost - } - const str = blogPost.id - const arr = str.split('-post-') - const str1 = arr[0] - - const blogId = removePrefix(str1) - const str2 = arr[1] - return ( - - + {blogPosts.map((post, index) => { + const existingPost = hashMapPosts[post.id + '-' + post.user] + const blogPost = existingPost ? existingPost : post + const str = blogPost.id + const arr = str.split('-post-') + const str1 = arr[0] + const blogId = removePrefix(str1) + const str2 = arr[1] + return ( + - { - navigate(`/${blogPost.user}/${blogId}/${str2}`) - }} - description={blogPost?.description} - title={blogPost?.title} - createdAt={blogPost?.createdAt} - author={blogPost.user} - postImage={blogPost?.postImage} - blogPost={blogPost} - tags={blogPost?.tags} - /> - - {blogPost.user === user?.name && ( - { - navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) - }} - /> - )} - - ) - })} - + + navigate(`/${blogPost.user}/${blogId}/${str2}`)} + description={blogPost?.description} + title={blogPost?.title} + createdAt={blogPost?.createdAt} + author={blogPost.user} + postImage={blogPost?.postImage} + blogPost={blogPost} + tags={blogPost?.tags} + fullWidth + /> + + {blogPost.user === user?.name && ( + { + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + }} + /> + )} + + ) + })} + + ) : ( + + {blogPosts.map((post, index) => { + const existingPost = hashMapPosts[post.id + '-' + post.user] + const blogPost = existingPost ? existingPost : post + const str = blogPost.id + const arr = str.split('-post-') + const str1 = arr[0] + const blogId = removePrefix(str1) + const str2 = arr[1] + return ( + + + { + navigate(`/${blogPost.user}/${blogId}/${str2}`) + }} + description={blogPost?.description} + title={blogPost?.title} + createdAt={blogPost?.createdAt} + author={blogPost.user} + postImage={blogPost?.postImage} + blogPost={blogPost} + tags={blogPost?.tags} + /> + + {blogPost.user === user?.name && ( + { + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + }} + /> + )} + + ) + })} + + )} ) diff --git a/src/pages/BlogList/BlogList.tsx b/src/pages/BlogList/BlogList.tsx index dbf15ba..4c6827c 100644 --- a/src/pages/BlogList/BlogList.tsx +++ b/src/pages/BlogList/BlogList.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useEffect, useRef } from 'react' +import React, { FC, useCallback, useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' import { RootState } from '../../state/store' @@ -79,6 +79,19 @@ export const BlogList = ({ mode }: BlogListProps) => { await getBlogPosts() }, [getBlogPosts, mode, favoritesLocal, user?.name, isFiltering, filterValue]) + const [isListView, setIsListView] = useState( + () => (localStorage.getItem('qblog_view') || 'tile') === 'list' + ) + + useEffect(() => { + const onViewMode = (e: Event) => { + const ce = e as CustomEvent + setIsListView(ce.detail === 'list') + } + window.addEventListener('qblog:viewmode', onViewMode as EventListener) + return () => window.removeEventListener('qblog:viewmode', onViewMode as EventListener) + }, []) + let posts = globalPosts if (mode === 'favorites') { @@ -149,75 +162,134 @@ export const BlogList = ({ mode }: BlogListProps) => { )} - - {posts.map((post, index) => { - const existingPost = hashMapPosts[post.id + "-" + post.user] - let blogPost = post - if (existingPost) { - blogPost = existingPost - } - const str = blogPost.id - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) - return ( - - + {posts.map((post, index) => { + const existingPost = hashMapPosts[post.id + '-' + post.user] + let blogPost = existingPost ? existingPost : post + const str = blogPost.id + const arr = str.split('-post-') + const str1 = arr[0] + const str2 = arr[1] + const blogId = removePrefix(str1) + return ( + - { - navigate(`/${blogPost.user}/${blogId}/${str2}`) - }} - description={blogPost?.description} - title={blogPost?.title} - createdAt={blogPost?.createdAt} - author={blogPost.user} - postImage={blogPost?.postImage} - blogPost={blogPost} - isValid={blogPost?.isValid} - tags={blogPost?.tags} - /> - - {blogPost.user === user?.name && ( - { - navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) - }} - /> - )} - - ) - })} - + + navigate(`/${blogPost.user}/${blogId}/${str2}`)} + description={blogPost?.description} + title={blogPost?.title} + createdAt={blogPost?.createdAt} + author={blogPost.user} + postImage={blogPost?.postImage} + blogPost={blogPost} + isValid={blogPost?.isValid} + tags={blogPost?.tags} + fullWidth + /> + + {blogPost.user === user?.name && ( + { + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + }} + /> + )} + + ) + })} + + ) : ( + + {posts.map((post, index) => { + const existingPost = hashMapPosts[post.id + '-' + post.user] + let blogPost = existingPost ? existingPost : post + const str = blogPost.id + const arr = str.split('-post-') + const str1 = arr[0] + const str2 = arr[1] + const blogId = removePrefix(str1) + return ( + + + { + navigate(`/${blogPost.user}/${blogId}/${str2}`) + }} + description={blogPost?.description} + title={blogPost?.title} + createdAt={blogPost?.createdAt} + author={blogPost.user} + postImage={blogPost?.postImage} + blogPost={blogPost} + isValid={blogPost?.isValid} + tags={blogPost?.tags} + /> + + {blogPost.user === user?.name && ( + { + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + }} + /> + )} + + ) + })} + + )} {/* */} diff --git a/src/pages/BlogList/PostPreview.tsx b/src/pages/BlogList/PostPreview.tsx index 46ec146..71668f6 100644 --- a/src/pages/BlogList/PostPreview.tsx +++ b/src/pages/BlogList/PostPreview.tsx @@ -56,6 +56,7 @@ interface BlogPostPreviewProps { onClick?: () => void isValid?: boolean tags?: string[] + fullWidth?: boolean } const BlogPostPreview: React.FC = ({ @@ -67,7 +68,8 @@ const BlogPostPreview: React.FC = ({ onClick, blogPost, isValid, - tags + tags, + fullWidth = false }) => { const [avatarUrl, setAvatarUrl] = React.useState('') const [showIcons, setShowIcons] = React.useState(false) @@ -179,6 +181,7 @@ const BlogPostPreview: React.FC = ({ onClick={continueToPost} onMouseEnter={() => setShowIcons(true)} onMouseLeave={() => setShowIcons(false)} + sx={fullWidth ? { width: '100%', maxWidth: 'none' } : undefined} > {/* {postImage && ( -- 2.43.0 From 9eea8ada526e7112fb990b0975dde922900fad05 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Mon, 4 Aug 2025 18:10:55 -0400 Subject: [PATCH 09/42] Fix images in list view --- src/index.css | 3 + src/pages/BlogList/PostPreview.tsx | 176 ++++++++++++++++++++--------- 2 files changed, 125 insertions(+), 54 deletions(-) diff --git a/src/index.css b/src/index.css index da9dca8..fe65fcf 100644 --- a/src/index.css +++ b/src/index.css @@ -176,6 +176,9 @@ a:focus { width: 100%; max-width: none; } +.list-view-item .MuiCard-root .list-image-col { + max-width: 40%; +} @media (max-width: 450px) { .list-view-container { diff --git a/src/pages/BlogList/PostPreview.tsx b/src/pages/BlogList/PostPreview.tsx index 71668f6..69664c3 100644 --- a/src/pages/BlogList/PostPreview.tsx +++ b/src/pages/BlogList/PostPreview.tsx @@ -183,66 +183,135 @@ const BlogPostPreview: React.FC = ({ onMouseLeave={() => setShowIcons(false)} sx={fullWidth ? { width: '100%', maxWidth: 'none' } : undefined} > - - {/* {postImage && ( - - - - )} */} - - + )} + {fullWidth ? ( + - - - - - + - {title} - - + + + + + {title} + + + {author} + + + + + + {description} + + + + {formatDate(+createdAt)} + + + + + {postImage && ( + - {author} - - - - - + + )} + + ) : ( + + - {description} - - - - {formatDate(+createdAt)} + + + + + + {title} + + + {author} + + + + + + {description} - - - + + + {formatDate(+createdAt)} + + + + + )} = ({ - Date: Mon, 4 Aug 2025 18:11:02 -0400 Subject: [PATCH 10/42] Styled view toggle button --- src/components/layout/Navbar/Navbar.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 65b8bc6..1ffcb1b 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -257,7 +257,12 @@ const NavBar: React.FC = ({ + + + + + + ) +} diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index 347a172..878bba9 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -8,6 +8,7 @@ import { RootState } from '../../state/store' import TextField from '@mui/material/TextField' import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' import ImageUploader from '../../components/common/ImageUploader' +import { ImagePanel } from '../../components/common/ImagePanel' import AudiotrackIcon from '@mui/icons-material/Audiotrack' import DeleteIcon from '@mui/icons-material/Delete' import { Button, Box, useTheme, Dialog, DialogTitle, DialogContent, Tabs, Tab, Typography } from '@mui/material' @@ -1163,17 +1164,11 @@ export const CreatePostBuilder = ({ width: 'auto' }} /> - editImage(base64, section)} - > - - + editImage(src, section)} + /> diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index 06be06e..781201c 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -7,6 +7,7 @@ import { RootState } from '../../state/store' import TextField from '@mui/material/TextField' import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' import ImageUploader from '../../components/common/ImageUploader' +import { ImagePanel } from '../../components/common/ImagePanel' import AudiotrackIcon from '@mui/icons-material/Audiotrack' import AddCircleRoundedIcon from '@mui/icons-material/AddCircleRounded' import { Button, Box, useTheme, Dialog, DialogTitle, DialogContent, Tabs, Tab, Typography } from '@mui/material' @@ -1043,13 +1044,13 @@ export const CreatePostMinimal = ({ marginRight: '20px' }} > - - addImageToRow(row, rowIndex, 'left', base64) + + addImageToRow(row, rowIndex, 'left', src) } - > - - + /> )} {row.ids.map((elementId: string) => { @@ -1156,17 +1157,11 @@ export const CreatePostMinimal = ({ width: 'auto' }} /> - editImage(base64, section)} - > - - + editImage(src, section)} + /> @@ -1351,13 +1346,13 @@ export const CreatePostMinimal = ({ marginLeft: '20px' }} > - - addImageToRow(row, rowIndex, 'right', base64) + + addImageToRow(row, rowIndex, 'right', src) } - > - - + /> )} diff --git a/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx b/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx index 6a6de44..0aae556 100644 --- a/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx +++ b/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx @@ -4,8 +4,7 @@ import Slider from '@mui/material/Slider' import { AudioPanel } from '../../../../components/common/AudioPanel' import { Box, Toolbar, AppBar, useTheme } from '@mui/material' import { styled } from '@mui/system' -import ImageUploader from '../../../../components/common/ImageUploader' -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' +import { ImagePanel } from '../../../../components/common/ImagePanel' import { VideoPanel } from '../../../../components/common/VideoPanel' import MenuOpenIcon from '@mui/icons-material/MenuOpen' import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded' @@ -80,17 +79,7 @@ export const EditorToolbar = ({ /> - - - - - + diff --git a/src/pages/EditPost/EditPost.tsx b/src/pages/EditPost/EditPost.tsx index 2e79b2a..ba7a173 100644 --- a/src/pages/EditPost/EditPost.tsx +++ b/src/pages/EditPost/EditPost.tsx @@ -7,8 +7,7 @@ import ReadOnlySlate from '../../components/editor/ReadOnlySlate' import { useDispatch, useSelector } from 'react-redux' import { RootState } from '../../state/store' import { Box } from '@mui/material' -import ImageUploader from '../../components/common/ImageUploader' -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' +import { ImagePanel } from '../../components/common/ImagePanel' import { checkStructure } from '../../utils/checkStructure' import { BlogContent } from '../../interfaces/interfaces' import PostAddIcon from '@mui/icons-material/PostAdd' @@ -375,12 +374,11 @@ export const EditPost = () => { return ( {editingSection && editingSection.id === section.id ? ( - editImage(base64, section)} - > - Add Image - - + editImage(src, section)} + /> ) : ( { cursor: 'pointer' }} /> - editImage(base64, section)} - > - - + editImage(src, section)} + /> )} @@ -533,15 +527,11 @@ export const EditPost = () => { height: '50px' }} /> - - - + Date: Mon, 4 Aug 2025 18:11:29 -0400 Subject: [PATCH 13/42] Fix button text and color --- .../BlogIndividualPost/BlogIndividualPost.tsx | 2 +- src/pages/CreatePost/CreatePostBuilder.tsx | 10 ++++++---- src/pages/CreatePost/CreatePostMinimal.tsx | 15 +++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index 3bf1747..eaef52c 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -898,7 +898,7 @@ export const BlogIndividualPost = () => { + {selectedPoster && ( @@ -1490,8 +1490,10 @@ export const CreatePostBuilder = ({ )} - + + {selectedPoster && ( @@ -1494,8 +1491,10 @@ export const CreatePostMinimal = ({ )} - + + )} + + ) + } + if (!userBlog) return null return ( <> diff --git a/src/utils/qortalRequestFunctions.ts b/src/utils/qortalRequestFunctions.ts new file mode 100644 index 0000000..f584a97 --- /dev/null +++ b/src/utils/qortalRequestFunctions.ts @@ -0,0 +1,33 @@ +export interface NameRecord { + name: string; + owner: string; +} + +export async function getAccountNames(address: string): Promise { + try { + const res = await qortalRequest({ + action: 'GET_ACCOUNT_NAMES', + address + }); + const list = Array.isArray(res) ? res : []; + if (!list.length) return [{ name: '', owner: address }]; + return list.map((n: any) => ({ + name: typeof n?.name === 'string' ? n.name : '', + owner: typeof n?.owner === 'string' ? n.owner : address + })); + } catch { + return [{ name: '', owner: address }]; + } +} + +export async function getPrimaryAccountName(address: string): Promise { + try { + const res = await qortalRequest({ + action: 'GET_PRIMARY_NAME', + address + }); + return typeof res === 'string' ? res : ''; + } catch { + return ''; + } +} diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index f9a5d35..5aa820e 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -33,6 +33,8 @@ import localForage from 'localforage' import ConsentModal from '../components/modals/ConsentModal' import localforage from 'localforage' import { Item } from '../components/common/Comments/CommentEditor' +import { getAccountNames, getPrimaryAccountName, NameRecord } from '../utils/qortalRequestFunctions' +import { removePrefix } from '../utils/blogIdformats' interface Props { children: React.ReactNode @@ -49,6 +51,7 @@ const GlobalWrapper: React.FC = ({ children }) => { const dispatch = useDispatch() const [userAvatar, setUserAvatar] = useState('') + const [allNames, setAllNames] = useState([]) const interval = useRef(null) const { user } = useSelector((state: RootState) => state.auth) const { audios, currAudio } = useSelector((state: RootState) => state.global) @@ -125,17 +128,6 @@ const GlobalWrapper: React.FC = ({ children }) => { isOpenEditBlogModal } = useSelector((state: RootState) => state.global) - async function getNameInfo(address: string) { - const response = await fetch('/names/address/' + address) - const nameData = await response.json() - - if (nameData?.length > 0) { - return nameData[0].name - } else { - return '' - } - } - async function verifyIfBlogIdExtists(username: string, identifier: string) { let doesExist = true //TODO - SHOULD REMOVE NAME FILTER AND IDENTIFIER SHOULD BE EXACT @@ -163,7 +155,7 @@ const GlobalWrapper: React.FC = ({ children }) => { return doesExist } - async function getBlog(name: string) { + async function getBlog(name: string): Promise { //TODO NAME SHOULD BE EXACT const url = `/arbitrary/resources/search?mode=ALL&service=BLOG&identifier=q-blog-&exactmatchnames=true&name=${name}&prefix=true&limit=20&includemetadata=true` const responseBlogs = await fetch(url, { @@ -177,10 +169,11 @@ const GlobalWrapper: React.FC = ({ children }) => { blog.identifier.startsWith('q-blog-') ) let blog - if (filterOut.length === 0) return - if (filterOut.length !== 0) { - blog = filterOut[0] + if (filterOut.length === 0) { + dispatch(setCurrentBlog(null)) + return null } + blog = filterOut[0] const urlBlog = `/arbitrary/BLOG/${blog.name}/${blog.identifier}` const response = await fetch(urlBlog, { method: 'GET', @@ -202,6 +195,7 @@ const GlobalWrapper: React.FC = ({ children }) => { navbarConfig: responseData?.navbarConfig || null }) ) + return blog.identifier // const response = await fetch("/names/address/" + address); // const nameData = await response.json(); @@ -218,10 +212,16 @@ const GlobalWrapper: React.FC = ({ children }) => { action: 'GET_USER_ACCOUNT' }) - const name = await getNameInfo(account.address) - dispatch(addUser({ ...account, name })) + const names = await getAccountNames(account.address) + const primaryName = await getPrimaryAccountName(account.address) + setAllNames(names) - const blog = await getBlog(name) + dispatch(addUser({ ...account, name: primaryName })) + if (primaryName) { + await getBlog(primaryName) + } else { + dispatch(setCurrentBlog(null)) + } setHasAttemptedToFetchBlogInitial(true) } catch (error) { console.error(error) @@ -445,6 +445,21 @@ const GlobalWrapper: React.FC = ({ children }) => { askForAccountInformation() }, []) + const switchActiveName = React.useCallback( + async (newName: string) => { + if (!user) return + dispatch(addUser({ ...user, name: newName })) + const blogId = await getBlog(newName) + setHasAttemptedToFetchBlogInitial(true) + if (blogId) { + navigate(`/${newName}/${removePrefix(blogId)}`) + } else { + navigate('/') + } + }, + [user, navigate] + ) + const onClosePublishBlogModal = React.useCallback(() => { dispatch(togglePublishBlogModal(false)) }, []) @@ -610,7 +625,7 @@ const GlobalWrapper: React.FC = ({ children }) => { isCalling = false }, 20000) }, - [checkNotifications] + [checkNotificationCreatorComment] ) useEffect(() => { if (!user?.name) return @@ -653,6 +668,8 @@ const GlobalWrapper: React.FC = ({ children }) => { blog={currentBlog} authenticate={askForAccountInformation} hasAttemptedToFetchBlogInitial={hasAttemptedToFetchBlogInitial} + allNames={allNames} + onSwitchActiveName={switchActiveName} /> {children} -- 2.43.0 From 2bc2fb3dcf634fdab3dd7184e3db3134f2e112aa Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Thu, 7 Aug 2025 21:17:48 -0400 Subject: [PATCH 15/42] Add polls to blog posts --- src/components/common/PollPanel.tsx | 322 ++++++++++++++++++ src/components/common/PollWidget.tsx | 175 ++++++++++ src/global.d.ts | 5 + .../BlogIndividualPost/BlogIndividualPost.tsx | 37 ++ src/pages/CreatePost/CreatePostBuilder.tsx | 29 ++ src/pages/CreatePost/CreatePostMinimal.tsx | 42 ++- .../components/Toolbar/EditorToolbar.tsx | 6 +- src/utils/checkStructure.ts | 3 +- 8 files changed, 615 insertions(+), 4 deletions(-) create mode 100644 src/components/common/PollPanel.tsx create mode 100644 src/components/common/PollWidget.tsx diff --git a/src/components/common/PollPanel.tsx b/src/components/common/PollPanel.tsx new file mode 100644 index 0000000..cbd6f0a --- /dev/null +++ b/src/components/common/PollPanel.tsx @@ -0,0 +1,322 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { styled, Box } from '@mui/system' +import { + Drawer, Typography, Tooltip, Button, ButtonBase, TextField, Checkbox, FormControlLabel, + List, ListItem, ListItemText, Divider, CircularProgress +} from '@mui/material' +import HowToVoteIcon from '@mui/icons-material/HowToVote' +import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../state/store' +import { setNotification } from '../../state/features/notificationsSlice' + +interface PollPanelProps { + onSelect: (pollName: string) => void + height?: string + width?: string +} + +const Panel = styled('div')` + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + height: 100%; + overflow: hidden; + + &::-webkit-scrollbar { width: 8px; height: 8px; } + &::-webkit-scrollbar-thumb { background-color: #888; border-radius: 4px; } + &::-webkit-scrollbar-thumb:hover { background-color: #555; } +` + +type PollRow = { + pollName: string + description?: string + owner?: string + pollOptions?: { optionName: string }[] + published?: number +} + +export const PollPanel: React.FC = ({ onSelect, height, width }) => { + const [open, setOpen] = useState(false) + const [isCreateOpen, setIsCreateOpen] = useState(false) + const [search, setSearch] = useState('') + const [myOnly, setMyOnly] = useState(true) + const { user } = useSelector((s: RootState) => s.auth) + + // --- create poll form + const [name, setName] = useState('') + const [description, setDescription] = useState('') + const [options, setOptions] = useState(['', '']) + + // Cache of ALL polls from the first scan + const apiPageSize = 50 + const [allPolls, setAllPolls] = useState([]) + const [rawOffset, setRawOffset] = useState(0) + const [scanningAll, setScanningAll] = useState(false) + const [fullyScanned, setFullyScanned] = useState(false) + const scanIdRef = useRef(0) + + // UI slice: how many filtered items to show (increments by 50 via "Load more") + const [displayCount, setDisplayCount] = useState(50) + + // Reset everything we derive from filters/search/open + const resetDerived = () => { + setDisplayCount(50) + } + + const dispatch = useDispatch() + + // Helper: fetch ONE raw page from /polls at a given offset + const fetchRawPage = async (offsetNum: number) => { + const url = new URL('/polls', window.location.origin) + url.searchParams.set('limit', String(apiPageSize)) + url.searchParams.set('offset', String(offsetNum)) + const res = await fetch(url.toString()) + if (!res.ok) throw new Error('Failed to fetch polls') + const data = await res.json() + return Array.isArray(data) ? data as PollRow[] : [] + } + + const scanAllPolls = useCallback(async () => { + if (fullyScanned || scanningAll) return + setScanningAll(true) + const myScanId = ++scanIdRef.current + try { + let nextRawOffset = rawOffset + let collected: PollRow[] = allPolls // keep any partial cache if we reopen + for (; ;) { + if (scanIdRef.current !== myScanId) return // canceled due to new open/filter change + const page = await fetchRawPage(nextRawOffset) + if (!page.length) { + setFullyScanned(true) + break + } + collected = [...collected, ...page] + setAllPolls(collected) // progressively update so UI can show partial results quickly + nextRawOffset += apiPageSize + setRawOffset(nextRawOffset) + if (page.length < apiPageSize) { + setFullyScanned(true) + break + } + // yield to UI; optional tiny delay to keep drawer responsive + await new Promise(r => setTimeout(r, 0)) + } + } catch (e) { + // If it errors, we stop scanning; user can close/open to retry. + setFullyScanned(true) + } finally { + setScanningAll(false) + } + }, [fullyScanned, scanningAll, rawOffset, allPolls]) + + useEffect(() => { + if (!open) return + // cancel any in-flight scan loop and start fresh if needed + scanIdRef.current++ + resetDerived() + // kick off a full scan if we haven't already completed one + if (!fullyScanned && !scanningAll) { + scanAllPolls() + } + // If we *have* already scanned, we just re-derive the visible slice from cache. + }, [open, myOnly, search, fullyScanned, scanningAll, scanAllPolls]) + + + + const handleToggle = () => setOpen(v => !v) + const addOption = () => setOptions(o => (o.length >= 100 ? o : [...o, ''])) + const updateOption = (i: number, val: string) => setOptions(o => o.map((x, idx) => (idx === i ? val : x))) + const removeOption = (i: number) => setOptions(o => o.filter((_, idx) => idx !== i)) + + // newest first by published, fallback to pollName + const sortedAll = useMemo(() => { + const arr = allPolls.slice() + arr.sort((a, b) => { + const aPub = a.published ?? 0 + const bPub = b.published ?? 0 + if (bPub !== aPub) return bPub - aPub + // fallback deterministic tiebreaker + return (b.pollName || '').localeCompare(a.pollName || '') + }) + return arr + }, [allPolls]) + + const normalizedSearch = search.trim().toLowerCase() + const filtered = useMemo(() => { + const byOwner = myOnly && user?.address + ? (p: PollRow) => (p.owner || '').toLowerCase() === user.address.toLowerCase() + : () => true + + const byQuery = normalizedSearch + ? (p: PollRow) => { + const inName = p.pollName?.toLowerCase().includes(normalizedSearch) + const inDesc = p.description?.toLowerCase().includes(normalizedSearch) + const inOpts = (p.pollOptions || []).some(o => + o.optionName?.toLowerCase().includes(normalizedSearch) + ) + return !!(inName || inDesc || inOpts) + } + : () => true + + return sortedAll.filter(p => byOwner(p) && byQuery(p)) + }, [sortedAll, myOnly, user, normalizedSearch]) + + const visible = useMemo(() => filtered.slice(0, displayCount), [filtered, displayCount]) + const canLoadMore = visible.length < filtered.length // controls "Load more" + + const canCreate = useMemo(() => { + const validName = name.trim().length >= 3 && name.trim().length <= 400 + const validDesc = description.length <= 4000 + const cleaned = options.map(o => o.trim()).filter(Boolean) + return validName && validDesc && cleaned.length >= 1 + }, [name, description, options]) + + const handleCreate = async () => { + if (!user?.address) return + const cleaned = options.map(o => o.trim()).filter(Boolean) + const optionsString = cleaned.join(', ') + try { + await qortalRequest({ + action: 'CREATE_POLL', + pollName: name.trim(), + pollDescription: description.trim(), + pollOptions: [optionsString], + pollOwnerAddress: user.address + }) + dispatch( + setNotification({ + msg: 'Creating poll successful', + alertType: 'success' + }) + ) + // After create, immediately hand back pollName to editor + onSelect(name.trim()) + setIsCreateOpen(false) + setOpen(false) + } catch (e: any) { + dispatch( + setNotification({ + msg: e?.message || 'Creating poll failed', + alertType: 'error' + }) + ) + } + } + + return ( + + + + + + + + + Select Poll + Search polls on Qortal. Default shows only your polls. + + + { setSearch(e.target.value); setDisplayCount(50) }} + /> + + { setMyOnly(e.target.checked); setDisplayCount(50) }} + /> + } + label="My Polls" + /> + + + + + + + + + + {visible.map(p => ( + { onSelect(p.pollName); setOpen(false) }}> + Embed + + }> + { onSelect(p.pollName); setOpen(false) }} sx={{ width: '100%', textAlign: 'left' }}> + + + + ))} + {/* While scanning & no cache yet, show spinner */} + {open && scanningAll && allPolls.length === 0 && ( + + + + )} + {/* If scan finished (or partial cache exists) but filter produced nothing */} + {!scanningAll && visible.length === 0 && ( + No polls found. + )} + {/* Only show Load more if there are more filtered results beyond what's visible */} + {!scanningAll && canLoadMore && ( + + + + )} + + + {/* Create Poll Dialog (inline, simple) */} + {isCreateOpen && ( + + Create Poll + setName(e.target.value)} + helperText="3–400 chars; must be unique on-chain" + sx={{ mb: 1 }} + /> + setDescription(e.target.value)} sx={{ mb: 1 }} + /> + Options + {options.map((opt, i) => ( + + updateOption(i, e.target.value)} /> + + + ))} + + + + + + + + + )} + + + + ) +} diff --git a/src/components/common/PollWidget.tsx b/src/components/common/PollWidget.tsx new file mode 100644 index 0000000..29fdcab --- /dev/null +++ b/src/components/common/PollWidget.tsx @@ -0,0 +1,175 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { Box, Card, CardContent, Typography, RadioGroup, FormControlLabel, Radio, Button, LinearProgress } from '@mui/material' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../state/store' +import { setNotification } from '../../state/features/notificationsSlice' + +type PollOption = { optionName: string } +type PollInfo = { pollName: string; description?: string; pollOptions: PollOption[] } +type VotesResp = { + votes: { voterPublicKey: string | string[], optionIndex: number }[] + totalVotes: number + voteCounts: { optionName: string, voteCount: number }[] +} + +export const PollWidget: React.FC<{ pollName: string }> = ({ pollName }) => { + const { user } = useSelector((s: RootState) => s.auth) + const [info, setInfo] = useState(null) + const [votes, setVotes] = useState(null) + const [submitting, setSubmitting] = useState(false) + const [selected, setSelected] = useState(null) + const [myExistingChoice, setMyExistingChoice] = useState(null) + const [myPendingChoice, setMyPendingChoice] = useState(null) + const myPk = user?.publicKey + const dispatch = useDispatch() + + const fetchInfo = async () => { + try { + const r = await fetch(`/polls/${encodeURIComponent(pollName)}`) + const j = await r.json() + setInfo(j) + } catch {} + } + + const fetchVotes = async () => { + try { + const r = await fetch(`/polls/votes/${encodeURIComponent(pollName)}`) + const j = await r.json() + setVotes(j) + if (myPk && j?.votes?.length) { + const found = j.votes.find((v: any) => { + const arr = Array.isArray(v.voterPublicKey) ? v.voterPublicKey : [v.voterPublicKey] + return arr?.[0]?.toLowerCase?.() === String(myPk).toLowerCase() + }) + // <-- NEW: capture your recorded vote separately + setMyExistingChoice(found ? found.optionIndex : null) + // Initialize the radio selection once, but don't let it drive the label + if (selected === null) { + setSelected(found ? found.optionIndex : null) + } + } else { + setMyExistingChoice(null) + if (selected === null) setSelected(null) + } + } catch { } + } + + const fetchPendingVotes = async () => { + if (!user?.address) { + setMyPendingChoice(null) + return + } + try { + const r = await fetch( + `/transactions/search?txType=VOTE_ON_POLL&reverse=true&confirmationStatus=UNCONFIRMED&limit=100` + ) + const txs = await r.json() + // Filter to only this user's unconfirmed votes for this poll + const matching = txs + .filter((tx: any) => + tx.pollName === pollName && + tx.creatorAddress?.toLowerCase() === user.address.toLowerCase() + ) + .sort((a: any, b: any) => b.timestamp - a.timestamp) + + if (matching.length > 0) { + setMyPendingChoice(matching[0].optionIndex) + } else { + setMyPendingChoice(null) + } + } catch { + setMyPendingChoice(null) + } + } + + useEffect(() => { + fetchInfo() + fetchVotes() + fetchPendingVotes() + // eslint-disable-next-line + }, [pollName, myPk]) + + const total = votes?.totalVotes || 0 + const countsByOption = useMemo(() => { + const map = new Map() + votes?.voteCounts?.forEach(v => map.set(v.optionName, v.voteCount)) + return map + }, [votes]) + + const doVote = async () => { + if (selected === null) return + try { + setSubmitting(true) + await qortalRequest({ + action: 'VOTE_ON_POLL', + pollName, + optionIndex: selected + }) + dispatch( + setNotification({ + msg: 'Voting successful', + alertType: 'success' + }) + ) + await fetchVotes() + await fetchPendingVotes() + } catch (e: any) { + dispatch( + setNotification({ + msg: e?.message || 'Voting failed', + alertType: 'error' + }) + ) + } finally { + setSubmitting(false) + } + } + + if (!info) return null + + return ( + + + {info.pollName} + {info.description && {info.description}} + + setSelected(Number(e.target.value))} + > + {info.pollOptions?.map((opt, i) => { + const count = countsByOption.get(opt.optionName) || 0 + const pct = total ? Math.round((count / total) * 100) : 0 + const isMine = myPk ? myExistingChoice === i : false + const isPending = myPk ? myPendingChoice === i : false + return ( + + } label={opt.optionName} /> + + + {count} vote{count !== 1 ? 's' : ''} {total ? `(${pct}%)` : ''} + {isMine ? ` • Your choice${isPending ? ' (pending)' : ''}` : ''} + + + ) + })} + + + + + + + + + ) +} diff --git a/src/global.d.ts b/src/global.d.ts index 213f36d..75357f6 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -37,6 +37,11 @@ interface QortalRequestOptions { exactMatchNames?: boolean excludeBlocked?: boolean mode?: string + pollName?: string + pollDescription?: string + pollOptions?: string[] + pollOwnerAddress?: string + optionIndex?: number } declare function qortalRequest(options: QortalRequestOptions): Promise diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index eaef52c..f07cc87 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -45,6 +45,7 @@ import FileElement from '../../components/FileElement' import { CopyToClipboard } from 'react-copy-to-clipboard' import { setNotification } from '../../state/features/notificationsSlice' import ContextMenuResource from '../../components/common/ContextMenu/ContextMenuResource' +import { PollWidget } from '../../components/common/PollWidget' const ResponsiveGridLayout = WidthProvider(Responsive) const initialMinHeight = 2 // Define an initial minimum height for grid items @@ -620,6 +621,24 @@ export const BlogIndividualPost = () => { ) } + if (section?.type === 'poll') { + return ( +
+ Error loading poll}> + + + + +
+ ) + } })} )} @@ -877,6 +896,24 @@ export const BlogIndividualPost = () => { ) } + if (section?.type === 'poll') { + return ( +
+ Error loading poll}> + + + + +
+ ) + } })}
) diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index abe8efe..0d42eb6 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -979,6 +979,11 @@ export const CreatePostBuilder = ({ }) }, []) + const onSelectPoll = React.useCallback((pollName: string) => { + const section = { type: 'poll', version: 1, content: { pollName }, id: uid() } + setNewPostContent((prev) => [...prev, section]) + }, []) + const closeAddTextModal = React.useCallback(() => { setIsOpenAddTextModal(false) }, []) @@ -1017,6 +1022,7 @@ export const CreatePostBuilder = ({ onSelectVideo={onSelectVideo} onSelectAudio={onSelectAudio} onSelectFile={onSelectFile} + onSelectPoll={onSelectPoll} paddingValue={paddingValue} onChangePadding={onChangePadding} addNav={addNav} @@ -1343,6 +1349,29 @@ export const CreatePostBuilder = ({ ) } + if (section.type === 'poll') { + return ( +
+ + + {/* lightweight preview */} + Poll: {section.content?.pollName} + The interactive poll will appear in the published post. + + removeSection(section)} sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} /> + + + +
+ ) + } })} diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index 344bdd7..66b491c 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -166,6 +166,7 @@ export const CreatePostMinimal = ({ setExistingImagesLoading(false) } }, [user]) + const addPostSection = React.useCallback((content: any) => { const id = uid() const type = 'editor' @@ -175,7 +176,6 @@ export const CreatePostMinimal = ({ content, id } - setNewPostContent((prev) => [...prev, section]) setLayouts((prev: any) => { return { @@ -193,6 +193,17 @@ export const CreatePostMinimal = ({ setEditorKey((prev) => prev + 1) }, []) + const addPollSection = React.useCallback((pollName: string) => { + const id = uid() + const type = 'poll' + const section = { type, version: 1, content: { pollName }, id } + setNewPostContent((prev) => [...prev, section]) + setLayouts((prev: any) => ({ + ...prev, + rows: [...prev.rows, { ids: [id], id: uid(), type }] + })) + }, []) + useEffect(() => { if (blogContentForEdit && postIdForEdit && blogMetadataForEdit) { setTitle(blogContentForEdit?.title || '') @@ -922,6 +933,11 @@ export const CreatePostMinimal = ({ }) }, []) + const onSelectPoll = React.useCallback((pollName: string) => { + const section = { type: 'poll', version: 1, content: { pollName }, id: uid() } + setNewPostContent((prev) => [...prev, section]) + }, []) + const closeAddTextModal = React.useCallback(() => { setIsOpenAddTextModal(false) }, []) @@ -988,6 +1004,7 @@ export const CreatePostMinimal = ({ onSelectVideo={onSelectVideo} onSelectAudio={onSelectAudio} onSelectFile={onSelectFile} + onSelectPoll={addPollSection} // OR onSelectPoll paddingValue={paddingValue} onChangePadding={onChangePadding} isMinimal={true} @@ -1339,6 +1356,29 @@ export const CreatePostMinimal = ({ ) } + if (section.type === 'poll') { + return ( +
+ + + {/* lightweight preview */} + Poll: {section.content?.pollName} + The interactive poll will appear in the published post. + + removeSection(section, rowIndex)} sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} /> + + + +
+ ) + } })} {row.type === 'image' && row.ids.length < 3 && ( void onSelectAudio: (audio: any) => void onSelectFile: (file: any) => void + onSelectPoll: (pollName: string) => void paddingValue: number onChangePadding: (padding: number) => void isMinimal?: boolean @@ -43,6 +45,7 @@ export const EditorToolbar = ({ onSelectVideo, onSelectAudio, onSelectFile, + onSelectPoll, paddingValue, onChangePadding, isMinimal = false, @@ -80,11 +83,10 @@ export const EditorToolbar = ({ - - + { c.type !== 'image' && c.type !== 'video' && c.type !== 'audio' && - c.type !== 'file' + c.type !== 'file' && + c.type !== 'poll' ) { isValid = false } -- 2.43.0 From 450df3aa445db6ca3ff470d4301c25898c8eceac Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sat, 16 Aug 2025 21:21:57 -0400 Subject: [PATCH 16/42] Phase 0: repo hygiene + docs + PR kit --- .editorconfig | 10 + .gitea/ISSUE_TEMPLATE/bug_report.md | 32 + .gitea/ISSUE_TEMPLATE/feature_request.md | 24 + .gitea/PULL_REQUEST_TEMPLATE.md | 21 + .gitea/labels.json | 29 + .gitea/workflows/ci-no-marketplace.yml | 83 + .npmrc | 3 + .nvmrc | 1 + .prettierrc.json | 13 +- CODEOWNERS | 1 + CONTRIBUTING.md | 20 + MANIFEST_PATCH0.md | 34 + docs/A11Y_MANUAL_CHECKLIST.md | 21 + docs/DECISIONS/ADR-TEMPLATE.md | 20 + docs/GLOSSARY_DOMAIN.md | 20 + docs/PATCH0_PLAN.md | 21 + docs/Q-Blog_1.0.0_ROADMAP_conceptual.md | 234 + docs/Q-Blog_1.0.0_ROADMAP_implementation.md | 329 + docs/Q-Blog_1.0.0_ROADMAP_non-technical.md | 158 + docs/Q-Blog_Project_Instructions.md | 186 + docs/QUALITY_CHARTER.md | 42 + docs/RELEASING.md | 10 + docs/RISKS_ASSUMPTIONS.md | 12 + docs/ROADMAP_DEPENDENCIES.md | 19 + docs/SECURITY_PRIVACY_POSTURE.md | 19 + docs/TESTING.md | 17 + docs/USER_JOURNEYS.md | 21 + docs/VISION_PRFAQ.md | 39 + eslint.config.mjs | 31 + .../001-Adopt_Quality_Charter_(sign-off).md | 20 + ...ture,_Testing,_Accessibility,_Security).md | 22 + ...Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md | 21 + ...-ESLint_Flat_Config_+_jsx-a11y_Baseline.md | 20 + ...I_—_MUI_v4_imports_&_ts-nocheck_removal.md | 20 + ...II_—_Type_hygiene_in_reducers-selectors.md | 20 + ...mpty-Loading-Error_states_standardization.md | 20 + issues/adopt-quality-charter--sign-off-.md | 20 + ...i---mui-v4-imports---ts-nocheck-removal.md | 20 + ...ii---type-hygiene-in-reducers-selectors.md | 20 + ...ty-loading-error-states-standardization.md | 20 + .../eslint-flat-config---jsx-a11y-baseline.md | 20 + ...ture--testing--accessibility--security-.md | 22 + issues/issues_patch0_phase1-3.json | 74 + ...harness--vitest---rtl---msw---jest-axe-.md | 21 + milestone_patch0.json | 5 + package-lock.json | 12812 +++++++++++++++- package.json | 32 +- pr/phase0.md | 23 + qblog-phase-file-guide.md | 217 + qblog-structure-and-edit-plan.md | 225 + scripts/dev/install-dev-deps.sh | 15 + scripts/dev/patch-package-scripts.sh | 25 + scripts/tracker/README.md | 27 + scripts/tracker/TROUBLESHOOTING.md | 20 + scripts/tracker/bootstrap_patch0.sh | 99 + scripts/tracker/labels_dedupe.sh | 34 + scripts/tracker/rename_milestone_phase0.sh | 19 + scripts/tracker/verify_patch0.sh | 33 + tests/axe-smoke.test.tsx | 15 + tests/msw/handlers.ts | 7 + tests/msw/server.ts | 4 + tests/setup.ts | 11 + tests/types/expect-extensions.d.ts | 12 + tsconfig.strict.json | 13 + vitest.config.ts | 20 + 65 files changed, 15266 insertions(+), 232 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitea/ISSUE_TEMPLATE/bug_report.md create mode 100644 .gitea/ISSUE_TEMPLATE/feature_request.md create mode 100644 .gitea/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitea/labels.json create mode 100644 .gitea/workflows/ci-no-marketplace.yml create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 CODEOWNERS create mode 100644 CONTRIBUTING.md create mode 100644 MANIFEST_PATCH0.md create mode 100644 docs/A11Y_MANUAL_CHECKLIST.md create mode 100644 docs/DECISIONS/ADR-TEMPLATE.md create mode 100644 docs/GLOSSARY_DOMAIN.md create mode 100644 docs/PATCH0_PLAN.md create mode 100644 docs/Q-Blog_1.0.0_ROADMAP_conceptual.md create mode 100644 docs/Q-Blog_1.0.0_ROADMAP_implementation.md create mode 100644 docs/Q-Blog_1.0.0_ROADMAP_non-technical.md create mode 100644 docs/Q-Blog_Project_Instructions.md create mode 100644 docs/QUALITY_CHARTER.md create mode 100644 docs/RELEASING.md create mode 100644 docs/RISKS_ASSUMPTIONS.md create mode 100644 docs/ROADMAP_DEPENDENCIES.md create mode 100644 docs/SECURITY_PRIVACY_POSTURE.md create mode 100644 docs/TESTING.md create mode 100644 docs/USER_JOURNEYS.md create mode 100644 docs/VISION_PRFAQ.md create mode 100644 eslint.config.mjs create mode 100644 issues/001-Adopt_Quality_Charter_(sign-off).md create mode 100644 issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md create mode 100644 issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md create mode 100644 issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md create mode 100644 issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md create mode 100644 issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md create mode 100644 issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md create mode 100644 issues/adopt-quality-charter--sign-off-.md create mode 100644 issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md create mode 100644 issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md create mode 100644 issues/correctness-sweep-iii---empty-loading-error-states-standardization.md create mode 100644 issues/eslint-flat-config---jsx-a11y-baseline.md create mode 100644 issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md create mode 100644 issues/issues_patch0_phase1-3.json create mode 100644 issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md create mode 100644 milestone_patch0.json create mode 100644 pr/phase0.md create mode 100644 qblog-phase-file-guide.md create mode 100644 qblog-structure-and-edit-plan.md create mode 100755 scripts/dev/install-dev-deps.sh create mode 100755 scripts/dev/patch-package-scripts.sh create mode 100644 scripts/tracker/README.md create mode 100644 scripts/tracker/TROUBLESHOOTING.md create mode 100644 scripts/tracker/bootstrap_patch0.sh create mode 100644 scripts/tracker/labels_dedupe.sh create mode 100755 scripts/tracker/rename_milestone_phase0.sh create mode 100644 scripts/tracker/verify_patch0.sh create mode 100644 tests/axe-smoke.test.tsx create mode 100644 tests/msw/handlers.ts create mode 100644 tests/msw/server.ts create mode 100644 tests/setup.ts create mode 100644 tests/types/expect-extensions.d.ts create mode 100644 tsconfig.strict.json create mode 100644 vitest.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..159bd2f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# .editorconfig — Q-Blog +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..9db16ac --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Something is broken or wrong +labels: fix +--- + +## Summary +What happened vs expected? + +## Environment +Browser/OS, screen reader if relevant + +## Steps to Reproduce +1. +2. +3. + +## Expected vs Actual +- Expected: +- Actual: + +## Screenshots/Logs +(attach if possible, redact sensitive info) + +## Impact +P0 | P1 | P2 + +## Checks +- [ ] Keyboard path verified (if UI) +- [ ] A11y names/labels/states present +- [ ] Error includes recovery path +- [ ] Repro is deterministic diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..74b9dac --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Propose a capability or improvement +labels: feat +--- + +## Problem / Outcome +What user problem are we solving? + +## Proposal +Short description, acceptance criteria, and non-goals. + +## A11y & Quality +- Keyboard/focus implications +- Labels/roles/states +- Tests (unit/component) needed + +## Dependencies +Related issues/ADRs + +## Definition of Done +- [ ] Acceptance criteria met +- [ ] Tests/docs updated +- [ ] Quality gates green diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6335ad6 --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +## Summary +What this PR changes and why. + +## Scope +- [ ] Code +- [ ] Tests +- [ ] Docs +- [ ] A11y +- [ ] Build/Config + +## Verification +Commands run, screenshots, and notes. Include keyboard path if UI. + +## Risk & Rollback +Potential impacts; how to revert if needed. + +## Checklist +- [ ] Typecheck + ESLint green +- [ ] Vitest green (incl. axe smoke where relevant) +- [ ] No new `any` in public props; no blanket disables +- [ ] Docs/ADRs updated if behavior changes diff --git a/.gitea/labels.json b/.gitea/labels.json new file mode 100644 index 0000000..85c39e5 --- /dev/null +++ b/.gitea/labels.json @@ -0,0 +1,29 @@ +{ + "labels": [ + {"name":"feat","color":"#36a64f"}, + {"name":"fix","color":"#d73a49"}, + {"name":"docs","color":"#0e8a16"}, + {"name":"test","color":"#5319e7"}, + {"name":"a11y","color":"#795548"}, + {"name":"perf","color":"#1f77b4"}, + {"name":"security","color":"#b60205"}, + {"name":"chore","color":"#c0c0c0"}, + {"name":"editor","color":"#2196f3"}, + {"name":"lists","color":"#00bcd4"}, + {"name":"blogs","color":"#8bc34a"}, + {"name":"members","color":"#009688"}, + {"name":"routing","color":"#3f51b5"}, + {"name":"state","color":"#9c27b0"}, + {"name":"build","color":"#607d8b"}, + {"name":"tests","color":"#673ab7"}, + {"name":"docs-area","color":"#4caf50"}, + {"name":"P0","color":"#e91e63"}, + {"name":"P1","color":"#ff9800"}, + {"name":"P2","color":"#ffc107"}, + {"name":"XS","color":"#e0f7fa"}, + {"name":"S","color":"#b2ebf2"}, + {"name":"M","color":"#80deea"}, + {"name":"L","color":"#4dd0e1"}, + {"name":"XL","color":"#26c6da"} + ] +} \ No newline at end of file diff --git a/.gitea/workflows/ci-no-marketplace.yml b/.gitea/workflows/ci-no-marketplace.yml new file mode 100644 index 0000000..00cd180 --- /dev/null +++ b/.gitea/workflows/ci-no-marketplace.yml @@ -0,0 +1,83 @@ +name: CI (no marketplace actions, Q-Blog) + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - name: Manual checkout from Gitea + shell: bash + run: | + set -euxo pipefail + : "${GITHUB_SERVER_URL:?}" + : "${GITHUB_REPOSITORY:?}" + : "${GITHUB_SHA:?}" + git init . + git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + git fetch --depth=1 origin "${GITHUB_SHA}" + git checkout --detach FETCH_HEAD + + - name: Install Node (tarball) and add to PATH + shell: bash + env: + NODE_VERSION: "20.16.0" + run: | + set -euxo pipefail + ARCH="x64" + curl -fsSLO "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${ARCH}.tar.xz" + tar -xJf "node-v${NODE_VERSION}-linux-${ARCH}.tar.xz" + echo "${{ github.workspace }}/node-v${NODE_VERSION}-linux-${ARCH}/bin" >> $GITHUB_PATH + node -v + npm -v + + - name: Enable corepack and setup pnpm + shell: bash + run: | + set -euxo pipefail + corepack enable + corepack prepare pnpm@9.7.0 --activate + pnpm -v + + - name: Install dependencies + shell: bash + run: | + set -euxo pipefail + if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; else npm ci; fi + + - name: Typecheck + shell: bash + run: | + set -euxo pipefail + if [ -f tsconfig.json ]; then npx tsc -noEmit || pnpm tsc -noEmit; else echo "No TS"; fi + + - name: Lint + shell: bash + run: | + set -euxo pipefail + if [ -f package.json ] && jq -e '.scripts.lint' package.json >/dev/null; then pnpm lint; else echo "No lint script"; fi + + - name: Test (Vitest) + shell: bash + run: | + set -euxo pipefail + mkdir -p ci-results + if [ -f vitest.config.ts ] || [ -f vitest.config.mts ] || [ -d tests ]; then pnpm test -- --run --coverage || npm test -- --run --coverage; else echo "No tests"; fi + + - name: Build (Vite) + shell: bash + run: | + set -euxo pipefail + if [ -f vite.config.ts ] || [ -f vite.config.mts ]; then pnpm build || npm run build; else echo "No build"; fi + + - name: List artifacts we expect + if: always() + shell: bash + run: | + echo '--- dist ---'; (ls -la dist || true) + echo '--- coverage ---'; (ls -la coverage || true) + echo '--- ci-results ---'; (ls -la ci-results || true) diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..85016f4 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +fund=false +audit=true +save-exact=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..80a9956 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.16.0 diff --git a/.prettierrc.json b/.prettierrc.json index 0a4d4fc..6b0fda4 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,10 +1,7 @@ { - "printWidth": 80, - "singleQuote": false, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false, - "arrowParens": "avoid", - "tabWidth": 2, - "semi": true + "printWidth": 100, + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "arrowParens": "always" } diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..d85be2e --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @greenflame089 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bf80b04 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# Contributing — Q-Blog (Phase 0) + +## Prereqs +- Node 20.16.x (`nvm use`), pnpm or npm +- `pnpm install` or `npm ci` + +## Useful scripts +- `pnpm typecheck` — TS noEmit +- `pnpm lint` — ESLint flat config (jsx-a11y) +- `pnpm test` / `pnpm test:run` — Vitest (JSDOM), coverage v8 +- `pnpm build` — Vite build if configured + +## PR checklist (Phase 0) +- [ ] Lints, types, tests green locally +- [ ] a11y smoke (jest-axe) passes +- [ ] Docs updated if behavior/contract changed +- [ ] Small, vertical slice; clear “How to verify” + +## Commit style +Conventional-ish short prefix is fine (`fix:`, `feat:`, `docs:`). Keep subjects concise. diff --git a/MANIFEST_PATCH0.md b/MANIFEST_PATCH0.md new file mode 100644 index 0000000..f6dde85 --- /dev/null +++ b/MANIFEST_PATCH0.md @@ -0,0 +1,34 @@ +# Q-Blog Patch-0 Bundle — Manifest + +_Where each file belongs (relative to repo root)._ + +## Tracker & Workflow + +- `.gitea/labels.json` — label set for tracker import. +- `.gitea/ISSUE_TEMPLATE/bug_report.md` — bug template. +- `.gitea/ISSUE_TEMPLATE/feature_request.md` — feature template. +- `.gitea/PULL_REQUEST_TEMPLATE.md` — PR template. +- `milestone_patch0.json` — milestone payload for Patch 0. +- `issues/issues_patch0_phase1-3.json` — machine-readable issues for bootstrap script. +- `issues/*.md` — human-readable issue texts (copy/paste). +- `scripts/tracker/bootstrap_patch0.sh` — creates labels, milestone, issues via Gitea API. +- `scripts/tracker/README.md` — bootstrap instructions. + +## Docs (foundation) + +- `docs/Q-Blog_Project_Instructions.md` — canonical Project Instructions. +- `docs/QUALITY_CHARTER.md` — SLOs/gates and acceptance policy. +- `docs/PATCH0_PLAN.md` — Patch 0 scope and acceptance. +- `docs/DECISIONS/ADR-TEMPLATE.md` — ADR skeleton for future choices. +- `docs/VISION_PRFAQ.md` — narrative and FAQ for 1.0. +- `docs/USER_JOURNEYS.md` — personas and protected flows. +- `docs/GLOSSARY_DOMAIN.md` — shared vocabulary & invariants. +- `docs/RISKS_ASSUMPTIONS.md` — risks and mitigations. +- `docs/ROADMAP_DEPENDENCIES.md` — phase dependencies and gates. +- `docs/SECURITY_PRIVACY_POSTURE.md` — principles & checklist. +- `docs/A11Y_MANUAL_CHECKLIST.md` — manual keyboard/a11y tests. + +## Planning aides + +- `qblog-structure-and-edit-plan.md` — full file list with edit/new notes. +- `qblog-phase-file-guide.md` — phase-by-phase file plan. diff --git a/docs/A11Y_MANUAL_CHECKLIST.md b/docs/A11Y_MANUAL_CHECKLIST.md new file mode 100644 index 0000000..ee02848 --- /dev/null +++ b/docs/A11Y_MANUAL_CHECKLIST.md @@ -0,0 +1,21 @@ +# Q‑Blog — Accessibility Manual Test Checklist +_Generated 2025-08-16 23:27Z_ + +## Global +- Tab through top‑level navigation, header, main, footer; **skip link** focuses main. +- Focus is always visible; Escape closes modals/popovers and returns focus to invoker. + +## Read Post +- Heading hierarchy is logical; images have meaningful `alt` or `alt=""` (decorative). +- Landmarks correctly identify regions; links have descriptive names. + +## Create/Edit Post +- Labels programmatic; errors linked via `aria-describedby`. +- Toolbar buttons expose role/name/state; disabled vs. pressed states clear. +- Live region announces save start/success/failure; respects `prefers-reduced-motion`. + +## Manage Blogs +- Switcher is keyboard‑reachable; selection updates page content and title. + +## Collaboration +- Without permission, destructive controls are absent or disabled; UI explains restrictions. diff --git a/docs/DECISIONS/ADR-TEMPLATE.md b/docs/DECISIONS/ADR-TEMPLATE.md new file mode 100644 index 0000000..c4840e3 --- /dev/null +++ b/docs/DECISIONS/ADR-TEMPLATE.md @@ -0,0 +1,20 @@ +# ADR-XXXX — Title +Status: Proposed | Accepted | Superseded | Deprecated +Date: 2025-08-16 23:43Z + +## Context +What problem are we solving? What constraints apply? + +## Options +- Option A — pros/cons +- Option B — pros/cons +- (Optional) Option C — pros/cons + +## Decision +Which option and why. + +## Consequences +Positive/negative outcomes, follow-ups, measurable impacts. + +## References +Links to issues, PRs, specs; related ADRs. diff --git a/docs/GLOSSARY_DOMAIN.md b/docs/GLOSSARY_DOMAIN.md new file mode 100644 index 0000000..b042a91 --- /dev/null +++ b/docs/GLOSSARY_DOMAIN.md @@ -0,0 +1,20 @@ +# Q‑Blog — Glossary & Domain Canon +_Generated 2025-08-16 23:27Z_ + +**Name** — The account identity under which blogs are created. +**Blog** — A container for posts; owned by a Name; has **handle**, **title**, **visibility**. +**Handle** — Human‑friendly unique identifier per Name (slug rules). +**Post** — Content item; belongs to exactly one Blog (immutable link). +**Role** — `owner | editor | author`; defines allowed operations. +**Membership** — Name ↔ Blog relationship with role. +**Revision** — Monotonically increasing number used for concurrency control. +**Invite** — Time‑limited token that assigns a role on acceptance. + +### Invariants +- A Post’s `blogId` does not change after creation. +- (`nameId`, `blogHandle`) is unique. +- All write operations require a role check (server‑enforced). + +### Identifier & URL Guidance +- Canonical blog URL: `/{nameHandle}/{blogHandle}/…` (conceptual). +- Slugs are normalized to lowercase, ASCII, hyphen‑separated; collisions rejected with a helpful message. diff --git a/docs/PATCH0_PLAN.md b/docs/PATCH0_PLAN.md new file mode 100644 index 0000000..28c495f --- /dev/null +++ b/docs/PATCH0_PLAN.md @@ -0,0 +1,21 @@ +# Patch 0 — Orientation & Quality Bar +_Generated 2025-08-16 23:43Z_ + +## Goal +Ratify the Quality Charter and spin up the basic governance scaffolding before touching code. + +## Tasks +1. **Adopt Quality Charter** — review, adjust targets if needed, and sign. +2. **Label set** — create canonical labels in the tracker (Area, Type, Priority, Size). +3. **Templates** — add Issue + PR templates with acceptance sections and a11y/security checks. +4. **Milestone** — create `Patch 0` milestone with this checklist as description. +5. **Backlog triage** — seed initial issues for Phase 1–3 planning (docs, harness, correctness). + +## Label Set (proposed) +- **Type:** feat, fix, chore, docs, test, a11y, perf, security +- **Area:** editor, lists, blogs, members, routing, state, build, tests, docs +- **Priority:** P0, P1, P2 +- **Size:** XS, S, M, L, XL + +## Acceptance +- Charter signed; templates merged; labels exist; milestone created; 5–8 seeded issues for Phase 1–3. diff --git a/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md b/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md new file mode 100644 index 0000000..4358559 --- /dev/null +++ b/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md @@ -0,0 +1,234 @@ +**TL;DR:** Below is a higher-level, technical “how we’ll do it” roadmap for Phases 0–10. It explains approaches, contracts, guardrails, and sequencing—no filenames or concrete code yet. + +--- + +# Q-Blog → 1.0.0: Technical Roadmap (Conceptual “How”) + +## Phase 0 — Orientation & Quality Bar + +**How:** + +* Define protected user journeys and SLIs/SLOs (e.g., time-to-interactive, save success rate, keyboard reachability). +* Adopt a single “definition of done” template (accessibility, tests, docs, telemetry hook, error state). +* Establish a lightweight decision record format (context → options → decision → impact) for any cross-cutting choice. + +--- + +## Phase 1 — Project Docs & Working Agreements + +**How:** + +* Author concise, source-of-truth docs: a high-level architecture map (UI/state/data flows), a testing strategy pyramid, an accessibility standard, and contribution rules. +* Record initial decisions (editor stack, state mgmt, theming, data shape conventions). +* Add a changelog discipline (human-readable entries grouped by “Added/Changed/Fixed/Removed/Docs/Tests”). +* Define semantic labels for issues/PRs (type, area, effort) to enable predictable triage. + +--- + +## Phase 2 — Test Harness & Linting Baseline + +**How:** + +* Testing layers: + + * **Unit & component:** Vitest + JSDOM + React Testing Library; global test setup for matchers/mocks. + * **Integration/smoke (later):** small number of Playwright/Happy-path flows (create/edit/publish). +* Coverage: + + * Enforce thresholds at the package level; exclude generated/fixture code; surface a coverage report artifact. +* Linting/formatting: + + * ESLint (flat config) with TypeScript, React, and **jsx-a11y**; Prettier for formatting; consistent import ordering. +* Fast feedback: + + * Watch mode locally; pre-push hooks run **lint + typecheck + tests (changed files)**. +* CI scaffolding (conceptual): + + * Separate jobs for install/cache, lint/typecheck, unit/component tests, and (later) e2e smoke; cache node modules; artifact uploads for coverage. + +--- + +## Phase 3 — Correctness Sweep + +**How:** + +* Dependency hygiene: + + * Align UI toolkit imports to current major; remove legacy namespaces; verify peer-dep ranges; lock versions for determinism. +* Type safety: + + * Replace broad `any`/suppressed regions with minimal interfaces and discriminated unions where helpful. + * Introduce a “strict mode budget”: upgrades gated by keeping type errors at zero. +* State/data flows: + + * Standardize async lifecycles (`idle/loading/success/error`) and empty states; unify error objects (message + code + recoverability). +* Image/media safety: + + * Enforce text alternatives policy; handle failed loads with fallbacks; guard upload constraints (size/type). +* Cross-browser sanity: + + * Run a small matrix (Chromium/WebKit/Firefox) in CI for a smoke render test. + +--- + +## Phase 4 — Accessibility Baseline + +**How:** + +* Semantics & landmarks: + + * Use structural regions (header/nav/main/footer) and a skip-to-content anchor; ensure a single H1 per view. +* Keyboard & focus: + + * Trap focus in modals/popovers, restore focus on close, visible outlines; consistent Tab/Shift+Tab order; Esc closes transient UI. +* Names/roles/states: + + * Deterministic accessible names for controls; toggles use `pressed` state; forms have programmatic labels and error/help text wiring. +* Live updates & motion: + + * Polite live region for async progress; respect `prefers-reduced-motion`; avoid seizure risk (no flashing). +* Testing: + + * Automated axe checks in component tests for critical views; a short manual keyboard checklist per protected journey. + +--- + +## Phase 5 — UX & IA Touch-up + +**How:** + +* Navigation model: + + * Clarify primary vs. secondary navigation; ensure routes map cleanly to the data model (current blog scope). +* Consistency system: + + * Define tokens for spacing/typography/contrast; standardize button/empty/error patterns; unify iconography. +* Editor ergonomics: + + * Deterministic toolbar enable/disable; clear state for draft vs. published; undo/redo affordances; autosave with visible status. +* Content lifecycle: + + * Uniform toasts/banners for success/failure; retry affordances on transient failures; protective confirms on destructive actions. + +--- + +## Phase 6 — Data Model Extension: Multiple Blogs per Name + +**How:** + +* Domain relationships: + + * **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog**; keep **Post → Blog** immutable after creation (prevents cross-blog drift). +* Identifiers & URLs: + + * Introduce stable blog handles (human-friendly, unique per name) that compose into routes; add collision policy and normalization rules. +* State management: + + * Represent “current blog” as first-class app state; selector scoping so lists/metrics derive from the active blog. +* Migrations: + + * Backfill: create a default blog per name and attach historical posts; record migration version on the data root to allow repeatable ops. +* UX: + + * Blog switcher in global nav; “create blog” guided flow with handle validation and preview; clearly scoped creation forms. +* Indexing & constraints (backend/API expectations): + + * Unique composite index (name, blog handle); foreign key from posts to blog id; server rejects cross-blog updates. + +--- + +## Phase 7 — Permissions Model: Shared Blogs + +**How:** + +* Role model (lean start): + + * **Owner** (full control), **Editor** (edit any post, manage drafts), **Author** (create/edit own posts), **Viewer** (public, implicit). +* Invitations: + + * Tokenized invite flow with expiration; acceptance binds a name to a role on a blog; owner can revoke. +* Enforcement: + + * All write endpoints check role → operation matrix; UI gates controls (disable/hide) but never trusts the client. +* Attribution & activity: + + * Store author id and updated-by on posts; optional lightweight activity log for edits/publishes; surface attribution in UI. +* Conflicts: + + * Last-writer-wins with ETag/if-match semantics or revision numbers; reject stale writes; small conflict UI with diff view (stretch goal). + +--- + +## Phase 8 — Performance & Resilience + +**How:** + +* Rendering: + + * Keep lists virtualized; hydrate only what’s visible; memoize high-churn components; defer non-critical work with idle callbacks. +* Network: + + * Structured fetch layer with timeouts, abort support, and retry/backoff on idempotent GETs; idempotency keys on create/update to prevent dupes. +* Perceived speed: + + * Optimistic UI where safe (e.g., draft saves); skeletons/spinners with progress semantics; prefetch likely next views. +* Failure handling: + + * Standard error surface (banner/toast + inline details); retry path; offline detection with queued drafts. +* Metrics: + + * Track Web Vitals and action latencies; set budgets (e.g., LCP, INP) and regressions fail CI perf checks (can be a nightly). + +--- + +## Phase 9 — Internationalization & Theming Consistency + +**How:** + +* Strings: + + * Centralize UI strings under a namespace scheme; ensure interpolation is safe; default English bundle with stubs for others. +* Formatting: + + * Use Intl APIs for dates/numbers/plurals; avoid manual formatting; prepare RTL-safe layouts (logical properties). +* Themes: + + * Single source for color/spacing/typography tokens; verify contrast programmatically; support `prefers-color-scheme`. + +--- + +## Phase 10 — Observability, Security & Release Readiness + +**How:** + +* Observability: + + * Error boundaries with user-friendly fallback views; structured client logs (level, code, context) routed to a collector; anonymized action breadcrumbs for repro. +* Security posture: + + * Input validation at boundaries; sanitize rich text; content-security-policy hardening; permission checks at every write; least-privilege tokens. +* Migrations & backups: + + * Versioned migrations with idempotent steps; pre-prod dry run; exportable backup of critical entities; rollback procedure. +* Release discipline: + + * SemVer tags; changelog; migration notes; upgrade guide; “go/no-go” checklist (tests green, a11y checks pass, metrics within budget). + +--- + +## Cross-cutting Technical Principles + +* **Contracts first:** Define data and role contracts before wiring UI; document them in the repo and tests. +* **Small vertical slices:** Ship changes end-to-end (schema → API → state → UI → tests) in thin increments. +* **One source of truth:** Centralize constants (roles, statuses, limits) and import them everywhere. +* **Guardrails by default:** Lint/type/test/a11y checks run locally and in CI; merges require green gates. +* **Telemetry with restraint:** Collect only what’s necessary for quality; feature-flag any new collection. + +--- + +## Assumptions & Risks + +* We control both client and API contracts (or can negotiate them). +* Current data fits a clean migration to per-name default blogs without manual reconciliation. +* Editor content can be sanitized without breaking required formatting. +* Minimal E2E is enough for confidence; we can expand later if flakiness appears. diff --git a/docs/Q-Blog_1.0.0_ROADMAP_implementation.md b/docs/Q-Blog_1.0.0_ROADMAP_implementation.md new file mode 100644 index 0000000..36fe114 --- /dev/null +++ b/docs/Q-Blog_1.0.0_ROADMAP_implementation.md @@ -0,0 +1,329 @@ +**TL;DR:** Below is a phase-by-phase implementation guide: what libraries we’ll use, what kinds of tests we’ll write, what contracts/functions we’ll define, and how we’ll validate each step. Still high-level—no filenames or code dropped—yet detailed enough to steer day-to-day work. + +--- + +# Q-Blog → 1.0.0 Implementation Plan (Technical, High-Level) + +## Phase 0 — Orientation & Quality Bar + +**Libraries / tools** + +* Issue labels & PR templates (tracker-native). +* Metrics: Web Vitals (`web-vitals`), synthetic a11y checks (`axe-core` via CI). + **Artifacts** +* **Quality charter**: SLOs (e.g., “save post success ≥ 99.9%”), accessibility bar (keyboard-only paths pass), perf budgets (LCP/INP). +* **Protected journeys** list: *read post, create post, edit post, manage blogs*. + **Validation** +* Signed-off charter; journeys ratified in a short doc. + +--- + +## Phase 1 — Project Docs & Working Agreements + +**Libraries / tools** + +* Diagrams as code (Mermaid in docs renderer). +* Conventional commits (tooling optional). + **Artifacts** +* **Architecture map** (UI ↔ state ↔ API), **Testing pyramid**, **A11y standard**, **Contributing rules**, **Changelog categories**. +* **Decision Record template** (context → options → decision → impact). + **Validation** +* New contributor dry run: “can run + test + understand contracts in <30 min.” + +--- + +## Phase 2 — Test Harness & Linting Baseline + +**Libraries** + +* Unit/Component: **Vitest**, **@testing-library/react**, **@testing-library/user-event**, **@testing-library/jest-dom**. +* A11y lint/checks: **eslint-plugin-jsx-a11y**, **axe-core** or **jest-axe**. +* Mocking: **MSW** (Mock Service Worker) for API. +* Types: TypeScript strict mode. +* Lint/format: **eslint** (flat config), **@typescript-eslint** plugin, **prettier**. + **Key setup** +* Global test setup (RTL matchers, MSW server, clock/timers). +* Coverage thresholds (e.g., 80/70/70 lines/branches/functions to start). +* CI lanes: install/cache → typecheck → lint → unit/component → (later) e2e smoke. + **Tests to seed** +* Smoke renders: home, post list, editor screen. +* Tiny interaction: typing in editor, enabling/disabling toolbar control, save button disabled until valid. + **Validation** +* Green **typecheck + lint + tests** on clean checkout; coverage reports generated. + +--- + +## Phase 3 — Correctness Sweep + +**Libraries** + +* RTK ecosystem: **@reduxjs/toolkit** (already present), **RTK Query** for data-fetching (adds caching, retries, invalidation). +* Schema/runtime validation: **Zod** (form/io validation) **and** `zod-to-json-schema` for docs **or** **AJV** if we align with JSON Schema like Q-Chess. (Pick one; see “Contracts” below.) + **Contracts / functions** +* **Fetch layer** with cancellation & retries: + + * `fetchJson(req: Request, opts): Promise>` + * `withTimeout(controller, ms): AbortSignal` +* **Async state convention**: `{ status: 'idle'|'loading'|'success'|'error', data?, error? }`. +* **Error object**: `{ code: string; message: string; recoverable: boolean }`. +* **Validation**: + + * For Zod: `const PostDto = z.object({ id: z.string(), ... })` → `parse(response)`. + * For AJV: compile JSON Schemas once; validate per response. + **Work items** +* Remove legacy UI imports (v4 → v5), delete `ts-nocheck` sections by adding minimal types. +* Replace `any` in reducers/selectors and editor props with discriminated unions & precise payloads. +* Normalize empty/loading/error states for all remote views. + **Tests** +* Contract tests: MSW returns both **valid** and **invalid** payloads → validation errors surface as user-friendly messages. +* Reducer tests: transitions for each thunk/query state. + **Validation** +* No suppressed type regions; “known issues” list empty; core routes never hard-crash on bad data. + +--- + +## Phase 4 — Accessibility Baseline + +**Libraries** + +* A11y testing: **jest-axe** or **axe-core** integration; **testing-library** queries by role/name. +* Focus management: rely on MUI’s **Dialog/Popover** focus traps; add **focus-trap** only where needed. + **Contracts / behaviors** +* **Landmarks**: header/nav/main/footer; **Skip link** available and visible on focus. +* **Focus policy**: focus moves to first interactive element on open; returns to invoker on close. +* **Names/roles/states**: toggles expose `aria-pressed`; inputs have programmatic labels; errors are linked with `aria-describedby`. +* **Live regions**: polite updates for “saving…/uploaded/failed”. +* **Motion/contrast**: respect `prefers-reduced-motion`; enforce contrast tokens. + **Tests** +* Keyboard paths for protected journeys (Tab/Shift+Tab order, Escape close). +* axe checks for key pages (no critical violations). +* Live region announces long-running operations (assertions via `toHaveAccessibleDescription` or role queries). + **Validation** +* Keyboard-only success across protected journeys; automated checks pass with zero criticals. + +--- + +## Phase 5 — UX & IA Touch-up + +**Libraries** + +* Router (current choice): ensure route guards & prefetch hooks supported. +* Toasts/banners: lightweight (e.g., notistack or MUI Snackbar pattern). + **Contracts / functions** +* **Design tokens**: spacing, radius, typography, color roles (text, surface, brand) centralized. +* **Common UI patterns**: + + * Empty state contract: `{ icon?, title, body, primaryAction?, secondaryAction? }` + * Error surface: banner + inline guidance; all errors have recovery action. +* **Editor ergonomics**: + + * Toolbar state machine: `computeToolbarState({ selection, schema }) → { boldEnabled, … }` + * Draft lifecycle: `autosaveDraft(postId, content) → { savedAt }` with debounce and “last saved” indicator. + **Tests** +* Token snapshots for theme roles; editor toolbar enable/disable based on selection. +* Error/empty states render consistently across pages (table-driven tests). + **Validation** +* Navigation clarity (first-click discoverability); zero dead ends; consistent affordances. + +--- + +## Phase 6 — Multiple Blogs per Name + +**Libraries** + +* State & data: continue **RTK Query** (cache per blog key). +* Validation: same as Phase 3 (Zod/AJV). + **Domain model** +* **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog** (immutable relationship post-creation). +* **Identifiers**: + + * Blog handle: lowercase, slug rules; unique per **Name**; stored canonical form. + * URL composition: `/{name}/{blogHandle}/…` (conceptual, router-agnostic). + **Functions / contracts** +* `listBlogs(name): Promise` +* `createBlog(input: { handle; title; visibility }): Promise` +* `switchBlog(handle): void` (updates app-scoped “current blog”) +* `listPosts(blogId, filters)`, `createPost(blogId, input)` +* **Migration**: `backfillDefaultBlogs(): MigrationResult` (idempotent: skip if blog exists) + **Tests** +* Migration test with legacy dataset → posts appear under default blog. +* Router/selector scoping: when switching blogs, lists & counts update without leaking data. +* Handle validation: rejects collisions & invalid slugs; normalizes input. + **Validation** +* Users can create/switch blogs; all lists/summaries are scoped; legacy users retained seamlessly. + +--- + +## Phase 7 — Shared Blogs (Collaboration) + +**Libraries** + +* Role/permission helpers: in-house constants; optional **casl** if we want DSL-like permissions (not required). + **Role model** +* **Owner**, **Editor**, **Author**, **Viewer** (implicit). +* Operation matrix: + + * Owner: all + * Editor: edit any post, manage drafts + * Author: CRUD own posts + **Functions / contracts** +* Membership: + + * `inviteMember(blogId, name, role): InviteToken` + * `acceptInvite(token): Membership` + * `removeMember(blogId, name): void` + * `listMembers(blogId): Membership[]` +* Authorization guard: + + * `can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { user, blog, post? }): boolean` +* Attribution: + + * Post metadata: `{ createdBy, updatedBy, updatedAt, revision }` +* Concurrency: + + * `savePost(postId, input, { ifMatchRevision }): { revision }` → 409 on stale; client shows resolve UI. + **Tests** +* Permission matrix table tests (unit): each role × operation result. +* API guard tests via MSW: simulate forbidden responses, assert UI disables and surfaces denial. +* Concurrency tests: stale write attempt returns conflict → user offered resolve path. + **Validation** +* Owner can manage access; editors/authors can work within role; no unauthorized writes pass. + +--- + +## Phase 8 — Performance & Resilience + +**Libraries** + +* **react-virtuoso** or equivalent (already used) for long lists. +* Data layer retries/backoff: **RTK Query**’s retry plugin or custom wrapper. +* RUM metrics: **web-vitals** + lightweight sender. + **Functions / policies** +* **Optimistic updates** where safe (draft save, title edits) with rollback on failure. +* **Retry policy**: GETs exponential backoff; POST/PUT guarded by idempotency keys where applicable. +* **Abortable fetch**: timeouts and user-initiated cancel for uploads. +* **Prefetching**: on-hover/visible prefetch of likely next routes and data keys. + **Tests** +* Latency injection with MSW to verify spinners/skeletons and cancel/abort behaviors. +* Optimistic update rollback test: forced server failure restores previous UI state. + **Validation** +* Subjective feel: interactions seem instant; objective: Web Vitals within budget; error paths recover without data loss. + +--- + +## Phase 9 — Internationalization & Theming Consistency + +**Libraries** + +* **react-i18next** (already used in sister projects). +* Intl APIs for date/number/plural; fallback polyfills if needed. + **Functions / contracts** +* `t('namespace:key', { vars })` usage with explicit, descriptive keys. +* Currency/number/date formatters as wrapper utilities to standardize formatting. +* Theme tokens as a single source of truth; contrast checks (scripted) against WCAG AA. + **Tests** +* Snapshot tests for key screens in both light/dark with token diffs. +* Formatting tests for pluralization and number/date locales. + **Validation** +* Strings externalized; theme passes automated contrast check; RTL-safety spot check on core screens. + +--- + +## Phase 10 — Observability, Security & Release Readiness + +**Libraries** + +* Error tracking: **Sentry** (or equivalent) with source maps. +* Sanitization: **DOMPurify** for any HTML serialization/render of editor output. +* CSP guidance (server-side; document the policy). + **Functions / policies** +* **Error boundaries**: route-level and editor-level with friendly fallback and “copy details”. +* **Client logging**: `logClientError({ code, message, context })` throttle; opt-in breadcrumbs (sanitized, no PII). +* **Security checks**: + + * Validate all inputs client-side with Zod/AJV before sending. + * Sanitize rich text on ingest and display; allowlist for marks/nodes. + * Permission checks every write path; never trust client-side hide/disable alone. +* **Migrations**: + + * Versioned, idempotent; dry-run flag; record migration status. +* **Release discipline**: + + * Version bump + changelog; migration notes; upgrade guide; go/no-go checklist (green gates + budgets met). + **Tests** +* Error boundary renders fallback on thrown child component; telemetry is called with redacted payload. +* XSS tests: paste malicious payload → sanitized output (no script execution) both save and render paths. +* Migration dry-run/effects tests with sample datasets. + **Validation** +* Crashes captured with actionable context; no stored XSS vectors; release checklist green. + +--- + +## Cross-Cutting Standards + +**Contracts-first** + +* Pick **Zod** *(form/input + client response validation, type inference)* or **JSON Schema + AJV** *(cross-project parity with Q-Chess)*. + + * If Zod: generate JSON Schema for docs via `zod-to-json-schema`. + * If AJV: generate TypeScript types via `json-schema-to-ts`. +* Maintain a single **roles/status/constants** module imported across UI, data, and tests. + +**Data access layer** + +* Prefer **RTK Query** for cache, memoized selectors, retries, and invalidation; treat queries/mutations as the one path to remote state. + +**State shaping** + +* Store UI state distinct from server cache; selectors derive view models (small pure functions, unit-tested). + +**Security posture** + +* Default-deny in permission checks; treat editor input as untrusted; output sanitize on every render path. + +**Testing pyramid (target distributions)** + +* Unit (\~60%): pure utils, selectors, reducers, permission guards. +* Component (\~30%): screens/components with RTL (+ axe). +* Integration/E2E (\~10%): happy-path create/edit/publish; blog switch; invite/accept. + +**CI** + +* Node LTS matrix (current + previous). +* Caching for package manager. +* Artifacts: coverage report, axe report, Web Vitals synthetic (if applicable), build output for preview. + +--- + +## Example Function Signatures (illustrative, no filenames) + +```ts +// Networking & validation +type Result = { ok: true; data: T } | { ok: false; error: { code: string; message: string; recoverable: boolean } }; +function fetchJson(input: RequestInfo, init?: RequestInit & { timeoutMs?: number }): Promise>; + +// Permissions +type Role = 'owner' | 'editor' | 'author'; +function can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { role: Role; userId: string; postOwnerId?: string }): boolean; + +// Blog management +function createBlog(input: { handle: string; title: string; visibility: 'public'|'private' }): Promise>; +function inviteMember(blogId: string, name: string, role: Role): Promise>; +function savePost(postId: string, input: PostInput, opts: { ifMatchRevision: number }): Promise>; +``` + +--- + +## Completion Bars (what “done” looks like per category) + +* **Correctness:** zero suppressed type regions; known issues = empty; core flows never hard-crash. +* **Testability:** green typecheck/lint/tests on clean checkout; coverage trend up and stable; fast local watch. +* **Accessibility:** keyboard-only paths pass; axe shows no criticals; live region & focus rules verified. +* **Usability:** consistent patterns for empty/error; editor ergonomics predictable; no dead ends. +* **Security:** sanitized rich text; permission matrix enforced server- and client-side; no privilege escalation paths. +* **Performance:** web vitals within budget; optimistic updates/abortable fetches; graceful failure/retry. + +--- + +**Assumptions & Risks:** +We can evolve API contracts to support multi-blog and roles; legacy data maps cleanly to default blogs; Slate output can be sanitized without losing required formatting; e2e scope remains minimal to avoid CI flake—expand only if signals require it. diff --git a/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md b/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md new file mode 100644 index 0000000..81f124f --- /dev/null +++ b/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md @@ -0,0 +1,158 @@ +**TL;DR:** Here’s a non-technical, stepwise roadmap from today’s v0.0.0 to a polished 1.0.0. It front-loads docs + testing, cleans correctness/a11y/UX, then adds **multi-blog per name**, and finally **shared blogs**. Each phase has clear outcomes and “done” checks. + +--- + +# Q-Blog → 1.0.0 Roadmap (non-technical) + +## Phase 0 — Orientation & Quality Bar (now) + +**Goal:** Align on what “top quality” means. + +* Define success metrics: a11y baseline, crash/error rate, perf feel (snappy interactions), test coverage targets, doc set. +* Pick a short list of user journeys to protect (e.g., read posts, create post, edit post, manage blogs). + **Done when:** +* We have a one-pager of goals/metrics and the protected journeys list. + +--- + +## Phase 1 — Project Docs & Working Agreements + +**Goal:** Make the project legible and predictable. + +* Author concise docs (what/why/how): Overview, Architecture sketch, Testing approach, Accessibility expectations, Contributing, Changelog. +* Record a few early decisions (editor, state, data shapes) as short decision notes. + **Done when:** +* New contributor can read docs and understand how to run, test, and contribute—without asking questions. + +--- + +## Phase 2 — Test Harness & Linting Baseline (no feature work yet) + +**Goal:** Establish guardrails to catch regressions early. + +* Stand up unit/component test harness and a minimal test pyramid strategy (unit > component > a few integration). +* Add accessibility and code-quality lint passes (no rules list yet—just the intent). + **Done when:** +* “Run tests” and “lint” are routine and green on a clean checkout. +* We can write a simple render test for a page without friction (proof the harness works). + +--- + +## Phase 3 — Correctness Sweep (stability before polish) + +**Goal:** Remove known hazards and obvious defects. + +* Replace legacy/broken imports, remove blanket type-ignores, fix crashing edge cases and flaky flows. +* Normalize basic data flows (loading/error/empty states) so the app never “mystery fails.” + **Done when:** +* No known red flags remain (e.g., invalid imports, suppressed type errors, missing alt text). +* Core pages load and operate consistently on a fresh profile. + +--- + +## Phase 4 — Accessibility Baseline (foundational) + +**Goal:** Make the app operable by keyboard and assistive tech. + +* Landmarks & navigation: header/nav/main/footer + skip link. +* Focus management for dialogs/menus/editor; visible focus rings. +* Text alternatives, form labels, live region for async work; motion/contrast preferences respected. + **Done when:** +* Keyboard-only journeys succeed; basic automated checks are clean; a short manual checklist passes. + +--- + +## Phase 5 — UX & Information Architecture Touch-up + +**Goal:** Reduce friction and ambiguity before adding new power. + +* Clear page hierarchy and navigation; consistent button labels and empty/error states. +* Editor ergonomics: predictable toolbar states, undo/redo clarity, draft/publish feedback. + **Done when:** +* The protected journeys feel obvious and forgiving (you can’t get stuck; errors are actionable). + +--- + +## Phase 6 — Data Model Extension: **Multiple Blogs per Name** + +**Goal:** Allow one “name” to own several separate blogs. + +* Concepts (non-technical): **Name** owns 0..N **Blogs**; **Post** belongs to exactly one **Blog**. +* Identity: pick a human-friendly blog identifier (e.g., “name + blog handle”) that works well in URLs. +* UX: a simple “Blog switcher” and “Create new blog” flow; list scoped to current blog. +* Migration: existing content becomes Blog #1 for each name. + **Done when:** +* A user can create/switch blogs and see posts scoped appropriately. +* Existing users’ posts are intact under their first blog without surprises. + +--- + +## Phase 7 — Permissions Model: **Shared Blogs** + +**Goal:** Enable collaboration on a blog. + +* Roles (simple first): **Owner**, **Editor**, **Author** (can post), **Viewer** (implicit, public). +* Invitations & membership management: add/remove collaborators; show who has access. +* Edit safeguards: clear attribution on posts, activity notes on updates (lightweight). + **Done when:** +* Owner can invite/remove collaborators. +* Collaborators can create/edit posts according to role, and non-members cannot. + +--- + +## Phase 8 — Performance & Resilience + +**Goal:** Keep interactions fast and reliable as features grow. + +* Perceived speed: snappy navigation, optimistic updates where safe, progress indicators for async operations. +* Robustness: graceful offline/slow-network handling for key actions; safe retries on saves/uploads. + **Done when:** +* Core actions “feel instant” with clear feedback, and temporary failures recover without data loss. + +--- + +## Phase 9 — Internationalization & Theming Consistency (light) + +**Goal:** Make UI text and visuals consistent and adaptable. + +* Externalize UI strings; ensure headings/labels follow a consistent style. +* Validate light/dark themes for contrast and motion preferences. + **Done when:** +* Strings are centralized for future i18n; themes meet contrast/motion guardrails. + +--- + +## Phase 10 — Observability, Security & Release Readiness + +**Goal:** Ship 1.0 with confidence. + +* Error boundaries and user-friendly error pages; minimal analytics/telemetry (opt-in) to catch crashes. +* Basic security posture review (inputs, uploads, permission checks). +* Backups/migration notes; upgrade guide; crisp release notes. + **Done when:** +* We can trace and act on production errors; permissions are enforced in all write paths; the “1.0 checklist” is fully checked. + +--- + +# Why this order? + +* **Docs + tests first** reduce rework (catch issues as we fix). +* **Correctness → a11y → UX** ensures polish builds on stable, inclusive foundations. +* **Multi-blog** precedes **Shared blogs** so collaboration lands on the right data model. +* **Perf/Resilience** and **Release** bookend features to keep the app trustworthy. + +--- + +## Acceptance bars (numbers keep us honest) + +* Accessibility: keyboard-only passes for protected journeys; automated checks free of critical issues. +* Reliability: zero known crashers in core flows; error states always show recovery actions. +* Testability: green test/lint on clean checkout; steadily rising coverage (we’ll agree targets when we stand up the harness). +* Usability: no dead ends; consistent labels; empty/error states always informative. + +--- + +## On your two feature asks + +* **Multiple blogs per name:** 👍 Fits naturally with Q-Blog’s goals and unlocks better organization. We’ll keep identifiers human-readable and migrate existing content seamlessly. +* **Shared blogs:** 👍 A strong, later-phase addition. We’ll start with a lean role model and keep invites/management simple to avoid UX sprawl. diff --git a/docs/Q-Blog_Project_Instructions.md b/docs/Q-Blog_Project_Instructions.md new file mode 100644 index 0000000..c2e1e95 --- /dev/null +++ b/docs/Q-Blog_Project_Instructions.md @@ -0,0 +1,186 @@ +# Q-Blog — Project Instructions (1.0 Track) +_Generated 2025-08-16 23:37Z_ + +> Default instructions for all Q-Blog chats. Optimize for **correctness, accessibility, collaboration, and a focused writing UX**. Keep answers decisive and artifact‑oriented. + +--- + +## 1) Intent +Q-Blog is a modern blogging workspace for **individuals and small teams**. Writers can own **multiple blogs under one Name**, and enable **Shared Blogs** with roles (Owner/Editor/Author). North star: **inclusive, predictable publishing** with a resilient, testable UI. + +--- + +## 2) Core flows & stable components (props) +**Flows:** Read → Draft → Edit → Publish → Manage Blogs → Collaborate. + +**BlogSwitcher** — select active blog; scopes all lists/forms. +```ts +activeBlog: BlogRef | null +blogs: BlogSummary[] +onCreateBlog: () => void +onSelectBlog: (blog: BlogRef) => void +disabled?: boolean +``` + +**PostEditor** — Slate-based editor with autosave and preview. +```ts +mode: 'create' | 'edit' +blog: BlogRef +value: EditorState +onChange: (s: EditorState) => void +onSaveDraft: (s: EditorState) => Promise +onPublish: (input: PublishInput) => Promise +canPublish: boolean +status: 'idle'|'saving'|'publishing'|'error'|'success' +``` + +**PostList** — virtualized list scoped to active blog. +```ts +blog: BlogRef +filters: PostFilters +onOpenPost: (id: Id) => void +``` + +**MembersPanel** (shared blogs). +```ts +blog: BlogRef +members: Membership[] +onInvite: (name: NameRef, role: Role) => Promise +onRemove: (name: NameRef) => Promise +currentUserRole: Role +``` + +**Header/Nav** — global actions. +```ts +onOpenMembers?: () => void +onCreatePost: () => void +onSwitchBlog: () => void +unreadCount?: number +``` + +**Stable props** change only via coordinated refactor with tests & docs. + +--- + +## 3) Tech & repo posture +React 18 + TypeScript (strict) + Vite; **MUI v5**; Slate editor; **Redux Toolkit + RTK Query**; i18next (strings). +Testing: **Vitest + RTL + user-event + MSW + jest-axe**. +Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. Layout: `src/`, `tests/`, `docs/`, `.gitea/`. + +--- + +## 4) Behavior contracts +**Blog scoping & routing** +- Every view is scoped to **active blog**. Route params encode scope (conceptually `/{name}/{blog}/...`). Changing blog updates lists/forms without leaks. + +**Editor & autosave** +- Debounced autosave writes drafts with **polite live region** announcements. Clear “Last saved” time; explicit **Publish** pathway. Toolbar enable/disable is deterministic from selection. + +**Post lifecycle** +- States: `draft | scheduled | published | archived`. Idempotent publish; show attribution (`createdBy`, `updatedBy`, `updatedAt`). + +**Media** +- Client validates type/size; shows upload progress; failed uploads are retryable; all `` have alt (or `alt=""` if decorative). + +--- + +## 5) Accessibility (A11y) +- Landmarks: header/nav/main/footer; **Skip link** focuses main. One H1 per route. +- Keyboard: all controls reachable; **Esc closes** dialogs/popovers and **restores focus**. +- Names/roles/states: explicit labels; toggles expose `aria-pressed`. +- Live regions: succinct progress for save/publish/upload; no spam. +- Preferences: honor `prefers-reduced-motion`; enforce contrast tokens. +- Tests: axe checks for key pages; keyboard journey tests for protected flows. + +--- + +## 6) Data, permissions & contracts +- **Entities**: Name 1..N Blog; Post 1..1 Blog (immutable link). (`nameId`,`blogHandle`) unique. +- **Roles**: Owner (all), Editor (edit any, manage drafts), Author (own posts). Deny by default. +- **RTK Query** is the only remote path; queries/mutations define cache keys by blog. +- **Validation**: Prefer **Zod** for forms & responses (or JSON Schema + AJV if aligning w/ Q-Chess); failed validation → friendly error with retry path. +- **Concurrency**: optimistic where safe; use revision/ETag to detect conflicts; show resolve UI on 409. + +--- + +## 7) Testing standards +- Register matchers once in `tests/setup.ts`. Use **accessible queries** (`getByRole`, `getByLabelText`). +- Unit: selectors, reducers, guards (`can()`), formatters, sanitizers. +- Component: editor toolbar & autosave, blog switch scoping, lists (virtualized), dialogs focus. +- A11y: axe smoke on read/draft/manage; keyboard-only journeys. +- MSW: success + error + invalid payload paths. Deterministic tests; fake timers for debounce. +- Coverage gate per package; exclude fixtures/build artifacts. + +--- + +## 8) Performance & resilience +- Virtualize long lists; memoize high-churn components. +- Abortable fetch with timeouts; retry/backoff for GETs; idempotency keys for create/update. +- Perceived speed: skeletons & optimistic UI (draft saves); defer non-critical work to idle. +- Offline-friendly drafts: queued writes with clear status and conflict handling. + +--- + +## 9) TypeScript posture +Strict mode, zero `any` in public props; no `@ts-nocheck`. Prefer discriminated unions for async/status shapes. Treat red squiggles as **must fix** before PR. + +--- + +## 10) Security & content safety +- Sanitize editor output **on save and render** (DOMPurify allowlist). Never render untrusted HTML directly. +- Validate inputs at boundaries; never trust client-visible state for authorization (server checks required). +- Error boundaries: route-level and editor-level with friendly fallback + “copy details” action. +- Minimal telemetry for quality (crashes, vitals); no PII. + +--- + +## 11) Internationalization & theming +Centralize strings; use Intl for dates/numbers; prep RTL-safe layouts. Theme tokens (color/spacing/typography) audited for WCAG AA; respect `prefers-color-scheme`. + +--- + +## 12) Docs & decision hygiene +Docs live in `docs/`. Keep: **ARCHITECTURE**, **TESTING**, **ACCESSIBILITY**, **SECURITY**, **USER_JOURNEYS**, **GLOSSARY**, **RISKS**, **ROADMAP_DEPENDENCIES**, **DECISIONS/** (short ADRs). Update docs when behavior or contracts change. + +--- + +## 13) Delivery & workflow (assistants) +- Prepare files locally; share **download links** (send full files once edits exceed a few lines). Keep a resendable copy. +- Prefer thin **vertical slices** (schema→API→state→UI→tests→docs). Include acceptance notes + quick verify. +- If assumptions are needed, state them briefly and proceed with a concrete artifact. + +--- + +## 14) Single source of truth (constants) +Centralize shared constants and import everywhere: roles, statuses, limits (upload sizes), blog handle rules, debounce durations, routes, a11y labels. **UI, data, and tests** must reference the same values. + +--- + +## 15) Versioning & releases +SemVer with human changelogs; each release includes notes, migration steps, and quick verify. CI gates: typecheck, lint (jsx‑a11y), unit/component tests, axe smoke, coverage, build. + +--- + +## 16) Common error → action +- **MUI v4 import fails** → switch to `@mui/material` / `@mui/icons-material`. +- **Unexpected HTML execution** → sanitize on save+render; add unit tests with known XSS vectors. +- **Data shape mismatch** → validation failed: show friendly error; log redacted details; fix schema or endpoint. +- **Keyboard trap** → ensure focus trap + restore; add test. +- **Leaked posts across blogs** → check scoping (selector/query keys) and route params; add failing test then fix. + +--- + +## 17) Checklists +**Before sending anything** +- [ ] No red underlines; strict TS passes. +- [ ] Vite build + **Vitest green** (unit & component); axe smoke passes (no criticals). +- [ ] Dev/test/build configs match (aliases, JSX, TS options). +- [ ] Public props typed; callers match; no `any` in props. +- [ ] Landmarks + labels; toggles use `aria-pressed`; live regions only when meaningful. +- [ ] Lists scoped to active blog; routes stable and shareable. +- [ ] Editor: autosave status visible; publish path explicit; sanitized output. + +**Thin vertical delivery** +- [ ] Code + tests + docs move together. +- [ ] Include acceptance criteria and quick verification steps. +- [ ] If any contract changes, add/adjust a short Decision Record. diff --git a/docs/QUALITY_CHARTER.md b/docs/QUALITY_CHARTER.md new file mode 100644 index 0000000..510cfdc --- /dev/null +++ b/docs/QUALITY_CHARTER.md @@ -0,0 +1,42 @@ +# Q-Blog — Quality Charter (Patch 0) +_Generated 2025-08-16 23:43Z_ + +## Purpose +Define the quality bar for Q-Blog 1.0 and the gates that every change must pass. This charter governs scope, acceptance, and sign-off. + +## North Star +**Inclusive, predictable publishing** with a resilient, testable UI. + +## Protected User Journeys +(See `docs/USER_JOURNEYS.md`; mirrored here for convenience.) +1) Read posts 2) Create post 3) Edit post 4) Manage blogs 5) Collaborate + +## Service Level Objectives (SLOs) +- **Reliability:** Draft save success ≥ **99.9%** P7D; crash-free sessions ≥ **99%**. +- **Accessibility:** Keyboard-only paths pass for protected journeys; **axe** critical violations = **0**. +- **Performance:** P75 **LCP < 2.5 s**, **INP < 200 ms**, **CLS < 0.1** on modern desktop; mobile budgets P75 LCP < 3.2 s, INP < 250 ms. +- **Usability:** No “dead-end” screens; each error has a visible recovery action. +- **Security:** Sanitized rich text on save and render; deny-by-default for writes. +- **Testability:** Coverage gates start **80/70/70** (lines/branches/functions), trend to **90/80/80** by RC. + +## Acceptance Gates (per PR) +- Typecheck strict **green**; ESLint (incl. **jsx-a11y**) **green**; Vitest **green**. +- No new `any` in public props; no `@ts-nocheck` or blanket disables. +- For UI changes: keyboard path verified; aria names/states present; screenshots or notes for key states. +- For data/wire changes: request/response validated (Zod or AJV); error handling user-friendly. +- Docs updated if behavior/contract changes (ADR or section update). + +## Measurement & Observability +- **Local/CI:** Web Vitals synthetic where feasible; axe in tests; coverage artifacts exported in CI. +- **Runtime (opt-in minimal):** Crash/error codes, action breadcrumbs (no PII), vitals sample. Redaction enforced. + +## Roles & Sign-off +- **Quality Owner** (temporary): Project lead (or delegate) confirms gates pass. +- **A11y Owner:** Reviews keyboard + axe results for protected journeys. +- **Security Owner:** Signs off on sanitization and permission-sensitive changes. + +## Out of Scope (1.0) +- Fine-grained per-post ACLs; end-to-end encryption; advanced analytics. + +## Change Control +Any change to this charter or to protected journeys requires an ADR (see template) and project sign-off. diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 0000000..3c3a9ae --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,10 @@ +# Releasing — Q-Blog (Phase 0) + +- Versioning: SemVer; tag via repo UI or script (later phases). +- Artifacts: Vite `dist/` (if configured). +- Quick verify before tag: + - `pnpm typecheck && pnpm lint && pnpm test:run` + - CI green on main + - CHANGELOG updated (later phases) + +This file will evolve in Phase 9–10. diff --git a/docs/RISKS_ASSUMPTIONS.md b/docs/RISKS_ASSUMPTIONS.md new file mode 100644 index 0000000..d397dff --- /dev/null +++ b/docs/RISKS_ASSUMPTIONS.md @@ -0,0 +1,12 @@ +# Q‑Blog — Risks, Assumptions & Mitigations +_Generated 2025-08-16 23:27Z_ + +| ID | Area | Risk/Assumption | Phase | Impact | Mitigation | +|---:|------|------------------|:-----:|--------|------------| +| R1 | Data | Legacy content migration to default blogs may fail on malformed records | 6 | High | Idempotent migrator, dry run, backup + rollback notes | +| R2 | Editor | Rich‑text sanitization strips needed formatting | 10 | Medium | Allowlist tuned with tests; sample content goldens | +| R3 | A11y | Keyboard traps in complex modals/popovers | 4–5 | Medium | Component audits; focus tests; Esc/restore policies | +| R4 | Collab | Permission gaps lead to privilege escalation | 7 | High | Server‑side checks; matrix tests; deny‑by‑default | +| R5 | Perf | Large lists regress INP/LCP | 8 | Medium | Virtualization, prefetch, memoization; vitals budgets | +| A1 | API | We can evolve/extend server contracts | 0 | — | If not, draft shims and versioned adapters | +| A2 | Tooling | CI runners can execute headless browsers for axe/e2e | 2 | — | If flaky, move some checks to nightly | diff --git a/docs/ROADMAP_DEPENDENCIES.md b/docs/ROADMAP_DEPENDENCIES.md new file mode 100644 index 0000000..6827db3 --- /dev/null +++ b/docs/ROADMAP_DEPENDENCIES.md @@ -0,0 +1,19 @@ +# Q‑Blog — Roadmap Dependencies & Milestones +_Generated 2025-08-16 23:27Z_ + +## Dependency Graph (high level) +- Phase 2 depends on Phase 1 docs (testing/a11y standards referenced). +- Phase 3 depends on Phase 2 harness (to verify fixes). +- Phase 4 (a11y) builds on Phase 3 stability. +- Phase 6 (multi‑blog) depends on Phase 5 UX patterns (switcher placement, routing clarity). +- Phase 7 (shared blogs) depends on Phase 6 data model and routing. +- Phase 10 (release) depends on all prior phases plus observability wiring. + +## Milestone Gates +- **M1 — Baseline Ready (P0–P2):** Docs present; typecheck/lint/tests green; coverage report generated. +- **M2 — Stable & Accessible (P3–P4):** No crashers; keyboard‑only journeys pass; axe critical=0. +- **M3 — UX Solid (P5):** Consistent patterns, no dead ends; editor ergonomics audited. +- **M4 — Multi‑Blog (P6):** Create/switch blogs with migrated legacy content. +- **M5 — Shared Blogs (P7):** Role matrix enforced; invites work. +- **M6 — Resilient & Performant (P8–P9):** Vitals within budget; themes/i18n consistent. +- **M7 — Release Ready (P10):** Telemetry, security checks, notes/guides done. diff --git a/docs/SECURITY_PRIVACY_POSTURE.md b/docs/SECURITY_PRIVACY_POSTURE.md new file mode 100644 index 0000000..9fe67b4 --- /dev/null +++ b/docs/SECURITY_PRIVACY_POSTURE.md @@ -0,0 +1,19 @@ +# Q‑Blog — Security & Privacy Posture (1.0) +_Generated 2025-08-16 23:27Z_ + +## Principles +- **Least privilege** — Roles restrict actions; tokens scoped; client never authoritative. +- **Sanitize everywhere** — Rich text sanitized on save and render (allowlist). +- **Fail safe** — On doubt, deny writes; surface clear errors with next actions. +- **Minimal telemetry** — Only crash/quality signals; no PII; user‑visible policy. +- **Defense in depth** — CSP, input validation, dependency hygiene, error boundaries. + +## Non‑Goals (1.0) +- End‑to‑end encryption for content. +- Fine‑grained per‑post ACLs (roles are per blog). + +## Checklist (Dev) +- Inputs validated client‑side; re‑validated server‑side. +- All writes include role checks and revision/ETag for concurrency. +- Sanitization unit tests cover common XSS vectors. +- Dependencies audited; pinned versions for determinism. diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..7bcb886 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,17 @@ +# Testing — Q-Blog (Phase 0) + +## Local +```bash +pnpm typecheck +pnpm lint +pnpm test:run +``` +MSW is enabled in tests; unhandled requests are bypassed. + +## CI (Gitea) +- Workflow: `.gitea/workflows/ci-no-marketplace.yml` +- Matrix: single ubuntu, Node 20.16.0 via tarball +- Steps: typecheck → lint → vitest (coverage) → build (if Vite config exists) + +## a11y smoke +`tests/axe-smoke.test.tsx` runs jest-axe; add more targeted checks as UI grows. diff --git a/docs/USER_JOURNEYS.md b/docs/USER_JOURNEYS.md new file mode 100644 index 0000000..ef402ab --- /dev/null +++ b/docs/USER_JOURNEYS.md @@ -0,0 +1,21 @@ +# Q‑Blog 1.0 — Personas & Key Journeys +_Generated 2025-08-16 23:27Z_ + +## Personas +- **Solo Writer** — Publishes articles and drafts privately before release. +- **Team Owner** — Creates a shared blog and invites collaborators. +- **Invited Author** — Writes posts on a shared blog; cannot manage members. +- **Reader** — Browses and discovers posts; expects fast loads and clear navigation. + +## Protected Journeys (1.0) +1. **Read posts** — Navigate lists → open an article; keyboard and screen reader friendly. +2. **Create post** — Draft, autosave, preview, publish; clear feedback on errors. +3. **Edit post** — Update content; attribution and revision increase predictability. +4. **Manage blogs** — Create/switch blogs; lists scope to the selected blog. +5. **Collaborate** — Invite member; invited author creates/edits within role. + +## Acceptance Hints per Journey +- **Read:** Landmarks in place, heading hierarchy valid, link text descriptive, images have alt. +- **Create/Edit:** Toolbar states deterministic; autosave status surfaced via live region; error can be retried. +- **Manage:** Blog switch affects all scoped views; URLs stable and shareable. +- **Collaborate:** Role gating enforced in UI and API; forbidden paths never succeed. diff --git a/docs/VISION_PRFAQ.md b/docs/VISION_PRFAQ.md new file mode 100644 index 0000000..cb27ca0 --- /dev/null +++ b/docs/VISION_PRFAQ.md @@ -0,0 +1,39 @@ +# Q‑Blog 1.0 — PRFAQ +_Generated 2025-08-16 23:27Z_ + +## Press Release (Narrative) + +Today we announce **Q‑Blog 1.0**, a modern, accessible writing workspace that makes it effortless to publish under multiple blogs with a single name, and to collaborate safely through **Shared Blogs**. Q‑Blog pairs a focused editor with strong correctness, accessibility, and testability so writers can publish confidently, teams can coordinate, and readers can trust what they see. + +Key highlights: +- **Multiple blogs per name** — Organize topics cleanly without creating new accounts. +- **Shared blogs** — Invite editors and authors with role‑based permissions. +- **Inclusive by design** — Keyboard‑ready, screen‑reader friendly, respectful of motion/contrast preferences. +- **Resilient** — Deterministic saves, offline tolerance for drafts, clear recoveries on failure. +- **Transparent** — Human‑readable release notes and an upgrade guide. + +## FAQ + +**What is Q‑Blog? Who is it for?** +A content creation app for individuals and teams who want a fast, accessible, and reliable blogging workflow. + +**How is it different?** +Strong quality bar (tests, a11y), multi‑blog under one name, and safe collaboration via shared blogs—all while keeping the UI simple. + +**Why multiple blogs per name?** +Writers often publish in distinct domains (e.g., dev notes vs. essays). Separate blogs keep audiences clear without fragmenting identity. + +**How do Shared Blogs work?** +Blog owners can invite collaborators and assign roles (**Owner**, **Editor**, **Author**). Permissions are enforced server‑side; the UI mirrors capabilities but never trusts the client alone. + +**What about accessibility?** +We commit to keyboard-only journeys, semantic landmarks, focus management, descriptive names/labels, and honoring user preferences for motion and contrast. + +**How do you keep content safe?** +Rich text is sanitized on save and render (allowlist). Permissions gate every write. Observability captures issues without leaking personal data. + +**How do you measure success?** +Reliability (save success rate), accessibility (keyboard checks + automated audits), performance (Web Vitals), and usability (zero “dead‑end” flows). + +**What’s in 1.0?** +Multi‑blog per name, Shared Blogs, resilient editor workflows, accessibility baseline, docs, and a clear release process. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..72ba9c5 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,31 @@ +// eslint.config.mjs — Q-Blog Phase 0 (flat config) +import js from '@eslint/js' +import tseslint from 'typescript-eslint' +import reactHooks from 'eslint-plugin-react-hooks' +import jsxA11y from 'eslint-plugin-jsx-a11y' + +export default [ + { ignores: ['dist/**', 'coverage/**'] }, + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + parserOptions: { + project: ['./tsconfig.json', './tsconfig.*.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, + plugins: { 'react-hooks': reactHooks, 'jsx-a11y': jsxA11y }, + rules: { + 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + 'jsx-a11y/alt-text': 'error', + 'jsx-a11y/no-autofocus': 'warn', + 'jsx-a11y/label-has-associated-control': 'error', + }, + }, +] diff --git a/issues/001-Adopt_Quality_Charter_(sign-off).md b/issues/001-Adopt_Quality_Charter_(sign-off).md new file mode 100644 index 0000000..9ef74c4 --- /dev/null +++ b/issues/001-Adopt_Quality_Charter_(sign-off).md @@ -0,0 +1,20 @@ +# Adopt Quality Charter (sign-off) + +## Goal +Ratify the Quality Charter and set acceptance gates for Q-Blog 1.0. + +## Tasks +- Review reliability, a11y, perf, security, and testability targets +- Adjust thresholds if needed +- Record sign-off and owners (Quality, A11y, Security) + +## Acceptance +- Charter updated (if needed) and marked signed +- Owners assigned and documented +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: docs · Area: docs · Priority: P0 · Size: S + +**Labels:** docs, P0, S, docs-area +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md b/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md new file mode 100644 index 0000000..b6dbff2 --- /dev/null +++ b/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md @@ -0,0 +1,22 @@ +# Finalize Foundation Docs (Architecture, Testing, Accessibility, Security) + +## Goal +Publish concise, source-of-truth docs to guide development. + +## Includes +- ARCHITECTURE (UI ↔ state ↔ data, Name/Blog/Post model) +- TESTING (pyramid, coverage, fixtures/mocks, MSW policy) +- ACCESSIBILITY (landmarks, focus, live regions, motion/contrast) +- SECURITY & PRIVACY (sanitization, permission checks, CSP posture) +- USER_JOURNEYS & GLOSSARY (personas, invariants) + +## Acceptance +- Docs present in `docs/`, linked from README +- Each doc has a short "How to verify" section +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: docs · Area: docs · Priority: P1 · Size: M + +**Labels:** docs, P1, M, docs-area +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md b/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md new file mode 100644 index 0000000..1a66f96 --- /dev/null +++ b/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md @@ -0,0 +1,21 @@ +# Stand up Test Harness (Vitest + RTL + MSW + jest-axe) + +## Goal +Enable fast, realistic tests with accessibility checks. + +## Tasks +- Configure Vitest (jsdom), alias @→src, coverage thresholds +- Global test setup with RTL, user-event, jest-dom, jest-axe +- MSW server for success/error/invalid payloads +- Seed tests: app smoke, editor minimal, a11y smoke + +## Acceptance +- `pnpm test` runs green; coverage report produced +- At least 3 smoke tests and 1 a11y test in place +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: test · Area: tests · Priority: P0 · Size: M + +**Labels:** test, P0, M, tests +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md b/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md new file mode 100644 index 0000000..adb34f4 --- /dev/null +++ b/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md @@ -0,0 +1,20 @@ +# ESLint Flat Config + jsx-a11y Baseline + +## Goal +Catch accessibility and code-quality issues early. + +## Tasks +- Add flat ESLint config with TypeScript/React/jsx-a11y +- Prettier integration; import/order; testing-library plugin +- Fix initial lint errors (no blanket disables) + +## Acceptance +- `pnpm lint` green on clean checkout +- No `@ts-nocheck`; no new `any` in public props +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: chore · Area: build · Priority: P0 · Size: S + +**Labels:** chore, P0, S, build +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md b/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md new file mode 100644 index 0000000..c9aa07c --- /dev/null +++ b/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md @@ -0,0 +1,20 @@ +# Correctness Sweep I — MUI v4 imports & ts-nocheck removal + +## Goal +Remove known correctness hazards blocking development. + +## Targets +- Replace `@material-ui/*` imports with `@mui/*` +- Remove `@ts-nocheck` in editor and app; add minimal types +- Ensure app still builds/boots after changes + +## Acceptance +- No remaining v4 imports; no ts-nocheck in core paths +- Smoke tests green; manual run shows editor screen +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: fix · Area: editor · Priority: P0 · Size: M + +**Labels:** fix, P0, M, editor +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md b/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md new file mode 100644 index 0000000..68bde5b --- /dev/null +++ b/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md @@ -0,0 +1,20 @@ +# Correctness Sweep II — Type hygiene in reducers/selectors + +## Goal +Reduce `any` usage and adopt consistent async state shapes. + +## Tasks +- Replace common `any` with discriminated unions/interfaces +- Standardize async lifecycles (idle/loading/success/error) +- Normalize error object shape (code/message/recoverable) + +## Acceptance +- Reducer/selector tests added +- No new `any` in reducers/selectors; stricter types compile +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: fix · Area: state · Priority: P1 · Size: M + +**Labels:** fix, P1, M, state +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md b/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md new file mode 100644 index 0000000..2bed6bd --- /dev/null +++ b/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md @@ -0,0 +1,20 @@ +# Correctness Sweep III — Empty/Loading/Error states standardization + +## Goal +Users never experience a “mystery fail.” + +## Tasks +- Implement consistent empty/loading/error components +- Wire to data flows across list/detail/editor +- Ensure retry paths for transient failures + +## Acceptance +- Error surfaces include a recovery action +- Smoke tests cover all three states on a list screen +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: fix · Area: lists · Priority: P1 · Size: M + +**Labels:** fix, P1, M, lists +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/adopt-quality-charter--sign-off-.md b/issues/adopt-quality-charter--sign-off-.md new file mode 100644 index 0000000..9ef74c4 --- /dev/null +++ b/issues/adopt-quality-charter--sign-off-.md @@ -0,0 +1,20 @@ +# Adopt Quality Charter (sign-off) + +## Goal +Ratify the Quality Charter and set acceptance gates for Q-Blog 1.0. + +## Tasks +- Review reliability, a11y, perf, security, and testability targets +- Adjust thresholds if needed +- Record sign-off and owners (Quality, A11y, Security) + +## Acceptance +- Charter updated (if needed) and marked signed +- Owners assigned and documented +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: docs · Area: docs · Priority: P0 · Size: S + +**Labels:** docs, P0, S, docs-area +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md b/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md new file mode 100644 index 0000000..c9aa07c --- /dev/null +++ b/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md @@ -0,0 +1,20 @@ +# Correctness Sweep I — MUI v4 imports & ts-nocheck removal + +## Goal +Remove known correctness hazards blocking development. + +## Targets +- Replace `@material-ui/*` imports with `@mui/*` +- Remove `@ts-nocheck` in editor and app; add minimal types +- Ensure app still builds/boots after changes + +## Acceptance +- No remaining v4 imports; no ts-nocheck in core paths +- Smoke tests green; manual run shows editor screen +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: fix · Area: editor · Priority: P0 · Size: M + +**Labels:** fix, P0, M, editor +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md b/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md new file mode 100644 index 0000000..68bde5b --- /dev/null +++ b/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md @@ -0,0 +1,20 @@ +# Correctness Sweep II — Type hygiene in reducers/selectors + +## Goal +Reduce `any` usage and adopt consistent async state shapes. + +## Tasks +- Replace common `any` with discriminated unions/interfaces +- Standardize async lifecycles (idle/loading/success/error) +- Normalize error object shape (code/message/recoverable) + +## Acceptance +- Reducer/selector tests added +- No new `any` in reducers/selectors; stricter types compile +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: fix · Area: state · Priority: P1 · Size: M + +**Labels:** fix, P1, M, state +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md b/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md new file mode 100644 index 0000000..2bed6bd --- /dev/null +++ b/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md @@ -0,0 +1,20 @@ +# Correctness Sweep III — Empty/Loading/Error states standardization + +## Goal +Users never experience a “mystery fail.” + +## Tasks +- Implement consistent empty/loading/error components +- Wire to data flows across list/detail/editor +- Ensure retry paths for transient failures + +## Acceptance +- Error surfaces include a recovery action +- Smoke tests cover all three states on a list screen +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: fix · Area: lists · Priority: P1 · Size: M + +**Labels:** fix, P1, M, lists +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/eslint-flat-config---jsx-a11y-baseline.md b/issues/eslint-flat-config---jsx-a11y-baseline.md new file mode 100644 index 0000000..adb34f4 --- /dev/null +++ b/issues/eslint-flat-config---jsx-a11y-baseline.md @@ -0,0 +1,20 @@ +# ESLint Flat Config + jsx-a11y Baseline + +## Goal +Catch accessibility and code-quality issues early. + +## Tasks +- Add flat ESLint config with TypeScript/React/jsx-a11y +- Prettier integration; import/order; testing-library plugin +- Fix initial lint errors (no blanket disables) + +## Acceptance +- `pnpm lint` green on clean checkout +- No `@ts-nocheck`; no new `any` in public props +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: chore · Area: build · Priority: P0 · Size: S + +**Labels:** chore, P0, S, build +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md b/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md new file mode 100644 index 0000000..b6dbff2 --- /dev/null +++ b/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md @@ -0,0 +1,22 @@ +# Finalize Foundation Docs (Architecture, Testing, Accessibility, Security) + +## Goal +Publish concise, source-of-truth docs to guide development. + +## Includes +- ARCHITECTURE (UI ↔ state ↔ data, Name/Blog/Post model) +- TESTING (pyramid, coverage, fixtures/mocks, MSW policy) +- ACCESSIBILITY (landmarks, focus, live regions, motion/contrast) +- SECURITY & PRIVACY (sanitization, permission checks, CSP posture) +- USER_JOURNEYS & GLOSSARY (personas, invariants) + +## Acceptance +- Docs present in `docs/`, linked from README +- Each doc has a short "How to verify" section +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: docs · Area: docs · Priority: P1 · Size: M + +**Labels:** docs, P1, M, docs-area +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/issues/issues_patch0_phase1-3.json b/issues/issues_patch0_phase1-3.json new file mode 100644 index 0000000..af266bc --- /dev/null +++ b/issues/issues_patch0_phase1-3.json @@ -0,0 +1,74 @@ +{ + "issues": [ + { + "title": "Adopt Quality Charter (sign-off)", + "body": "## Goal\nRatify the Quality Charter and set acceptance gates for Q-Blog 1.0.\n\n## Tasks\n- Review reliability, a11y, perf, security, and testability targets\n- Adjust thresholds if needed\n- Record sign-off and owners (Quality, A11y, Security)\n\n## Acceptance\n- Charter updated (if needed) and marked signed\n- Owners assigned and documented\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: docs \u00b7 Area: docs \u00b7 Priority: P0 \u00b7 Size: S", + "labels": [ + "docs", + "P0", + "S", + "docs-area" + ] + }, + { + "title": "Finalize Foundation Docs (Architecture, Testing, Accessibility, Security)", + "body": "## Goal\nPublish concise, source-of-truth docs to guide development.\n\n## Includes\n- ARCHITECTURE (UI \u2194 state \u2194 data, Name/Blog/Post model)\n- TESTING (pyramid, coverage, fixtures/mocks, MSW policy)\n- ACCESSIBILITY (landmarks, focus, live regions, motion/contrast)\n- SECURITY & PRIVACY (sanitization, permission checks, CSP posture)\n- USER_JOURNEYS & GLOSSARY (personas, invariants)\n\n## Acceptance\n- Docs present in `docs/`, linked from README\n- Each doc has a short \"How to verify\" section\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: docs \u00b7 Area: docs \u00b7 Priority: P1 \u00b7 Size: M", + "labels": [ + "docs", + "P1", + "M", + "docs-area" + ] + }, + { + "title": "Stand up Test Harness (Vitest + RTL + MSW + jest-axe)", + "body": "## Goal\nEnable fast, realistic tests with accessibility checks.\n\n## Tasks\n- Configure Vitest (jsdom), alias @\u2192src, coverage thresholds\n- Global test setup with RTL, user-event, jest-dom, jest-axe\n- MSW server for success/error/invalid payloads\n- Seed tests: app smoke, editor minimal, a11y smoke\n\n## Acceptance\n- `pnpm test` runs green; coverage report produced\n- At least 3 smoke tests and 1 a11y test in place\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: test \u00b7 Area: tests \u00b7 Priority: P0 \u00b7 Size: M", + "labels": [ + "test", + "P0", + "M", + "tests" + ] + }, + { + "title": "ESLint Flat Config + jsx-a11y Baseline", + "body": "## Goal\nCatch accessibility and code-quality issues early.\n\n## Tasks\n- Add flat ESLint config with TypeScript/React/jsx-a11y\n- Prettier integration; import/order; testing-library plugin\n- Fix initial lint errors (no blanket disables)\n\n## Acceptance\n- `pnpm lint` green on clean checkout\n- No `@ts-nocheck`; no new `any` in public props\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: chore \u00b7 Area: build \u00b7 Priority: P0 \u00b7 Size: S", + "labels": [ + "chore", + "P0", + "S", + "build" + ] + }, + { + "title": "Correctness Sweep I \u2014 MUI v4 imports & ts-nocheck removal", + "body": "## Goal\nRemove known correctness hazards blocking development.\n\n## Targets\n- Replace `@material-ui/*` imports with `@mui/*`\n- Remove `@ts-nocheck` in editor and app; add minimal types\n- Ensure app still builds/boots after changes\n\n## Acceptance\n- No remaining v4 imports; no ts-nocheck in core paths\n- Smoke tests green; manual run shows editor screen\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: fix \u00b7 Area: editor \u00b7 Priority: P0 \u00b7 Size: M", + "labels": [ + "fix", + "P0", + "M", + "editor" + ] + }, + { + "title": "Correctness Sweep II \u2014 Type hygiene in reducers/selectors", + "body": "## Goal\nReduce `any` usage and adopt consistent async state shapes.\n\n## Tasks\n- Replace common `any` with discriminated unions/interfaces\n- Standardize async lifecycles (idle/loading/success/error)\n- Normalize error object shape (code/message/recoverable)\n\n## Acceptance\n- Reducer/selector tests added\n- No new `any` in reducers/selectors; stricter types compile\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: fix \u00b7 Area: state \u00b7 Priority: P1 \u00b7 Size: M", + "labels": [ + "fix", + "P1", + "M", + "state" + ] + }, + { + "title": "Correctness Sweep III \u2014 Empty/Loading/Error states standardization", + "body": "## Goal\nUsers never experience a \u201cmystery fail.\u201d\n\n## Tasks\n- Implement consistent empty/loading/error components\n- Wire to data flows across list/detail/editor\n- Ensure retry paths for transient failures\n\n## Acceptance\n- Error surfaces include a recovery action\n- Smoke tests cover all three states on a list screen\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: fix \u00b7 Area: lists \u00b7 Priority: P1 \u00b7 Size: M", + "labels": [ + "fix", + "P1", + "M", + "lists" + ] + } + ] +} \ No newline at end of file diff --git a/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md b/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md new file mode 100644 index 0000000..1a66f96 --- /dev/null +++ b/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md @@ -0,0 +1,21 @@ +# Stand up Test Harness (Vitest + RTL + MSW + jest-axe) + +## Goal +Enable fast, realistic tests with accessibility checks. + +## Tasks +- Configure Vitest (jsdom), alias @→src, coverage thresholds +- Global test setup with RTL, user-event, jest-dom, jest-axe +- MSW server for success/error/invalid payloads +- Seed tests: app smoke, editor minimal, a11y smoke + +## Acceptance +- `pnpm test` runs green; coverage report produced +- At least 3 smoke tests and 1 a11y test in place +- See docs/QUALITY_CHARTER.md. + +## Labels +Type: test · Area: tests · Priority: P0 · Size: M + +**Labels:** test, P0, M, tests +**Milestone:** Patch 0 — Orientation & Quality Bar diff --git a/milestone_patch0.json b/milestone_patch0.json new file mode 100644 index 0000000..e680ca3 --- /dev/null +++ b/milestone_patch0.json @@ -0,0 +1,5 @@ +{ + "title": "Patch 0 \u2014 Orientation & Quality Bar", + "description": "# Patch 0 \u2014 Orientation & Quality Bar\n_Generated 2025-08-16 23:43Z_\n\n## Goal\nRatify the Quality Charter and spin up the basic governance scaffolding before touching code.\n\n## Tasks\n1. **Adopt Quality Charter** \u2014 review, adjust targets if needed, and sign.\n2. **Label set** \u2014 create canonical labels in the tracker (Area, Type, Priority, Size).\n3. **Templates** \u2014 add Issue + PR templates with acceptance sections and a11y/security checks.\n4. **Milestone** \u2014 create `Patch 0` milestone with this checklist as description.\n5. **Backlog triage** \u2014 seed initial issues for Phase 1\u20133 planning (docs, harness, correctness).\n\n## Label Set (proposed)\n- **Type:** feat, fix, chore, docs, test, a11y, perf, security\n- **Area:** editor, lists, blogs, members, routing, state, build, tests, docs\n- **Priority:** P0, P1, P2\n- **Size:** XS, S, M, L, XL\n\n## Acceptance\n- Charter signed; templates merged; labels exist; milestone created; 5\u20138 seeded issues for Phase 1\u20133.\n", + "state": "open" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 15ce57f..bf40b19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,17 +40,68 @@ "ts-key-enum": "^2.0.12" }, "devDependencies": { + "@eslint/js": "^9.33.0", "@mui/types": "^7.2.3", - "@types/react": "^18.0.28", + "@testing-library/jest-dom": "^6.7.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.3.0", + "@types/react": "^18.3.23", "@types/react-copy-to-clipboard": "^5.0.4", - "@types/react-dom": "^18.0.11", + "@types/react-dom": "^18.3.7", "@vitejs/plugin-react-swc": "^3.2.0", - "prettier": "^2.8.6", - "typescript": "^4.9.3", + "@vitest/coverage-v8": "^3.2.4", + "axe-core": "^4.10.3", + "eslint": "^9.33.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react-hooks": "^5.2.0", + "jest-axe": "^10.0.0", + "jsdom": "^26.1.0", + "msw": "^2.10.5", + "prettier": "^2.8.8", + "typescript": "^4.9.5", + "typescript-eslint": "^8.39.1", "vite": "^4.2.0", + "vitest": "^3.2.4", "worker-loader": "^3.0.8" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -141,17 +192,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -170,9 +223,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -225,18 +282,190 @@ } }, "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.7.2" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, + "node_modules/@bundled-es-modules/tough-cookie/node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.10.6", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", @@ -375,6 +604,23 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", @@ -631,6 +877,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", @@ -647,6 +910,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", @@ -663,6 +943,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", @@ -727,6 +1024,160 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@formatjs/ecma402-abstract": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", @@ -771,6 +1222,236 @@ "tslib": "^2.4.0" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@inquirer/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@inquirer/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@internationalized/date": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.1.0.tgz", @@ -804,17 +1485,55 @@ "@swc/helpers": "^0.4.14" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/resolve-uri": { @@ -825,14 +1544,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", @@ -845,17 +1556,19 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@juggle/resize-observer": { @@ -863,6 +1576,24 @@ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, + "node_modules/@mswjs/interceptors": { + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.6.tgz", + "integrity": "sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@mui/base": { "version": "5.0.0-alpha.121", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.121.tgz", @@ -1104,6 +1835,80 @@ "react": "^17.0.0 || ^18.0.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -1437,6 +2242,293 @@ "node": ">=14" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.3.41", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.41.tgz", @@ -1631,6 +2723,139 @@ "tslib": "^2.4.0" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.7.0.tgz", + "integrity": "sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", @@ -1675,10 +2900,11 @@ "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==" }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.14.191", @@ -1686,11 +2912,14 @@ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" }, "node_modules/@types/node": { - "version": "18.15.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz", - "integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "devOptional": true, - "peer": true + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -1703,12 +2932,12 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -1722,12 +2951,13 @@ } }, "node_modules/@types/react-dom": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", - "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, - "dependencies": { - "@types/react": "*" + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/react-grid-layout": { @@ -1754,16 +2984,283 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", + "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/type-utils": "8.39.1", + "@typescript-eslint/utils": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.39.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", + "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", + "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.39.1", + "@typescript-eslint/types": "^8.39.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", + "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", + "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", + "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/utils": "8.39.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", + "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", + "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.39.1", + "@typescript-eslint/tsconfig-utils": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", + "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", + "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.39.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitejs/plugin-react-swc": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", @@ -1776,6 +3273,128 @@ "vite": "^4" } }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -1952,11 +3571,11 @@ "peer": true }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "peer": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1974,6 +3593,26 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1999,6 +3638,45 @@ "ajv": "^6.9.1" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -2010,6 +3688,169 @@ "node": ">=4" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", + "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.29", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2023,6 +3864,32 @@ "node": ">=4" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", @@ -2033,6 +3900,16 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2067,6 +3944,13 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -2081,6 +3965,30 @@ "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -2117,6 +4025,66 @@ "dev": true, "peer": true }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2150,6 +4118,23 @@ ], "peer": true }, + "node_modules/chai": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2171,6 +4156,16 @@ "node": ">=0.8.0" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -2186,6 +4181,120 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -2239,11 +4348,28 @@ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -2267,6 +4393,21 @@ "node": ">=10" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -2285,17 +4426,114 @@ "postcss-value-parser": "^4.0.2" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2306,6 +4544,66 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2314,6 +4612,27 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/direction": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", @@ -2336,6 +4655,14 @@ "redux": "^4.2.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2345,6 +4672,28 @@ "csstype": "^3.0.2" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.4.334", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz", @@ -2352,6 +4701,13 @@ "dev": true, "peer": true }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -2375,6 +4731,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2383,6 +4752,95 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -2390,6 +4848,66 @@ "dev": true, "peer": true }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/esbuild": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", @@ -2432,7 +4950,6 @@ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -2448,6 +4965,126 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -2462,12 +5099,175 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -2480,7 +5280,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -2495,6 +5294,33 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/estree-walker/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -2505,17 +5331,87 @@ "node": ">=0.8.x" } }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-selector": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", @@ -2527,11 +5423,62 @@ "node": ">= 12" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -2551,6 +5498,39 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -2565,11 +5545,12 @@ } }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2579,9 +5560,145 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } }, "node_modules/glob-to-regexp": { "version": "0.4.1", @@ -2590,6 +5707,32 @@ "dev": true, "peer": true }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -2598,6 +5741,36 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2605,6 +5778,23 @@ "dev": true, "peer": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -2616,6 +5806,19 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2624,6 +5827,84 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -2637,6 +5918,77 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -2666,6 +6018,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/intl-messageformat": { "version": "10.3.3", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.3.tgz", @@ -2677,11 +6064,65 @@ "tslib": "^2.4.0" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-blob": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", @@ -2693,6 +6134,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -2704,11 +6175,174 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-hotkey": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", @@ -2717,6 +6351,617 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-axe": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-10.0.0.tgz", + "integrity": "sha512-9QR0M7//o5UVRnEUUm68IsGapHrcKGakYy9dKWWMX79LmeUKguDI6DREyljC5I13j78OUmtKLF5My6ccffLFBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "axe-core": "4.10.2", + "chalk": "4.1.2", + "jest-matcher-utils": "29.2.2", + "lodash.merge": "4.6.2" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/jest-axe/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-axe/node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-axe/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-axe/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-axe/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-axe/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-axe/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", + "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -2763,6 +7008,59 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -2774,6 +7072,13 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -2785,6 +7090,13 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2797,6 +7109,66 @@ "node": ">=6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -2842,6 +7214,22 @@ "lie": "3.1.1" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2852,6 +7240,13 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2863,6 +7258,79 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2870,6 +7338,30 @@ "dev": true, "peer": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2889,6 +7381,39 @@ "node": ">= 0.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -2898,15 +7423,78 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msw": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.5.tgz", + "integrity": "sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.39.1", + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -2914,6 +7502,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -2928,6 +7523,13 @@ "dev": true, "peer": true }, + "node_modules/nwsapi": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2936,6 +7538,170 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2964,11 +7730,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -2977,6 +7800,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/philliplm-react-modern-audio-player": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/philliplm-react-modern-audio-player/-/philliplm-react-modern-audio-player-1.4.6.tgz", @@ -2997,10 +7837,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -3013,10 +7854,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -3026,12 +7877,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -3042,11 +7898,22 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, - "node_modules/prettier": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz", - "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, @@ -3057,6 +7924,44 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3077,15 +7982,57 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3373,6 +8320,20 @@ "react-dom": ">=16 || >=17 || >= 18" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", @@ -3389,11 +8350,72 @@ "redux": "^4" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/reselect": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", @@ -3423,6 +8445,17 @@ "node": ">=4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "3.19.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz", @@ -3439,6 +8472,57 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3460,6 +8544,61 @@ ], "peer": true }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -3494,6 +8633,19 @@ "compute-scroll-into-view": "^1.0.20" } }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -3504,11 +8656,83 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/short-unique-id": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz", @@ -3518,6 +8742,102 @@ "suid": "bin/short-unique-id" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/slate": { "version": "0.91.4", "resolved": "https://registry.npmjs.org/slate/-/slate-0.91.4.tgz", @@ -3569,10 +8889,11 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -3598,6 +8919,268 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/styled-components": { "version": "5.3.9", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", @@ -3659,6 +9242,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -3723,6 +9313,47 @@ } } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", @@ -3733,12 +9364,129 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, "engines": { - "node": ">=4" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, "node_modules/toggle-selection": { @@ -3746,6 +9494,45 @@ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-key-enum": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.12.tgz", @@ -3756,11 +9543,116 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3769,6 +9661,66 @@ "node": ">=4.2.0" } }, + "node_modules/typescript-eslint": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz", + "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.39.1", + "@typescript-eslint/parser": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/utils": "8.39.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -3805,6 +9757,17 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -3862,6 +9825,1287 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite-node/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-node/node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "node_modules/vite-node/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite-node/node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vitest/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "node_modules/vitest/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -3881,6 +11125,16 @@ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.2.tgz", "integrity": "sha512-aPAU4OADQsyH8mIw2nXmoni8KHo8s1f1bd5ZUrxhN4P/VMWd+oPDqEwA01XPSEfasAJW6mZ/EHQ2bZ9nOWRrNw==" }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/webpack": { "version": "5.76.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", @@ -3939,6 +11193,175 @@ "node": ">=10.13.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/worker-loader": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", @@ -3959,6 +11382,176 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -3966,9 +11559,128 @@ "engines": { "node": ">= 6" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true + }, + "@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "requires": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -4035,14 +11747,14 @@ } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" }, "@babel/highlight": { "version": "7.18.6", @@ -4055,9 +11767,12 @@ } }, "@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==" + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", + "requires": { + "@babel/types": "^7.28.2" + } }, "@babel/runtime": { "version": "7.21.0", @@ -4095,15 +11810,98 @@ } }, "@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" } }, + "@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true + }, + "@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "requires": { + "cookie": "^0.7.2" + } + }, + "@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "requires": { + "statuses": "^2.0.1" + } + }, + "@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "requires": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + }, + "dependencies": { + "tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + } + } + }, + "@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true + }, + "@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "requires": {} + }, + "@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "requires": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + } + }, + "@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "requires": {} + }, + "@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true + }, "@emotion/babel-plugin": { "version": "11.10.6", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", @@ -4223,6 +12021,13 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, + "@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "dev": true, + "optional": true + }, "@esbuild/android-arm": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", @@ -4335,6 +12140,13 @@ "dev": true, "optional": true }, + "@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "dev": true, + "optional": true + }, "@esbuild/netbsd-x64": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", @@ -4342,6 +12154,13 @@ "dev": true, "optional": true }, + "@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "dev": true, + "optional": true + }, "@esbuild/openbsd-x64": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", @@ -4349,6 +12168,13 @@ "dev": true, "optional": true }, + "@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "dev": true, + "optional": true + }, "@esbuild/sunos-x64": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", @@ -4377,6 +12203,102 @@ "dev": true, "optional": true }, + "@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true + }, + "@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, + "@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true + }, + "@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" + } + }, + "@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true + }, + "@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true + }, + "@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "requires": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + } + }, "@formatjs/ecma402-abstract": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", @@ -4421,6 +12343,144 @@ "tslib": "^2.4.0" } }, + "@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true + }, + "@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "requires": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "dependencies": { + "@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true + } + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true + }, + "@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "requires": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + } + }, + "@inquirer/core": { + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", + "dev": true, + "requires": { + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true + }, + "@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "dev": true, + "requires": {} + }, "@internationalized/date": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.1.0.tgz", @@ -4454,14 +12514,42 @@ "@swc/helpers": "^0.4.14" } }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "@jridgewell/resolve-uri": { @@ -4469,11 +12557,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" - }, "@jridgewell/source-map": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", @@ -4486,17 +12569,17 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@juggle/resize-observer": { @@ -4504,6 +12587,20 @@ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" }, + "@mswjs/interceptors": { + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.6.tgz", + "integrity": "sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==", + "dev": true, + "requires": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + } + }, "@mui/base": { "version": "5.0.0-alpha.121", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.121.tgz", @@ -4605,6 +12702,61 @@ "react-is": "^18.2.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true + }, + "@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "requires": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@popperjs/core": { "version": "2.11.6", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", @@ -4856,6 +13008,152 @@ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==" }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "dev": true, + "optional": true + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "@swc/core": { "version": "1.3.41", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.41.tgz", @@ -4952,6 +13250,101 @@ "tslib": "^2.4.0" } }, + "@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "dependencies": { + "aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "peer": true, + "requires": { + "dequal": "^2.0.3" + } + } + } + }, + "@testing-library/jest-dom": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.7.0.tgz", + "integrity": "sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "dependencies": { + "dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + } + } + }, + "@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, + "@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "requires": {} + }, + "@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "peer": true + }, + "@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "requires": { + "@types/deep-eql": "*" + } + }, + "@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, "@types/eslint": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", @@ -4996,9 +13389,9 @@ "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==" }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "@types/lodash": { @@ -5007,11 +13400,13 @@ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" }, "@types/node": { - "version": "18.15.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.5.tgz", - "integrity": "sha512-Ark2WDjjZO7GmvsyFFf81MXuGTA/d6oP38anyxWOL6EREyBKAxKoFHwBhaZxCfLRLpO8JgVXwqOwSwa7jRcjew==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "devOptional": true, - "peer": true + "requires": { + "undici-types": "~7.10.0" + } }, "@types/parse-json": { "version": "4.0.0", @@ -5024,12 +13419,11 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "@types/react": { - "version": "18.0.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.28.tgz", - "integrity": "sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==", + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "requires": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -5043,13 +13437,11 @@ } }, "@types/react-dom": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", - "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, - "requires": { - "@types/react": "*" - } + "requires": {} }, "@types/react-grid-layout": { "version": "1.3.2", @@ -5075,16 +13467,168 @@ "@types/react": "*" } }, - "@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + "@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true + }, + "@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true }, "@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, + "@typescript-eslint/eslint-plugin": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", + "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/type-utils": "8.39.1", + "@typescript-eslint/utils": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "dependencies": { + "ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true + } + } + }, + "@typescript-eslint/parser": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", + "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/project-service": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", + "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", + "dev": true, + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.39.1", + "@typescript-eslint/types": "^8.39.1", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", + "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1" + } + }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", + "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", + "dev": true, + "requires": {} + }, + "@typescript-eslint/type-utils": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", + "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/utils": "8.39.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + } + }, + "@typescript-eslint/types": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", + "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", + "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", + "dev": true, + "requires": { + "@typescript-eslint/project-service": "8.39.1", + "@typescript-eslint/tsconfig-utils": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/visitor-keys": "8.39.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", + "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.39.1", + "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", + "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.39.1", + "eslint-visitor-keys": "^4.2.1" + } + }, "@vitejs/plugin-react-swc": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", @@ -5094,6 +13638,91 @@ "@swc/core": "^1.3.35" } }, + "@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + } + }, + "@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "requires": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + } + }, + "@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "requires": { + "tinyrainbow": "^2.0.0" + } + }, + "@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "requires": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + } + }, + "@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "requires": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + } + }, + "@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "requires": { + "tinyspy": "^4.0.3" + } + }, + "@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "requires": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -5270,11 +13899,10 @@ "peer": true }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "peer": true + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true }, "acorn-import-assertions": { "version": "1.8.0", @@ -5284,6 +13912,19 @@ "peer": true, "requires": {} }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5303,6 +13944,29 @@ "dev": true, "requires": {} }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -5311,6 +13975,120 @@ "color-convert": "^1.9.0" } }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true + }, + "array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + } + }, + "array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + } + }, + "array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + } + }, + "array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + } + }, + "arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + } + }, + "assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "ast-v8-to-istanbul": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", + "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.29", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + }, + "dependencies": { + "js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + } + } + }, + "async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5321,6 +14099,21 @@ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true + }, "axios": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", @@ -5331,6 +14124,12 @@ "proxy-from-env": "^1.1.0" } }, + "axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true + }, "babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -5358,6 +14157,12 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -5369,6 +14174,25 @@ "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" }, + "brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, "browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -5389,6 +14213,44 @@ "dev": true, "peer": true }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5406,6 +14268,19 @@ "dev": true, "peer": true }, + "chai": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "dev": true, + "requires": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -5423,6 +14298,12 @@ } } }, + "check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -5435,6 +14316,86 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, "clsx": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", @@ -5482,11 +14443,23 @@ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, "convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true + }, "copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -5507,6 +14480,17 @@ "yaml": "^1.10.0" } }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", @@ -5522,17 +14506,122 @@ "postcss-value-parser": "^4.0.2" } }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "requires": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + } + }, "csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, "requires": { - "ms": "2.1.2" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + } + }, + "data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + } + }, + "data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + } + }, + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true + }, + "deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "delayed-stream": { @@ -5540,6 +14629,19 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "peer": true + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, "direction": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", @@ -5555,6 +14657,13 @@ "redux": "^4.2.0" } }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "peer": true + }, "dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -5564,6 +14673,23 @@ "csstype": "^3.0.2" } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, "electron-to-chromium": { "version": "1.4.334", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz", @@ -5571,6 +14697,12 @@ "dev": true, "peer": true }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -5588,6 +14720,12 @@ "tapable": "^2.2.0" } }, + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5596,6 +14734,80 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + } + }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, "es-module-lexer": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", @@ -5603,6 +14815,47 @@ "dev": true, "peer": true }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, + "es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "requires": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + } + }, "esbuild": { "version": "0.17.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", @@ -5637,14 +14890,166 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "peer": true + "dev": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, + "eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "dependencies": { + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "requires": {} + }, + "eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "requires": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + } + }, + "eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "requires": {} + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -5656,12 +15061,45 @@ "estraverse": "^4.1.1" } }, + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + }, + "espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "requires": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + } + }, + "esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "peer": true, "requires": { "estraverse": "^5.2.0" }, @@ -5670,8 +15108,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true + "dev": true } } }, @@ -5682,6 +15119,29 @@ "dev": true, "peer": true }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + }, + "dependencies": { + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + } + } + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5689,17 +15149,71 @@ "dev": true, "peer": true }, + "expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "requires": { + "flat-cache": "^4.0.0" + } + }, "file-selector": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", @@ -5708,16 +15222,70 @@ "tslib": "^2.4.0" } }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + } + }, + "flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true + }, "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "requires": { + "is-callable": "^1.2.7" + } + }, + "foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + } + }, "form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -5729,16 +15297,124 @@ } }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } }, "glob-to-regexp": { "version": "0.4.1", @@ -5752,6 +15428,22 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, + "globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "requires": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -5759,6 +15451,18 @@ "dev": true, "peer": true }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5767,11 +15471,65 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true + }, "hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -5787,6 +15545,56 @@ } } }, + "html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "requires": { + "whatwg-encoding": "^3.1.1" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -5806,6 +15614,29 @@ "resolve-from": "^4.0.0" } }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + } + }, "intl-messageformat": { "version": "10.3.3", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.3.tgz", @@ -5817,16 +15648,65 @@ "tslib": "^2.4.0" } }, + "is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" }, + "is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "requires": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "requires": { + "has-bigints": "^1.0.2" + } + }, "is-blob": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==" }, + "is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, "is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -5835,16 +15715,517 @@ "has": "^1.0.3" } }, + "is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + } + }, + "is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, "is-hotkey": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true + }, + "is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + } + }, + "is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + } + }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.16" + } + }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true + }, + "is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + } + }, + "istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "jest-axe": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-10.0.0.tgz", + "integrity": "sha512-9QR0M7//o5UVRnEUUm68IsGapHrcKGakYy9dKWWMX79LmeUKguDI6DREyljC5I13j78OUmtKLF5My6ccffLFBg==", + "dev": true, + "requires": { + "axe-core": "4.10.2", + "chalk": "4.1.2", + "jest-matcher-utils": "29.2.2", + "lodash.merge": "4.6.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-matcher-utils": { + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", + "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.2.1", + "jest-get-type": "^29.2.0", + "pretty-format": "^29.2.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -5881,11 +16262,54 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "requires": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + } + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -5897,12 +16321,64 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + } + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "requires": { + "language-subtag-registry": "^0.3.20" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -5942,6 +16418,15 @@ "lie": "3.1.1" } }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -5952,6 +16437,12 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5960,6 +16451,60 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "loupe": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "dev": true + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "peer": true + }, + "magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5967,6 +16512,22 @@ "dev": true, "peer": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -5980,20 +16541,79 @@ "mime-db": "1.52.0" } }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, "moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "msw": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.5.tgz", + "integrity": "sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A==", + "dev": true, + "requires": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.39.1", + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + } + }, + "mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true }, "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "neo-async": { @@ -6010,11 +16630,122 @@ "dev": true, "peer": true }, + "nwsapi": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "dev": true + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + } + }, + "object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + } + }, + "object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, + "outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true + }, + "own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6034,16 +16765,65 @@ "lines-and-columns": "^1.1.6" } }, + "parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "requires": { + "entities": "^6.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true + }, "philliplm-react-modern-audio-player": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/philliplm-react-modern-audio-player/-/philliplm-react-modern-audio-player-1.4.6.tgz", @@ -6060,9 +16840,9 @@ } }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "picomatch": { @@ -6070,15 +16850,21 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true + }, "postcss": { - "version": "8.4.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", - "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, "postcss-value-parser": { @@ -6086,12 +16872,46 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, - "prettier": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.6.tgz", - "integrity": "sha512-mtuzdiBbHwPEgl7NxWlqOkithPyp4VN93V7VeHVWBF+ad3I5avc0RVDT4oImXQy9H/AqxA2NSQH8pSxHW6FYbQ==", + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "peer": true + }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + } + } + }, "prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6114,10 +16934,31 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } + }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, "randombytes": { @@ -6302,6 +17143,16 @@ "integrity": "sha512-x0DeGmVAVOVaTXRMG7jzrHBwK7+dkt7n0G3tNmZXphQUBgkVBYuZoaJltQeZGFN42++3XvrgwStKCtmzgMJ0lA==", "requires": {} }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, "redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", @@ -6316,11 +17167,53 @@ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", "requires": {} }, + "reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + } + }, "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "reselect": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", @@ -6341,6 +17234,12 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true + }, "rollup": { "version": "3.19.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz", @@ -6350,6 +17249,34 @@ "fsevents": "~2.3.2" } }, + "rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6357,6 +17284,42 @@ "dev": true, "peer": true }, + "safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + } + }, + "safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -6384,6 +17347,12 @@ "compute-scroll-into-view": "^1.0.20" } }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true + }, "serialize-javascript": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", @@ -6394,16 +17363,128 @@ "randombytes": "^2.1.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + } + }, + "set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + } + }, "shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "short-unique-id": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz", "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw==" }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, "slate": { "version": "0.91.4", "resolved": "https://registry.npmjs.org/slate/-/slate-0.91.4.tgz", @@ -6444,9 +17525,9 @@ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, "source-map-support": { @@ -6469,6 +17550,186 @@ } } }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true + }, + "std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true + }, + "stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + } + }, + "strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + } + }, + "string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + } + }, + "string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true + } + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "requires": { + "js-tokens": "^9.0.1" + }, + "dependencies": { + "js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + } + } + }, "styled-components": { "version": "5.3.9", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", @@ -6511,6 +17772,12 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -6545,6 +17812,37 @@ "terser": "^5.16.5" } }, + "test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", @@ -6555,16 +17853,115 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "to-fast-properties": { + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "requires": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + } + } + }, + "tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true + }, + "tinyrainbow": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true + }, + "tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true + }, + "tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "requires": { + "tldts-core": "^6.1.86" + } + }, + "tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } }, "toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" }, + "tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "requires": { + "tldts": "^6.1.32" + } + }, + "tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + }, "ts-key-enum": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.12.tgz", @@ -6575,12 +17972,116 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true + }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + } + }, + "typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + } + }, + "typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "requires": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + } + }, "typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, + "typescript-eslint": { + "version": "8.39.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz", + "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "8.39.1", + "@typescript-eslint/parser": "8.39.1", + "@typescript-eslint/typescript-estree": "8.39.1", + "@typescript-eslint/utils": "8.39.1" + } + }, + "unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + } + }, + "undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "devOptional": true + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + }, "update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -6601,6 +18102,16 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -6620,6 +18131,584 @@ "rollup": "^3.18.0" } }, + "vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "requires": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "dev": true, + "optional": true + }, + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "requires": { + "@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" + } + }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + }, + "rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "@types/estree": "1.0.8", + "fsevents": "~2.3.2" + } + }, + "vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "requires": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "fsevents": "~2.3.3", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + } + } + } + }, + "vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "requires": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "dependencies": { + "@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "dev": true, + "optional": true + }, + "@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "requires": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + } + }, + "esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "requires": { + "@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" + } + }, + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true + }, + "rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "@types/estree": "1.0.8", + "fsevents": "~2.3.2" + } + }, + "vite": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "dev": true, + "requires": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "fsevents": "~2.3.3", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" + } + } + } + }, + "w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "requires": { + "xml-name-validator": "^5.0.0" + } + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -6636,6 +18725,12 @@ "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.2.tgz", "integrity": "sha512-aPAU4OADQsyH8mIw2nXmoni8KHo8s1f1bd5ZUrxhN4P/VMWd+oPDqEwA01XPSEfasAJW6mZ/EHQ2bZ9nOWRrNw==" }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, "webpack": { "version": "5.76.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", @@ -6676,6 +18771,117 @@ "dev": true, "peer": true }, + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true + }, + "whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "requires": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "requires": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + } + }, + "which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + } + }, + "which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "requires": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + } + }, + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + }, + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, "worker-loader": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", @@ -6686,10 +18892,178 @@ "schema-utils": "^3.0.0" } }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "requires": {} + }, + "xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true } } } diff --git a/package.json b/package.json index 2be0b27..e37d33f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,13 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "lint": "eslint .", + "format": "prettier --check .", + "format:write": "prettier --write .", + "test": "vitest", + "test:run": "vitest run", + "typecheck": "tsc -p tsconfig.json -noEmit" }, "dependencies": { "@emotion/react": "^11.10.6", @@ -41,14 +47,30 @@ "ts-key-enum": "^2.0.12" }, "devDependencies": { + "@eslint/js": "^9.33.0", "@mui/types": "^7.2.3", - "@types/react": "^18.0.28", + "@testing-library/jest-dom": "^6.7.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.3.0", + "@types/react": "^18.3.23", "@types/react-copy-to-clipboard": "^5.0.4", - "@types/react-dom": "^18.0.11", + "@types/react-dom": "^18.3.7", "@vitejs/plugin-react-swc": "^3.2.0", - "prettier": "^2.8.6", - "typescript": "^4.9.3", + "@vitest/coverage-v8": "^3.2.4", + "axe-core": "^4.10.3", + "eslint": "^9.33.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react-hooks": "^5.2.0", + "jest-axe": "^10.0.0", + "jsdom": "^26.1.0", + "msw": "^2.10.5", + "prettier": "^2.8.8", + "typescript": "^4.9.5", + "typescript-eslint": "^8.39.1", "vite": "^4.2.0", + "vitest": "^3.2.4", "worker-loader": "^3.0.8" } } diff --git a/pr/phase0.md b/pr/phase0.md new file mode 100644 index 0000000..f3e2883 --- /dev/null +++ b/pr/phase0.md @@ -0,0 +1,23 @@ +# Phase 0 — Orientation & Quality Bar + +**Summary** +- Add CI workflow (no marketplace actions) +- Establish lint/format/test baseline +- Seed a11y smoke + MSW harness +- Tracker hygiene scripts + +**Changes** +- ESLint flat + jsx-a11y, Prettier, EditorConfig +- Vitest config (JSDOM, coverage, @ alias) +- tests: setup + a11y smoke + MSW +- tracker scripts: verify / labels dedupe / milestone rename +- CI: `.gitea/workflows/ci-no-marketplace.yml` +- Repo hygiene: `.nvmrc`, `.npmrc`, CODEOWNERS, CONTRIBUTING + +**How to verify** +```bash +nvm use +pnpm install +pnpm typecheck && pnpm lint && pnpm test:run +``` +Confirm CI runs on PR and shows typecheck/lint/test/build steps. diff --git a/qblog-phase-file-guide.md b/qblog-phase-file-guide.md new file mode 100644 index 0000000..2df50a3 --- /dev/null +++ b/qblog-phase-file-guide.md @@ -0,0 +1,217 @@ +# Q-Blog Phase-by-Phase File Guide + +_Generated: 2025-08-16T23:24:16.656998Z_ + +This guide lists files to **edit** and **create** for each roadmap phase, with a short rationale per item. It aligns Q-Blog with conventions from Q-Place and Q-Chess. + +## Phase 0 + +### Edit +- _No edits in this phase._ + +### Create +- **docs/QUALITY_CHARTER.md** — Quality goals, SLOs, protected journeys; living doc. + +## Phase 1 + +### Edit +- **README.md** — If present: align quickstart/run/test steps with new tooling; link core docs. + +### Create +- **docs/ARCHITECTURE.md** — High-level UI/state/data map; domain model for Name/Blog/Post. +- **docs/TESTING.md** — Pyramid, coverage thresholds, fixtures/mocks guidance. +- **docs/ACCESSIBILITY.md** — Landmarks, focus/focus return, live regions, motion/contrast, keyboard checklist. +- **docs/CONTRIBUTING.md** — Branch/commit, PR, code review; conventions adopted from Q-Place/Q-Chess. +- **docs/DECISIONS/ADR-0001-editor-stack.md** — Why Slate; sanitation constraints; alternatives considered. +- **docs/DECISIONS/ADR-0002-data-validation.md** — Choose Zod or JSON Schema/AJV; consequences for types/docs. +- **CHANGELOG.md** — Human-readable changes; keepers for releases. + +## Phase 2 + +### Edit +- **package.json** — Add scripts (typecheck/lint/test/coverage), devDeps (Vitest/RTL/MSW/axe), align TS/Vite versions. +- **tsconfig.json** — Enable strict, noImplicitAny; add path alias '@/*' to 'src/*'. +- **vite.config.ts** — Add @ alias; ensure React plugin; keep SWC or standard plugin as chosen. + +### Create +- **eslint.config.js** — Flat ESLint (TS, React, jsx-a11y); import/order; testing-library plugin. +- **vitest.config.ts** — jsdom env, alias '@'→ 'src', coverage thresholds, setup file. +- **tests/setup.ts** — RTL matchers, MSW server, jest-axe/axe; test-time polyfills. +- **tests/smoke/app.smoke.test.tsx** — Top-level route rendering smoke test. +- **tests/a11y/home.a11y.test.tsx** — axe checks for home and one form page. + +## Phase 3 + +### Edit +- **src/components/editor/BlogEditor.tsx** — Replace @material-ui/* with @mui/* equivalents; fix icon imports. +- **src/App.tsx** — Remove '@ts-nocheck'; add minimal types or local @ts-expect-error with rationale. +- **src/components/editor/BlogEditor.tsx** — Remove '@ts-nocheck'; add minimal types or local @ts-expect-error with rationale. +- **src/components/AudioElement.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/DynamicHeightItem.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/DynamicHeightItemMinimal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/FileElement.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/AudioPlayer.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/AudioPublishModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/Comments/Comment.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/Comments/CommentEditor.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/Comments/CommentSection.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/ContextMenu/ContextMenuResource.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/DownloadTaskManager.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/GenericPublishModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/ImagePanel.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/PollPanel.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/PollWidget.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/PostPublishModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/PublishAudio.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/PublishGeneric.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/PublishVideo.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/Tipping/Tipping.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/UserNavbar/UserNavbar.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/VideoPlayer.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/common/VideoPublishModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/editor/BlogEditor.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/editor/ReadOnlySlate.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/layout/Navbar/Navbar.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/modals/EditBlogModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/modals/PublishBlogModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/components/modals/ReusableModal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/global.d.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/hooks/useFetchPosts.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/interfaces/interfaces.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/main.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/BlogIndividualPost/BlogIndividualPost.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/BlogList/PostPreview.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/CreatePost/CreatePostBuilder.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/CreatePost/CreatePostMinimal.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/CreatePost/components/Navbar/NavbarBuilder.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/EditPost/EditPost.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/state/features/blogSlice.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/state/features/globalSlice.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/state/features/mailSlice.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/utils/checkStructure.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/utils/extractTextFromSlate.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/utils/fetchMail.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/utils/fetchPosts.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/utils/qortalRequestFunctions.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/utils/toBase64.ts** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/wrappers/DownloadWrapper.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/wrappers/GlobalWrapper.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. +- **src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx** — Remove or guard console.log behind debug util. +- **src/wrappers/GlobalWrapper.tsx** — Remove or guard console.log behind debug util. +- **src/components/FileElement.tsx** — Remove or guard console.log behind debug util. +- **src/utils/fetchMail.ts** — Remove or guard console.log behind debug util. +- **src/hooks/useIframe.tsx** — Remove or guard console.log behind debug util. +- **src/components/common/PollWidget.tsx** — Remove eslint-disable; fix rule (deps/exhaustive-deps, etc.). +- **src/wrappers/GlobalWrapper.tsx** — Wrap app with ErrorBoundary; verify provider order; remove logs. +- **index.html** — Verify lang, meta, and root element a11y (no functional change). + +### Create +- **src/components/system/ErrorBoundary.tsx** — Top-level error boundary with fallback UI and error reporting hook. + +## Phase 4 + +### Edit +- **src/pages/BlogIndividualPost/BlogIndividualPost.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/pages/CreatePost/CreatePostMinimal.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/pages/CreatePost/CreatePostBuilder.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/pages/EditPost/EditPost.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/components/common/ImagePanel.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/components/common/ResponsiveImage.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/components/common/Tipping/Tipping.tsx** — Add meaningful alt text or alt='' for decorative images. +- **src/App.tsx** — Ensure page landmarks and skip link mount; set document titles per route. +- **src/components/common/*** — Normalize labels, aria-* states; ensure focus-visible rings remain visible. +- **src/components/modals/*** — Trap focus, restore on close; Esc to dismiss. + +### Create +- **src/components/a11y/SkipToContentLink.tsx** — Visible on focus; anchors main content. +- **src/components/a11y/LiveRegion.tsx** — Polite status updates (saving, uploaded, failed). +- **tests/a11y/keyboard.journeys.test.tsx** — Tab order, focus trap/restore across protected journeys. + +## Phase 5 + +### Edit +- **src/components/layout/Header.tsx** — Clarify nav, add Blog switch entry placeholder, consistent naming. +- **src/components/layout/Nav.tsx** — Primary/secondary nav separation; active route indication. +- **src/components/editor/Toolbar.tsx** — Deterministic enable/disable states; accessible names. +- **src/components/editor/EditorSurface.tsx** — Autosave status UI, debounced save feedback. +- **src/pages/*** — Consistent empty/error state components with clear actions. + +### Create +- **src/components/feedback/Toast.tsx** — Unified success/error toasts (MUI Snackbar pattern). +- **src/components/states/EmptyState.tsx** — Shared empty-state component with title/body/actions. +- **src/components/states/ErrorBanner.tsx** — Shared error banner with retry hook. +- **tests/ui/patterns.test.tsx** — Ensure consistent empty/error/toast patterns across pages. + +## Phase 6 + +### Edit +- **src/pages/PostList.tsx** — Scope list to current blog; update queries/selectors. +- **src/pages/CreatePost.tsx** — Bind creation to current blog; default blog auto-select. +- **src/wrappers/GlobalWrapper.tsx** — Provide current blog context from state for children. +- **src/routes/*** — Reflect blog handle in routes (router params). +- **docs/ARCHITECTURE.md** — Update domain model and routing implications. + +### Create +- **src/features/blogs/domain.ts** — Blog entity types; handle normalization/validation. +- **src/features/blogs/api.ts** — listBlogs/createBlog endpoints (RTK Query). +- **src/features/blogs/state.ts** — Current blog state, selectors, actions. +- **src/components/blogs/BlogSwitcher.tsx** — Switcher in header; shows current blog; keyboard-friendly. +- **src/components/blogs/CreateBlogDialog.tsx** — Create flow with handle validation preview. +- **tests/blogs/blog-switcher.test.tsx** — Switcher interaction and scoping of lists. +- **tests/blogs/migration.test.ts** — Legacy data migration to default blog per name. + +## Phase 7 + +### Edit +- **src/components/posts/PostEditor.tsx** — Gate edit/publish controls by role; show attribution. +- **src/components/posts/PostListItem.tsx** — Show author/updatedBy; role-sensitive actions. +- **docs/DECISIONS/ADR-0002-permissions-model.md** — Fill in finalized operation matrix and tradeoffs. + +### Create +- **src/features/permissions/roles.ts** — Role constants and operation matrix. +- **src/features/permissions/can.ts** — `can(op, ctx)` guard utilities. +- **src/features/memberships/api.ts** — invite/accept/remove/list endpoints. +- **src/pages/BlogMembers.tsx** — Membership management UI, Owner-only actions. +- **tests/permissions/matrix.test.ts** — Table-driven role × operation tests. +- **tests/permissions/ui-guards.test.tsx** — Controls hidden/disabled for insufficient roles. + +## Phase 8 + +### Edit +- **src/components/lists/VirtualizedList.tsx** — Ensure virtualization for long lists (react-virtuoso). +- **src/features/*/api.ts** — Adopt retry/backoff for idempotent GET; idempotency keys for mutations where applicable. +- **src/components/uploads/*** — Make uploads abortable; show progress via LiveRegion. + +### Create +- **src/lib/net/fetchJson.ts** — Abortable fetch with timeout and structured errors. +- **src/lib/net/retry.ts** — Backoff strategy; idempotency helpers. +- **src/components/states/SkeletonList.tsx** — Skeletons for perceived speed on lists. +- **tests/perf/optimistic-updates.test.ts** — Rollback on failure; consistent UI after retry. + +## Phase 9 + +### Edit +- **src/theme/tokens.ts** — Centralize color/spacing/typography tokens; enforce contrast. +- **src/components/layout/*** — Replace literals with t('…'); ensure logical properties for RTL prep. + +### Create +- **src/i18n/index.ts** — i18next init; namespace scaffolding. +- **src/i18n/en/common.json** — Default strings. +- **tests/i18n/formatting.test.ts** — Plural/date/number formatting sanity. + +## Phase 10 + +### Edit +- **src/components/editor/Renderer.tsx** — Sanitize HTML on render. +- **src/components/editor/Serializer.ts** — Sanitize on save/serialize pipeline. +- **docs/QUALITY_CHARTER.md** — Mark budgets and acceptance as met; note metrics. + +### Create +- **src/components/system/RouteErrorBoundary.tsx** — Route-level error boundaries with friendly fallback. +- **src/lib/logging/clientLog.ts** — Client error/event logger with throttling. +- **src/lib/security/sanitizeHtml.ts** — DOMPurify wrapper with allowlist for editor output. +- **docs/RELEASE_NOTES.md** — Narrative changes per release. +- **docs/UPGRADE_GUIDE.md** — Breaking changes and migration steps. +- **.gitea/workflows/ci.yml** — Install/cache, typecheck, lint, tests, coverage artifacts. diff --git a/qblog-structure-and-edit-plan.md b/qblog-structure-and-edit-plan.md new file mode 100644 index 0000000..d9a3139 --- /dev/null +++ b/qblog-structure-and-edit-plan.md @@ -0,0 +1,225 @@ +# Q-Blog Structure & Edit Plan + +_Generated: 2025-08-16T23:19:33.635026Z_ + +## Conventions reference (from sister apps) +- Q-Place/Q-Chess use **eslint.config.js**, **vitest.config.ts**, **tests/setup.ts**, and a **docs/** folder with architecture/decisions. +- CI lives under **.gitea/**; tests run with Vitest + React Testing Library; a11y linting via jsx-a11y and axe checks. + +## Full file list (current repository) +```text +.gitignore +.prettierrc.json +index.html +package-lock.json +package.json +public/favicon.ico +src/App.tsx +src/assets/img/arrr.png +src/assets/img/btc.png +src/assets/img/dgb.png +src/assets/img/doge.png +src/assets/img/ltc.png +src/assets/img/qBlogLogo.png +src/assets/img/qort.png +src/assets/img/rvn.png +src/assets/react.svg +src/assets/svgs/AccountCircleSVG.tsx +src/assets/svgs/AlignCenterSVG.tsx +src/assets/svgs/AlignLeftSVG.tsx +src/assets/svgs/AlignRightSVG.tsx +src/assets/svgs/BoldSVG.tsx +src/assets/svgs/CodeBlockSVG.tsx +src/assets/svgs/H2SVG.tsx +src/assets/svgs/H3SVG.tsx +src/assets/svgs/ItalicSVG.tsx +src/assets/svgs/LinkSVG.tsx +src/assets/svgs/NewWindowSVG.tsx +src/assets/svgs/UnderlineSVG.tsx +src/assets/svgs/accountCircle.svg +src/assets/svgs/interfaces.ts +src/components/AudioElement.tsx +src/components/DynamicHeightItem.tsx +src/components/DynamicHeightItemMinimal.tsx +src/components/FileElement.tsx +src/components/common/AudioPanel.tsx +src/components/common/AudioPlayer.tsx +src/components/common/AudioPublishModal.tsx +src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts +src/components/common/BlockedNamesModal/BlockedNamesModal.tsx +src/components/common/Comments/Comment.tsx +src/components/common/Comments/CommentEditor.tsx +src/components/common/Comments/CommentSection.tsx +src/components/common/ContextMenu/ContextMenuResource.tsx +src/components/common/CustomIcon.tsx +src/components/common/DownloadTaskManager.tsx +src/components/common/DraggableResizableGrid.tsx +src/components/common/ErrorBoundary.tsx +src/components/common/FilePanel.tsx +src/components/common/GenericPublishModal.tsx +src/components/common/ImagePanel.tsx +src/components/common/ImageUploader.tsx +src/components/common/LazyLoad.tsx +src/components/common/Notification/Notification.tsx +src/components/common/PageLoader.tsx +src/components/common/PollPanel.tsx +src/components/common/PollWidget.tsx +src/components/common/Portal.tsx +src/components/common/PostPublishModal.tsx +src/components/common/PublishAudio.tsx +src/components/common/PublishGeneric.tsx +src/components/common/PublishVideo.tsx +src/components/common/ResponsiveImage.tsx +src/components/common/Tipping/Tipping.tsx +src/components/common/UserNavbar/UserNavbar-styles.ts +src/components/common/UserNavbar/UserNavbar.tsx +src/components/common/VideoContent.tsx +src/components/common/VideoPanel.tsx +src/components/common/VideoPlayer.tsx +src/components/common/VideoPublishModal.tsx +src/components/editor/BlogEditor.css +src/components/editor/BlogEditor.tsx +src/components/editor/ReadOnlySlate.tsx +src/components/editor/customTypes.ts +src/components/layout/Navbar/Navbar-styles.ts +src/components/layout/Navbar/Navbar.tsx +src/components/modals/ConsentModal.tsx +src/components/modals/EditBlogModal.tsx +src/components/modals/PublishBlogModal.tsx +src/components/modals/ReusableModal.tsx +src/constants/mail.ts +src/global.d.ts +src/hooks/useFetchPosts.tsx +src/hooks/useIframe.tsx +src/index.css +src/index.d.ts +src/interfaces/interfaces.ts +src/main.tsx +src/pages/BlogIndividualPost/BlogIndividualPost.tsx +src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx +src/pages/BlogList/BlogList.tsx +src/pages/BlogList/PostPreview-styles.ts +src/pages/BlogList/PostPreview.tsx +src/pages/CreateEditProfile/CreatEditProfile.tsx +src/pages/CreatePost/CreatePost-styles.ts +src/pages/CreatePost/CreatePost.tsx +src/pages/CreatePost/CreatePostBuilder.tsx +src/pages/CreatePost/CreatePostMinimal.tsx +src/pages/CreatePost/components/Navbar/NavbarBuilder.tsx +src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx +src/pages/EditPost/EditPost.tsx +src/pages/Home/Home.tsx +src/state/features/authSlice.ts +src/state/features/blogSlice.ts +src/state/features/globalSlice.ts +src/state/features/mailSlice.ts +src/state/features/notificationsSlice.ts +src/state/store.ts +src/styles/fonts/Cairo.ttf +src/styles/fonts/Cambon-Light.ttf +src/styles/fonts/Catamaran.ttf +src/styles/fonts/Oxygen.ttf +src/styles/fonts/Raleway.ttf +src/styles/theme.ts +src/utils/blogIdformats.ts +src/utils/checkAndUpdatePost.tsx +src/utils/checkStructure.ts +src/utils/extractTextFromSlate.ts +src/utils/fetchMail.ts +src/utils/fetchPosts.ts +src/utils/qortalRequestFunctions.ts +src/utils/time.ts +src/utils/toBase64.ts +src/vite-env.d.ts +src/webworkers/decodeBase64.js +src/webworkers/getBlogWorker.js +src/wrappers/DownloadWrapper.tsx +src/wrappers/GlobalWrapper.tsx +tsconfig.json +vite.config.ts +``` + +## Files to EDIT (with reasons) +- **index.html** — Verify a11y root attributes and mount point; no functional change expected. +- **package.json** — Add test/lint scripts; add Vitest/RTL/axe; upgrade TypeScript/Vite to match sister apps; add eslint/jsx-a11y. +- **src/App.tsx** — Remove '@ts-nocheck'; add minimal types/props. +- **src/components/AudioElement.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/DynamicHeightItem.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/DynamicHeightItemMinimal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/FileElement.tsx** — Reduce ': any' usage; add precise types for props/state/selectors.; Remove stray 'console.log' or guard behind debug flag. +- **src/components/common/AudioPlayer.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/AudioPublishModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/Comments/Comment.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/Comments/CommentEditor.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/Comments/CommentSection.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/ContextMenu/ContextMenuResource.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/DownloadTaskManager.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/GenericPublishModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/ImagePanel.tsx** — Ensure has alt (or alt='') and semantic context.; Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/PollPanel.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/PollWidget.tsx** — Reduce ': any' usage; add precise types for props/state/selectors.; Remove 'eslint-disable' and fix underlying lint issues. +- **src/components/common/PostPublishModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/PublishAudio.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/PublishGeneric.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/PublishVideo.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/ResponsiveImage.tsx** — Ensure has alt (or alt='') and semantic context. +- **src/components/common/Tipping/Tipping.tsx** — Ensure has alt (or alt='') and semantic context.; Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/UserNavbar/UserNavbar.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/VideoPlayer.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/common/VideoPublishModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/editor/BlogEditor.tsx** — Reduce ': any' usage; add precise types for props/state/selectors.; Remove '@ts-nocheck'; add minimal types/props.; Replace MUI v4 import with '@mui/material' (or icons). +- **src/components/editor/ReadOnlySlate.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/layout/Navbar/Navbar.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/modals/EditBlogModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/modals/PublishBlogModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/components/modals/ReusableModal.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/global.d.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/hooks/useFetchPosts.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/hooks/useIframe.tsx** — Remove stray 'console.log' or guard behind debug flag. +- **src/interfaces/interfaces.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/main.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/BlogIndividualPost/BlogIndividualPost.tsx** — Ensure has alt (or alt='') and semantic context.; Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx** — Reduce ': any' usage; add precise types for props/state/selectors.; Remove stray 'console.log' or guard behind debug flag. +- **src/pages/BlogList/PostPreview.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/CreatePost/CreatePostBuilder.tsx** — Ensure has alt (or alt='') and semantic context.; Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/CreatePost/CreatePostMinimal.tsx** — Ensure has alt (or alt='') and semantic context.; Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/CreatePost/components/Navbar/NavbarBuilder.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/pages/EditPost/EditPost.tsx** — Ensure has alt (or alt='') and semantic context.; Reduce ': any' usage; add precise types for props/state/selectors. +- **src/state/features/blogSlice.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/state/features/globalSlice.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/state/features/mailSlice.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/utils/checkStructure.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/utils/extractTextFromSlate.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/utils/fetchMail.ts** — Reduce ': any' usage; add precise types for props/state/selectors.; Remove stray 'console.log' or guard behind debug flag. +- **src/utils/fetchPosts.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/utils/qortalRequestFunctions.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/utils/toBase64.ts** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/wrappers/DownloadWrapper.tsx** — Reduce ': any' usage; add precise types for props/state/selectors. +- **src/wrappers/GlobalWrapper.tsx** — Reduce ': any' usage; add precise types for props/state/selectors.; Remove stray 'console.log' or guard behind debug flag.; Replace console logs; verify provider order; add error boundary if missing. +- **tsconfig.json** — Enable strict mode; add path aliases; align compiler options with sister apps. +- **vite.config.ts** — Add path alias '@' → 'src'; keep React SWC; verify base path. + +## Files to RENAME +- **src/pages/CreateEditProfile/CreatEditProfile.tsx → src/pages/CreateEditProfile/CreateEditProfile.tsx** — Fix spelling; align filename with folder and imports. + +## New files to ADD (to align with Q-Place/Q-Chess) +- **eslint.config.js** — Flat ESLint config with TypeScript, React, jsx-a11y; shared rules. +- **vitest.config.ts** — Vitest setup (jsdom), coverage thresholds, alias '@' to 'src'. +- **tests/setup.ts** — Global test setup: RTL matchers, MSW, jest-axe/axe-core. +- **tests/smoke/app.smoke.test.tsx** — Smoke render of top-level routes (sanity). +- **tests/a11y/home.a11y.test.tsx** — axe checks for key pages; keyboard path checks. +- **docs/ARCHITECTURE.md** — UI ↔ state ↔ data flows; blog/post domain model. +- **docs/ACCESSIBILITY.md** — Landmarks, focus policy, live regions, motion/contrast. +- **docs/TESTING.md** — Pyramid, coverage policy, fixtures/mocks guidance. +- **docs/DECISIONS/ADR-0001-editor-stack.md** — Why Slate; sanitation rules. +- **docs/DECISIONS/ADR-0002-permissions-model.md** — Roles and operation matrix (later phase). +- **.gitea/workflows/ci.yml** — CI: install/cache, typecheck, lint, tests, coverage artifact. +- **scripts/check-a11y.mjs** — Optional: batch axe checks for critical routes. +- **docs/RELEASE_NOTES.md** — Human-readable changes per release. + +## Notes +- Many files flagged for `: any` should be addressed opportunistically: start with reducers/selectors and editor props, then components used across pages. +- For `` elements, provide descriptive `alt` where meaningful; use `alt=""` (decorative) when the image conveys no unique information. +- Replace the single `eslint-disable` by fixing the underlying pattern (likely a hook dependency or exhaustive-deps issue). +- Consider adding an **ErrorBoundary** at the top-level wrapper if not present. diff --git a/scripts/dev/install-dev-deps.sh b/scripts/dev/install-dev-deps.sh new file mode 100755 index 0000000..d2a2a03 --- /dev/null +++ b/scripts/dev/install-dev-deps.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +PKG_MGR="" +if command -v pnpm >/dev/null 2>&1; then PKG_MGR="pnpm add -D" +elif command -v npm >/dev/null 2>&1; then PKG_MGR="npm i -D" +else echo "Install pnpm or npm first"; exit 1; fi + +$PKG_MGR typescript @types/node @types/react @types/react-dom +$PKG_MGR eslint @eslint/js typescript-eslint eslint-plugin-react-hooks eslint-plugin-jsx-a11y +$PKG_MGR prettier eslint-config-prettier +$PKG_MGR vitest @vitest/coverage-v8 jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom +$PKG_MGR jest-axe axe-core +$PKG_MGR msw +echo "Dev deps installed." diff --git a/scripts/dev/patch-package-scripts.sh b/scripts/dev/patch-package-scripts.sh new file mode 100755 index 0000000..ab66374 --- /dev/null +++ b/scripts/dev/patch-package-scripts.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required" >&2 + exit 1 +fi + +if [[ ! -f package.json ]]; then + echo "package.json not found" >&2 + exit 1 +fi + +tmp="$(mktemp)" +jq '. + | .scripts = (.scripts // {}) + | .scripts.lint = (.scripts.lint // "eslint .") + | .scripts["format"] = (.scripts["format"] // "prettier --check .") + | .scripts["format:write"] = (.scripts["format:write"] // "prettier --write .") + | .scripts.test = (.scripts.test // "vitest") + | .scripts["test:run"] = (.scripts["test:run"] // "vitest run") + | .scripts.typecheck = (.scripts.typecheck // "tsc -p tsconfig.json -noEmit") + ' package.json > "$tmp" +mv "$tmp" package.json +echo "Scripts patched." diff --git a/scripts/tracker/README.md b/scripts/tracker/README.md new file mode 100644 index 0000000..11d076f --- /dev/null +++ b/scripts/tracker/README.md @@ -0,0 +1,27 @@ +# Tracker Bootstrap — Patch 0 +Generated 2025-08-16 23:51Z + +## What this does +- Creates labels (from `.gitea/labels.json`) +- Creates milestone **Patch 0 — Orientation & Quality Bar** (from `milestone_patch0.json`) +- Creates 7 kickoff issues (from `issues/issues_patch0_phase1-3.json`) and assigns them to the milestone + +## Requirements +- Gitea with API access +- Tools: `curl`, `jq` + +## Usage +```bash +export GITEA_BASE_URL="https://gitea.example.com" +export GITEA_TOKEN="YOUR_TOKEN" +export OWNER="your-org-or-user" +export REPO="q-blog" +bash scripts/tracker/bootstrap_patch0.sh +``` + +## Files +- `.gitea/labels.json` +- `milestone_patch0.json` +- `issues/issues_patch0_phase1-3.json` +- `scripts/tracker/bootstrap_patch0.sh` +- `issues/*.md` (copy/paste friendly versions) diff --git a/scripts/tracker/TROUBLESHOOTING.md b/scripts/tracker/TROUBLESHOOTING.md new file mode 100644 index 0000000..1dbfefb --- /dev/null +++ b/scripts/tracker/TROUBLESHOOTING.md @@ -0,0 +1,20 @@ +# Tracker Bootstrap — Troubleshooting (v4, 2025-08-17 00:53Z) + +**Close duplicate milestone (keep lower ID):** +```bash +curl -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/milestones/2" --data '{"state":"closed"}' +``` + +**List duplicate labels by name:** +```bash +curl -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/labels" | jq 'group_by(.name) | map(select(length>1) | {name:.[0].name, ids: map(.id)})' +``` + +**Delete a duplicate label by ID:** +```bash +curl -X DELETE -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/labels/" +``` + +**Re-run bootstrap safely:** +- Script skips existing labels by exact name. +- Issues are created **without labels** to avoid server 422; add labels via UI later if desired. diff --git a/scripts/tracker/bootstrap_patch0.sh b/scripts/tracker/bootstrap_patch0.sh new file mode 100644 index 0000000..e32f872 --- /dev/null +++ b/scripts/tracker/bootstrap_patch0.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Q-Blog Tracker Scripts v4 — 2025-08-17 00:53Z +set -euo pipefail +# Env: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO + +need () { + for v in GITEA_BASE_URL GITEA_TOKEN OWNER REPO; do + if [[ -z "${!v:-}" ]]; then echo "ERROR: Missing $v" >&2; exit 1; fi + done +} + +api () { + local method="$1"; shift + local path="$1"; shift + local data="${1:-}" + local code body tmp; tmp="$(mktemp)" + if [[ -n "$data" && "$data" != "-" ]]; then + code="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" -H "Content-Type: application/json" -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1$path" --data @"$data")" + elif [[ "$data" == "-" ]]; then + code="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" -H "Content-Type: application/json" -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1$path" --data @-)" + else + code="$(curl -sS -o "$tmp" -w "%{http_code}" -X "$method" -H "Content-Type: application/json" -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1$path")" + fi + body="$(cat "$tmp")"; rm -f "$tmp"; echo "$code" "$body" +} + +get_json () { + local method="$1" path="$2" + read -r code body < <(api "$method" "$path") + if [[ "$code" != "200" ]]; then echo "ERROR $code: $body" >&2; exit 1; fi + echo "$body" +} + +ensure_auth_repo () { + echo "==> Checking token and repo" + local user repoFull + user="$(get_json GET "/user" | jq -r '.login // .username')" + echo "Authenticated as: $user" + repoFull="$(get_json GET "/repos/$OWNER/$REPO" | jq -r '.full_name')" + echo "Repo OK: $repoFull" +} + +create_labels () { + echo "==> Creating labels (idempotent by name)" + local file=".gitea/labels.json"; [[ -f "$file" ]] || { echo "WARN: $file missing"; return 0; } + local existing; existing="$(get_json GET "/repos/$OWNER/$REPO/labels" | jq -r '.[].name')" + local n; n="$(jq '.labels | length' "$file")" + for i in $(seq 0 $((n-1))); do + local name color color_clean payload code body + name="$(jq -r ".labels[$i].name" "$file")" + if echo "$existing" | grep -Fxq "$name"; then + echo "Label exists: $name (skip)" + continue + fi + color="$(jq -r ".labels[$i].color" "$file")" + color_clean="${color###}" + payload="$(jq -n --arg name "$name" --arg color "$color_clean" '{name:$name, color:$color}')" + read -r code body < <(echo "$payload" | api POST "/repos/$OWNER/$REPO/labels" -) + if [[ "$code" == "201" || "$code" == "200" ]]; then echo "Label created: $name"; else echo "WARN: $name → $code ($body)"; fi + done +} + +ensure_phase0_milestone () { + # returns id only + local title="Patch 0 — Orientation & Quality Bar" + local ms_payload="milestone_patch0.json" + local list ms_id code body + list="$(get_json GET "/repos/$OWNER/$REPO/milestones")" + ms_id="$(echo "$list" | jq -r --arg t "$title" '.[] | select(.title==$t) | .id' | head -n1)" + if [[ -z "$ms_id" || "$ms_id" == "null" ]]; then + [[ -f "$ms_payload" ]] || { echo "ERROR: $ms_payload missing" >&2; exit 1; } + read -r code body < <(api POST "/repos/$OWNER/$REPO/milestones" "$ms_payload") + [[ "$code" == "201" || "$code" == "200" ]] || { echo "ERROR: create milestone $code: $body" >&2; exit 1; } + ms_id="$(echo "$body" | jq -r '.id')" + fi + echo "$ms_id" +} + +create_issues () { + local ms_id="$1" + echo "==> Creating issues (milestone ID: $ms_id)" + local file="issues/issues_patch0_phase1-3.json"; [[ -f "$file" ]] || { echo "ERROR: $file missing" >&2; exit 1; } + local n; n="$(jq '.issues | length' "$file")" + for i in $(seq 0 $((n-1))); do + local title body payload code resp + title="$(jq -r ".issues[$i].title" "$file")" + body="$(jq -r ".issues[$i].body" "$file")" + payload="$(jq -n --arg title "$title" --arg body "$body" --argjson milestone "$ms_id" '{title:$title, body:$body, milestone:$milestone}')" + read -r code resp < <(echo "$payload" | api POST "/repos/$OWNER/$REPO/issues" -) + if [[ "$code" == "201" || "$code" == "200" ]]; then echo "Created: $title"; else echo "WARN: $title → $code ($resp)"; fi + done +} + +need +ensure_auth_repo +create_labels +ms_id="$(ensure_phase0_milestone)" +create_issues "$ms_id" +echo "==> Done." diff --git a/scripts/tracker/labels_dedupe.sh b/scripts/tracker/labels_dedupe.sh new file mode 100644 index 0000000..fc1a1b5 --- /dev/null +++ b/scripts/tracker/labels_dedupe.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail +# Env: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO +# Usage: DRY_RUN=1 bash scripts/tracker/labels_dedupe.sh +# APPLY=1 bash scripts/tracker/labels_dedupe.sh + +need () { + for v in GITEA_BASE_URL GITEA_TOKEN OWNER REPO; do + if [[ -z "${!v:-}" ]]; then echo "ERROR: Missing $v" >&2; exit 1; fi + done +} + +api () { + curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1$1" +} + +main () { + need + echo "== Finding duplicate labels by name ==" + DUPS="$(api "/repos/${OWNER}/${REPO}/labels?limit=500" | jq -c 'group_by(.name) | map(select(length>1))')" + count="$(echo "$DUPS" | jq 'length')" + if [[ "$count" -eq 0 ]]; then echo "No duplicates."; exit 0; fi + echo "$DUPS" | jq -r '.[] | "name=\(.[0].name) keep=\(.[0].id) delete=\([.[1:][] .id] | join(","))"' + if [[ "${APPLY:-}" != "1" ]]; then + echo "DRY RUN. Set APPLY=1 to delete extras (keeps the first ID per name)." + exit 0 + fi + echo "$DUPS" | jq -r '.[] | .[1:][] .id' | while read -r id; do + echo "Deleting label id=$id" + curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/labels/${id}" >/dev/null + done + echo "Done." +} +main "$@" diff --git a/scripts/tracker/rename_milestone_phase0.sh b/scripts/tracker/rename_milestone_phase0.sh new file mode 100755 index 0000000..8a31dd5 --- /dev/null +++ b/scripts/tracker/rename_milestone_phase0.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -euo pipefail +# Env: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO + +need() { + for v in GITEA_BASE_URL GITEA_TOKEN OWNER REPO; do + if [[ -z "${!v:-}" ]]; then echo "ERROR: Missing $v" >&2; exit 1; fi + done +} + +main() { + need + MS_ID=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/milestones" | jq -r '.[] | select(.title=="Patch 0 — Orientation & Quality Bar") | .id' | head -n1) + if [[ -z "$MS_ID" ]]; then echo "No Patch 0 milestone found"; exit 0; fi + echo "Renaming milestone id=$MS_ID to Phase 0 — Orientation & Quality Bar" + curl -sS -X PATCH -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/milestones/$MS_ID" --data '{"title":"Phase 0 — Orientation & Quality Bar"}' >/dev/null + echo "Done." +} +main "$@" diff --git a/scripts/tracker/verify_patch0.sh b/scripts/tracker/verify_patch0.sh new file mode 100644 index 0000000..29662f9 --- /dev/null +++ b/scripts/tracker/verify_patch0.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail +# Env: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO + +api () { + curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1$1" +} + +need () { + for v in GITEA_BASE_URL GITEA_TOKEN OWNER REPO; do + if [[ -z "${!v:-}" ]]; then echo "ERROR: Missing $v" >&2; exit 1; fi + done +} + +main () { + need + echo "== User ==" + api "/user" | jq '{login, id}' + echo "== Labels (count) ==" + api "/repos/${OWNER}/${REPO}/labels?limit=200" | jq 'length' + echo "== Milestones ==" + api "/repos/${OWNER}/${REPO}/milestones" | jq '.[] | {id, title, state, open_issues, closed_issues}' + echo "== Issues in Patch 0 (by milestone filter) ==" + ms_id="$(api "/repos/${OWNER}/${REPO}/milestones" | jq -r '.[] | select(.title=="Patch 0 — Orientation & Quality Bar") | .id' | head -n1)" + if [[ -n "$ms_id" ]]; then + api "/repos/${OWNER}/${REPO}/issues?milestones=$ms_id&state=open&limit=100" | jq '.[] | {number, title, labels:[.labels[].name]}' + else + echo "Milestone not found." + fi + echo "== All Open Issues (title + milestone) ==" + api "/repos/${OWNER}/${REPO}/issues?state=open&limit=100" | jq '.[] | {number, title, milestone: (.milestone.title // null)}' +} +main "$@" diff --git a/tests/axe-smoke.test.tsx b/tests/axe-smoke.test.tsx new file mode 100644 index 0000000..f22e72b --- /dev/null +++ b/tests/axe-smoke.test.tsx @@ -0,0 +1,15 @@ +import { describe, it, expect } from 'vitest' +import { render } from '@testing-library/react' +import { axe } from 'jest-axe' + +function Hello() { + return +} + +describe('a11y smoke', () => { + it('has no detectable violations', async () => { + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) +}) diff --git a/tests/msw/handlers.ts b/tests/msw/handlers.ts new file mode 100644 index 0000000..1027ca4 --- /dev/null +++ b/tests/msw/handlers.ts @@ -0,0 +1,7 @@ +import { http, HttpResponse } from 'msw' + +export const handlers = [ + http.get('/api/health', () => { + return HttpResponse.json({ ok: true }) + }), +] diff --git a/tests/msw/server.ts b/tests/msw/server.ts new file mode 100644 index 0000000..86f7d61 --- /dev/null +++ b/tests/msw/server.ts @@ -0,0 +1,4 @@ +import { setupServer } from 'msw/node' +import { handlers } from './handlers' + +export const server = setupServer(...handlers) diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..74ea3c9 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,11 @@ +// tests/setup.ts — registers matchers & MSW +import '@testing-library/jest-dom/vitest' +import { afterAll, afterEach, beforeAll, expect } from 'vitest' +import { toHaveNoViolations } from 'jest-axe' +import { server } from './msw/server' + +expect.extend(toHaveNoViolations) + +beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })) +afterEach(() => server.resetHandlers()) +afterAll(() => server.close()) diff --git a/tests/types/expect-extensions.d.ts b/tests/types/expect-extensions.d.ts new file mode 100644 index 0000000..8097861 --- /dev/null +++ b/tests/types/expect-extensions.d.ts @@ -0,0 +1,12 @@ +// tests/types/expect-extensions.d.ts +import 'vitest' +import 'jest-axe' + +declare module 'vitest' { + interface Assertion { + toHaveNoViolations(): void + } + interface AsymmetricMatchersContaining { + toHaveNoViolations(): void + } +} diff --git a/tsconfig.strict.json b/tsconfig.strict.json new file mode 100644 index 0000000..c84cd83 --- /dev/null +++ b/tsconfig.strict.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "jsx": "react-jsx", + "types": ["vitest/globals", "vite/client"] + } +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4f0e4a4 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vitest/config' +import path from 'node:path' + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./tests/setup.ts'], + css: false, + globals: true, + coverage: { + provider: 'v8', + reporter: ['text', 'lcov'], + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) -- 2.43.0 From 2dc1e585a5446fe01dec3ba94540196082fe1eb5 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sat, 16 Aug 2025 22:15:29 -0400 Subject: [PATCH 17/42] chore(ci): smoke trigger --- .eslintignore.bak | 15 ++++++ .gitignore | 41 +++++++++++++-- docs/PHASE1_PLAN.md | 29 +++++++++++ docs/RELEASING.md | 1 + eslint.config.mjs | 29 ++++++++--- package.json | 7 ++- scripts/dev/apply-phase0-lint.sh | 52 +++++++++++++++++++ scripts/dev/check.sh | 13 +++++ scripts/dev/cleanup-eslintignore.sh | 9 ++++ scripts/dev/patch-gitignore-phase1.sh | 72 +++++++++++++++++++++++++++ scripts/dev/patch-lint-scope.sh | 18 +++++++ scripts/dev/patch-phase1-scripts.sh | 15 ++++++ scripts/dev/phase1-report.sh | 53 ++++++++++++++++++++ scripts/dev/phase1-scan.sh | 57 +++++++++++++++++++++ scripts/tracker/open_phase_pr.sh | 36 ++++++++++++++ scripts/tracker/verify_patch0.sh | 0 tests/types/expect-extensions.d.ts | 2 +- 17 files changed, 434 insertions(+), 15 deletions(-) create mode 100644 .eslintignore.bak create mode 100644 docs/PHASE1_PLAN.md create mode 100755 scripts/dev/apply-phase0-lint.sh create mode 100755 scripts/dev/check.sh create mode 100755 scripts/dev/cleanup-eslintignore.sh create mode 100755 scripts/dev/patch-gitignore-phase1.sh create mode 100755 scripts/dev/patch-lint-scope.sh create mode 100755 scripts/dev/patch-phase1-scripts.sh create mode 100755 scripts/dev/phase1-report.sh create mode 100755 scripts/dev/phase1-scan.sh create mode 100755 scripts/tracker/open_phase_pr.sh mode change 100644 => 100755 scripts/tracker/verify_patch0.sh diff --git a/.eslintignore.bak b/.eslintignore.bak new file mode 100644 index 0000000..2f0bb45 --- /dev/null +++ b/.eslintignore.bak @@ -0,0 +1,15 @@ +# Phase 0: limit lint to harness/docs/scripts; app code will be addressed in Phase 1–3 sweeps. +node_modules/ +dist/ +coverage/ +.vite/ +.gitea/ + +# Ignore full app sources during Phase 0 +src/** + +# Type definitions are not linted +**/*.d.ts + +# Build artifacts & caches +*.log diff --git a/.gitignore b/.gitignore index bfe8e32..5dd19f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,43 @@ +# --- Q‑Blog canonical ignore (Phase 1) --- # Logs logs *.log npm-debug.log* yarn-debug.log* -yarn-error.log* pnpm-debug.log* lerna-debug.log* + +# Packages & archives +node_modules/ *.zip -node_modules -dist -dist-ssr -*.local +*.tgz + +# Build outputs +dist/ +dist-ssr/ +storybook-static/ + +# Test & coverage +coverage/ +.nyc_output/ +playwright-report/ +test-results/ + +# Tool caches +.vite/ +.eslintcache +.cache/ +.tmp/ +tmp/ +*.tsbuildinfo + +# Reports (local scans, not tracked) +reports/ + +# Env files +.env +.env.local +.env.*.local # Editor directories and files .vscode/* @@ -22,3 +49,7 @@ dist-ssr *.njsproj *.sln *.sw? + +# Releases +release/ +release-*/ diff --git a/docs/PHASE1_PLAN.md b/docs/PHASE1_PLAN.md new file mode 100644 index 0000000..c335d98 --- /dev/null +++ b/docs/PHASE1_PLAN.md @@ -0,0 +1,29 @@ +# Phase 1 — Baseline Scan & Planning +Goal: capture a **ground-truth snapshot** of technical debt (imports, ts-nochecks, any-usage, a11y hotspots) without changing app code. + +## Deliverables +- `reports/phase1-*/` folder with raw findings and a concise `summary.md`. +- Package scripts: `scan:phase1`, `report:phase1`. + +## What we scan +- **MUI v4 imports** (`@material-ui/*`) — should move to `@mui/material` & friends. +- **TS suppression** (`@ts-nocheck`, `@ts-ignore`) — create removal plan. +- **`any` usage** — count & top files. +- **A11y (img alt)** — naive grep for ` .eslintignore + echo "Applied Phase 0 lint scope (src/** ignored)." + ;; + full) + echo "$FULL_CONTENT" > .eslintignore + echo "Applied FULL lint scope (src/** included)." + ;; + *) + echo "Usage: $0 [phase0|full]" >&2 + exit 1 + ;; +esac + +# Show effective ignore +echo "== .eslintignore ==" +cat .eslintignore diff --git a/scripts/dev/check.sh b/scripts/dev/check.sh new file mode 100755 index 0000000..7978c52 --- /dev/null +++ b/scripts/dev/check.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail +# Unified local gate for Phase 0 (env-scoped lint) +if command -v pnpm >/dev/null 2>&1; then + pnpm typecheck + pnpm lint:phase0 + pnpm test:run +else + npm run typecheck --silent + npm run lint:phase0 --silent + npm run test:run --silent +fi +echo "All checks passed." diff --git a/scripts/dev/cleanup-eslintignore.sh b/scripts/dev/cleanup-eslintignore.sh new file mode 100755 index 0000000..f9ea4c2 --- /dev/null +++ b/scripts/dev/cleanup-eslintignore.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +# Optional: silence ESLint v9 warning by disabling legacy .eslintignore +if [[ -f .eslintignore ]]; then + mv .eslintignore .eslintignore.bak + echo "Moved .eslintignore -> .eslintignore.bak (flat config 'ignores' is now the source of truth)." +else + echo "No .eslintignore found. Nothing to do." +fi diff --git a/scripts/dev/patch-gitignore-phase1.sh b/scripts/dev/patch-gitignore-phase1.sh new file mode 100755 index 0000000..e825f3d --- /dev/null +++ b/scripts/dev/patch-gitignore-phase1.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")"/../.. && pwd)" +TARGET="$HERE/.gitignore" +BACKUP="$HERE/.gitignore.bak.$(date +%Y%m%d-%H%M%S)" + +if [[ -f "$TARGET" ]]; then + cp "$TARGET" "$BACKUP" + echo "Backed up existing .gitignore to $(basename "$BACKUP")" +fi + +cat > "$TARGET" <<'IGN' +# --- Q‑Blog canonical ignore (Phase 1) --- +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +pnpm-debug.log* +lerna-debug.log* + +# Packages & archives +node_modules/ +*.zip +*.tgz + +# Build outputs +dist/ +dist-ssr/ +storybook-static/ + +# Test & coverage +coverage/ +.nyc_output/ +playwright-report/ +test-results/ + +# Tool caches +.vite/ +.eslintcache +.cache/ +.tmp/ +tmp/ +*.tsbuildinfo + +# Reports (local scans, not tracked) +reports/ + +# Env files +.env +.env.local +.env.*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Releases +release/ +release-*/ +IGN + +echo "Applied canonical .gitignore (Phase 1)." +git status --ignored -s || true diff --git a/scripts/dev/patch-lint-scope.sh b/scripts/dev/patch-lint-scope.sh new file mode 100755 index 0000000..e032afb --- /dev/null +++ b/scripts/dev/patch-lint-scope.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail +if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq not found" >&2 + exit 1 +fi + +TMP=package.json.$$ + +# Ensure scripts exist and add lint variants +jq '. + | .scripts = (.scripts // {}) + | .scripts["lint:phase0"] = "LINT_SCOPE=phase0 eslint ." + | .scripts["lint:full"] = "LINT_SCOPE=full eslint ." + | .scripts["check"] = (.scripts["check"] // "npm run typecheck && npm run lint:phase0 && npm run test:run") +' package.json > "$TMP" && mv "$TMP" package.json + +echo "package.json scripts patched (lint:phase0, lint:full, check)." diff --git a/scripts/dev/patch-phase1-scripts.sh b/scripts/dev/patch-phase1-scripts.sh new file mode 100755 index 0000000..6b4c314 --- /dev/null +++ b/scripts/dev/patch-phase1-scripts.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail +if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq not found" >&2 + exit 1 +fi +TMP=package.json.$$ + +jq '. + | .scripts = (.scripts // {}) + | .scripts["scan:phase1"] = "bash scripts/dev/phase1-scan.sh" + | .scripts["report:phase1"] = "bash scripts/dev/phase1-report.sh" +' package.json > "$TMP" && mv "$TMP" package.json + +echo "Added scripts: scan:phase1, report:phase1" diff --git a/scripts/dev/phase1-report.sh b/scripts/dev/phase1-report.sh new file mode 100755 index 0000000..7752329 --- /dev/null +++ b/scripts/dev/phase1-report.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +LATEST="$(ls -1d reports/phase1-* 2>/dev/null | tail -n1 || true)" +if [[ -z "$LATEST" ]]; then + echo "No reports/phase1-* directory found. Run: npm run scan:phase1" >&2 + exit 1 +fi + +SUM_JSON="$LATEST/summary.json" +if [[ ! -f "$SUM_JSON" ]]; then + echo "summary.json not found in $LATEST" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq not found (needed to generate the report). Try: sudo apt-get install jq" >&2 + exit 1 +fi + +MUI=$(jq -r '.mui_v4_imports' "$SUM_JSON") +TS_SUP=$(jq -r '.ts_suppressions' "$SUM_JSON") +ANYC=$(jq -r '.any_usages' "$SUM_JSON") +IMGC=$(jq -r '.img_without_alt_grep' "$SUM_JSON") +HOOK=$(jq -r '.hooks_usages' "$SUM_JSON") +WW=$(jq -r '.webworker_globals' "$SUM_JSON") + +cat > "$LATEST/summary.md" <\` without \`alt=\` (grep) | $IMGC | +| Hooks usage (all hits) | $HOOK | +| WebWorker globals | $WW | + +## Next steps (create/attach to issues) + +- **MUI v4 → v5** (codemod + manual follow-ups). See: \`mui_v4_imports.txt\`. +- **Remove suppressions** — triage each; add types or refactor. See: \`ts_suppressions.txt\`. +- **Type hygiene** — sort by \`any_usage_topfiles.txt\`; tackle high-signal files first. +- **A11y** — review \`img_no_alt.txt\` results; fix meaningful alt text or \`alt=""\`. +- **Hooks** — confirm usages inside components/hooks only; flag utilities. See: \`hooks_usage.txt\`. +- **Workers** — set proper ESLint env or refactor. See: \`webworker_globals.txt\`. + +MD + +echo "Wrote $LATEST/summary.md" diff --git a/scripts/dev/phase1-scan.sh b/scripts/dev/phase1-scan.sh new file mode 100755 index 0000000..3d4bcff --- /dev/null +++ b/scripts/dev/phase1-scan.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +OUTDIR="reports/phase1-$(date +%Y%m%d-%H%M%S)" +mkdir -p "$OUTDIR" + +# Helpers +scan () { + local name="$1"; shift + echo "== $name ==" + ( bash -lc "$*" ) > "$OUTDIR/$name.txt" || true +} + +count () { + local file="$1" + if [[ -f "$file" ]]; then wc -l < "$file"; else echo 0; fi +} + +# Scans +scan mui_v4_imports "grep -RInE \"from ['\\\"]@material-ui/|require\\(['\\\"]@material-ui/\" src || true" + +scan ts_suppressions "grep -RInE '@ts-nocheck|@ts-ignore' src || true" + +scan any_usage "grep -RInE ': any\\b|\\bas any\\b|' src || true" + +# IMPORTANT: single-quote the pattern so '<' is not treated as redirection +scan img_no_alt "grep -RIn ']*>' src | grep -v 'alt=' || true" + +scan hooks_usage "grep -RInE 'useDispatch\\(|useSelector\\(|useEffect\\(|useCallback\\(' src || true" + +scan webworker_globals "grep -RInE '\\bself\\b|\\bXMLHttpRequest\\b|\\bfetch\\b|\\bURL\\b' src/webworkers 2>/dev/null || true" + +# Top files by any-usage +if [[ -f "$OUTDIR/any_usage.txt" ]]; then + awk -F: '{print $1}' "$OUTDIR/any_usage.txt" | sort | uniq -c | sort -nr > "$OUTDIR/any_usage_topfiles.txt" +fi + +# Summary JSON +MUI_CNT=$(count "$OUTDIR/mui_v4_imports.txt") +TS_SUP_CNT=$(count "$OUTDIR/ts_suppressions.txt") +ANY_CNT=$(count "$OUTDIR/any_usage.txt") +IMG_CNT=$(count "$OUTDIR/img_no_alt.txt") +HOOKS_CNT=$(count "$OUTDIR/hooks_usage.txt") +WW_CNT=$(count "$OUTDIR/webworker_globals.txt") + +cat > "$OUTDIR/summary.json" <&2; exit 1; fi + done + [[ -f "$BODY_FILE" ]] || { echo "Body file not found: $BODY_FILE" >&2; exit 1; } + if [[ -z "$HEAD_BRANCH" ]]; then + echo "ERROR: Not on a named branch (detached HEAD). Pass HEAD_BRANCH explicitly." >&2 + exit 1 + fi +} + +main () { + need + TITLE="$(head -n1 "$BODY_FILE" | sed 's/^# *//')" + BODY="$(sed '1d' "$BODY_FILE")" + PAYLOAD="$(jq -n --arg title "$TITLE" --arg head "$HEAD_BRANCH" --arg base "$BASE_BRANCH" --arg body "$BODY" '{title:$title, head:$head, base:$base, body:$body}')" + RESP="$(curl -sS -X POST -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/pulls" --data "$PAYLOAD")" + echo "$RESP" | jq '{number, state, html_url, title}' + if [[ "$(echo "$RESP" | jq -r '.number')" == "null" ]]; then + echo "ERROR: PR not created. Full response:" >&2 + echo "$RESP" >&2 + exit 1 + fi +} + +main "$@" diff --git a/scripts/tracker/verify_patch0.sh b/scripts/tracker/verify_patch0.sh old mode 100644 new mode 100755 diff --git a/tests/types/expect-extensions.d.ts b/tests/types/expect-extensions.d.ts index 8097861..16a6d5e 100644 --- a/tests/types/expect-extensions.d.ts +++ b/tests/types/expect-extensions.d.ts @@ -3,7 +3,7 @@ import 'vitest' import 'jest-axe' declare module 'vitest' { - interface Assertion { + interface Assertion { toHaveNoViolations(): void } interface AsymmetricMatchersContaining { -- 2.43.0 From 7631206d3aaa5608e029bf206c0e25046631c42d Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sat, 16 Aug 2025 23:31:26 -0400 Subject: [PATCH 18/42] Phase 0 closeout: bump to v0.0.1 --- .gitignore | 3 + docs/ENV_SETUP.md | 19 ++++++ docs/PHASE0_CLOSEOUT.md | 18 +++++ docs/PHASE0_STATUS.md | 26 +++++++ docs/PHASE1_NEXT_STEPS.md | 14 ++++ docs/PHASE1_STEP1.md | 21 ++++++ docs/PHASE1_TINY_PRS.md | 16 +++++ docs/RELEASE_NOTES_v0.0.1.md | 14 ++++ docs/VERSIONING.md | 13 ++++ package.json | 2 +- pr/phase0-closeout.md | 19 ++++++ pr/phase1-step1a.md | 16 +++++ scripts/dev/check.sh | 17 ++--- scripts/dev/ci-smoke-commit.sh | 16 +++++ scripts/dev/phase1/alt-fix-todo.sh | 34 ++++++++++ scripts/dev/phase1/commit-step1a.sh | 7 ++ scripts/dev/phase1/fail-on-suppressions.sh | 21 ++++++ scripts/dev/phase1/fix-mui-imports.sh | 60 +++++++++++++++++ scripts/dev/phase1/list-imgs-missing-alt.sh | 7 ++ scripts/dev/phase1/remove-ts-nocheck.sh | 37 ++++++++++ scripts/dev/phase1/tsnocheck-remove.sh | 37 ++++++++++ scripts/tracker/bootstrap_patch0.sh | 0 scripts/tracker/labels_dedupe.sh | 0 scripts/tracker/load_env.sh | 15 +++++ scripts/tracker/print_env.sh | 10 +++ scripts/tracker/verify_patch0.sh | 35 +--------- scripts/tracker/verify_phase0.sh | 75 +++++++++++++++++++++ scripts/tracker/with_env.sh | 13 ++++ src/App.tsx | 1 - src/components/editor/BlogEditor.tsx | 5 +- 30 files changed, 523 insertions(+), 48 deletions(-) create mode 100644 docs/ENV_SETUP.md create mode 100644 docs/PHASE0_CLOSEOUT.md create mode 100644 docs/PHASE0_STATUS.md create mode 100644 docs/PHASE1_NEXT_STEPS.md create mode 100644 docs/PHASE1_STEP1.md create mode 100644 docs/PHASE1_TINY_PRS.md create mode 100644 docs/RELEASE_NOTES_v0.0.1.md create mode 100644 docs/VERSIONING.md create mode 100644 pr/phase0-closeout.md create mode 100644 pr/phase1-step1a.md create mode 100755 scripts/dev/ci-smoke-commit.sh create mode 100755 scripts/dev/phase1/alt-fix-todo.sh create mode 100755 scripts/dev/phase1/commit-step1a.sh create mode 100755 scripts/dev/phase1/fail-on-suppressions.sh create mode 100755 scripts/dev/phase1/fix-mui-imports.sh create mode 100755 scripts/dev/phase1/list-imgs-missing-alt.sh create mode 100755 scripts/dev/phase1/remove-ts-nocheck.sh create mode 100755 scripts/dev/phase1/tsnocheck-remove.sh mode change 100644 => 100755 scripts/tracker/bootstrap_patch0.sh mode change 100644 => 100755 scripts/tracker/labels_dedupe.sh create mode 100755 scripts/tracker/load_env.sh create mode 100755 scripts/tracker/print_env.sh create mode 100755 scripts/tracker/verify_phase0.sh create mode 100755 scripts/tracker/with_env.sh diff --git a/.gitignore b/.gitignore index 5dd19f7..817f9c4 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ reports/ .env .env.local .env.*.local +.gitea.env # Editor directories and files .vscode/* @@ -53,3 +54,5 @@ reports/ # Releases release/ release-*/ +.runner +act_runner diff --git a/docs/ENV_SETUP.md b/docs/ENV_SETUP.md new file mode 100644 index 0000000..8a67134 --- /dev/null +++ b/docs/ENV_SETUP.md @@ -0,0 +1,19 @@ +# Gitea environment quick setup + +1) Create `.gitea.env` in repo root (or reuse your existing one): + +``` +GITEA_BASE_URL=https://gitea.qortal.link +GITEA_TOKEN=your_40_char_token +OWNER=greenflame089 +REPO=q-blog +``` + +2) Verify: +``` +bash scripts/tracker/with_env.sh .gitea.env bash scripts/tracker/verify_phase0.sh +``` + +Notes: +- `verify_phase0.sh` will auto-load `.gitea.env` if env vars are missing. +- If you already exported vars in your shell, you can call the script directly. diff --git a/docs/PHASE0_CLOSEOUT.md b/docs/PHASE0_CLOSEOUT.md new file mode 100644 index 0000000..a672c5a --- /dev/null +++ b/docs/PHASE0_CLOSEOUT.md @@ -0,0 +1,18 @@ +# Phase 0 Closeout Checklist + +**Goal:** repository hygiene, quality bar, and scaffolding. No behavior changes. + +## Must-haves +- [ ] **Tracker**: canonical labels deduped; Phase 0 milestone open; kickoff issues present. +- [ ] **Docs**: Quality Charter, Project Instructions, Testing, Releasing. +- [ ] **Harness**: Vitest + RTL + MSW + jest-axe; `tests/axe-smoke.test.tsx` passing. +- [ ] **Lint**: ESLint flat config working; Phase 0 scope excludes `src/**`. +- [ ] **CI**: Gitea workflow runs test + lint(phase0) on PRs. +- [ ] **Version**: bump to `0.0.1` via `scripts/release/bump-version.sh phase0`. + +## Nice-to-haves +- [ ] ENV helper scripts committed (`scripts/tracker/*`). +- [ ] Status doc updated (`docs/PHASE0_STATUS.md`). + +## Release notes +Create `docs/RELEASE_NOTES_v0.0.1.md` with one-liners (hygiene, docs, harness, CI). diff --git a/docs/PHASE0_STATUS.md b/docs/PHASE0_STATUS.md new file mode 100644 index 0000000..2ee7ed3 --- /dev/null +++ b/docs/PHASE0_STATUS.md @@ -0,0 +1,26 @@ +# Phase 0 — Closeout Checklist + +**Branch:** `update` +**Milestone:** Phase 0 — Orientation & Quality Bar + +## Acceptance +- [ ] Quality Charter adopted and referenced in repo +- [ ] Labels canonicalized; duplicates removed +- [ ] Issue/PR templates present +- [ ] Minimal CI runs on push + PR (Gitea) +- [ ] Harness green: Vitest + RTL + MSW + jest-axe (smoke) +- [ ] ESLint flat config installed; Phase 0 scope active +- [ ] Phase 1 baseline report generated and attached to issues + +## Baseline metrics (from latest report) +- MUI v4 imports: **1** +- TS suppressions: **2** +- `any` usages: **309** +- `` missing `alt=` (grep): **4** +- Hooks usage hits: **240** +- WebWorker globals: **17** + +## Links +- PR: (link) +- CI run: (link) +- Milestone: (link) diff --git a/docs/PHASE1_NEXT_STEPS.md b/docs/PHASE1_NEXT_STEPS.md new file mode 100644 index 0000000..835305e --- /dev/null +++ b/docs/PHASE1_NEXT_STEPS.md @@ -0,0 +1,14 @@ +# Phase 1 — Next Steps (Small, Mergeable PRs) + +1) **MUI v4 → v5 import fix** + - Run: `bash scripts/dev/phase1/fix-mui-imports.sh --dry` then `--apply`. + - Commit only the touched files + `reports/phase1-*/notes.txt` (optional). + +2) **Remove TS suppressions** + - Run: `bash scripts/dev/phase1/fail-on-suppressions.sh` (or `--allow=1` while fixing one file per PR). + - Convert ignored sections to proper types or refactor. + +3) **A11y: `` alt text** + - Run: `bash scripts/dev/phase1/list-imgs-missing-alt.sh` and fix each instance (`alt` or `alt=""` if decorative). + +Keep each PR focused (1–5 files), branch from `update`, and let CI run. diff --git a/docs/PHASE1_STEP1.md b/docs/PHASE1_STEP1.md new file mode 100644 index 0000000..7e5e102 --- /dev/null +++ b/docs/PHASE1_STEP1.md @@ -0,0 +1,21 @@ +# Phase 1 — Step 1 (Tiny PRs) + +1. **Remove `@ts-nocheck`** + ```bash + bash scripts/dev/phase1/tsnocheck-remove.sh + git push origin HEAD + ``` + +2. **Alt text audit** + ```bash + bash scripts/dev/phase1/list-imgs-missing-alt.sh + bash scripts/dev/phase1/alt-fix-todo.sh + # open the generated reports/*/alt-fix-todo.md and address each item + ``` + +3. **MUI v4 imports → v5** (preview then apply) + ```bash + bash scripts/dev/phase1/fix-mui-imports.sh --dry + bash scripts/dev/phase1/fix-mui-imports.sh + git push origin HEAD + ``` diff --git a/docs/PHASE1_TINY_PRS.md b/docs/PHASE1_TINY_PRS.md new file mode 100644 index 0000000..1c70033 --- /dev/null +++ b/docs/PHASE1_TINY_PRS.md @@ -0,0 +1,16 @@ +# Phase 1 — Tiny PR checklist + +1) **Remove `// @ts-nocheck` (2 files)** + - Dry run: `bash scripts/dev/phase1/remove-ts-nocheck.sh` + - Apply: `bash scripts/dev/phase1/remove-ts-nocheck.sh --apply` + - Commit on `update`: `git add -A && git commit -m "phase1: drop ts-nocheck headers (no behavior change)" && git push` + +2) **MUI v4 → v5 imports (if any)** + - Dry: `bash scripts/dev/phase1/fix-mui-imports.sh --dry` + - Apply in a tiny batch (1–3 files): `--apply`, commit & push. + +3) **A11y: ** + - `bash scripts/dev/phase1/list-imgs-missing-alt.sh` + - Fix each hit with meaningful `alt`, or `alt=""` when decorative. + +Keep PRs tiny and non-breaking so upstream can merge at any time. diff --git a/docs/RELEASE_NOTES_v0.0.1.md b/docs/RELEASE_NOTES_v0.0.1.md new file mode 100644 index 0000000..d110a3e --- /dev/null +++ b/docs/RELEASE_NOTES_v0.0.1.md @@ -0,0 +1,14 @@ +# Release Notes — v0.0.1 (Phase 0) + +**Summary:** Repo hygiene & scaffolding. No behavior changes. + +## Highlights +- ✅ Quality Charter, Project Instructions, testing & releasing guides +- ✅ Vitest + RTL + MSW + jest-axe harness (a11y smoke in CI) +- ✅ ESLint flat-config (Phase 0 scope), Prettier, EditorConfig +- ✅ Gitea CI workflow (no marketplace) +- ✅ Tracker scripts (labels, milestone, verification) + +## Compatibility +- No user-visible changes. +- No breaking changes. diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md new file mode 100644 index 0000000..f4aa499 --- /dev/null +++ b/docs/VERSIONING.md @@ -0,0 +1,13 @@ +# Versioning & Phases + +We map **project phases** to **SemVer**: + +- **Phase 0** → `0.0.1` (orientation & quality bar; no feature changes) +- **Phase 1** → `0.1.0` (correctness & hygiene foundations) +- **Phase 2** → `0.2.0` (docs/testing expansion, early UX fixes) +- ... + +Rules: +- Use **SemVer**; phases bump the **minor** (except Phase 0 uses `0.0.1`). +- Patch bumps (`0.X.Y`) are reserved for small fixes within a phase. +- Tag releases and include short notes in `docs/RELEASE_NOTES_vX.Y.Z.md`. diff --git a/package.json b/package.json index b20d6cc..6896ce1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "q-blog", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { "dev": "vite", diff --git a/pr/phase0-closeout.md b/pr/phase0-closeout.md new file mode 100644 index 0000000..1af893a --- /dev/null +++ b/pr/phase0-closeout.md @@ -0,0 +1,19 @@ +**Title:** Phase 0 — Closeout (v0.0.1) + +**Summary** +- Repo hygiene, docs, test harness, ESLint flat config, CI workflow +- No behavior changes + +**What’s included** +- Docs: Charter, Instructions, Testing, Releasing +- Harness: Vitest + RTL + MSW + jest-axe (a11y smoke) +- CI: Gitea workflow (test + lint:phase0) +- Version: bump to `0.0.1` + +**Verification** +- `npm run test` → green +- `npm run lint:phase0` → clean +- CI green on PR + +**Notes** +- App code (`src/**`) linting deferred to Phase 1–3 sweeps. diff --git a/pr/phase1-step1a.md b/pr/phase1-step1a.md new file mode 100644 index 0000000..4162be7 --- /dev/null +++ b/pr/phase1-step1a.md @@ -0,0 +1,16 @@ +**Title:** Phase 1 — Step 1A: Remove `@ts-nocheck` headers + +**Summary** +- Remove blanket `@ts-nocheck` headers to restore type safety. +- No behavior changes. + +**Details** +- Files touched: see diff (e.g., `src/App.tsx`, `src/components/editor/BlogEditor.tsx`). +- Leave per-line todo comments in follow-ups instead of blanket disables. + +**Verification** +- `scripts/dev/check.sh` (Vitest run + lint:phase0) → green +- App still builds locally (if applicable): `npm run dev` quick smoke + +**Follow-ups (tracked in Phase 1 issues)** +- Type hygiene & rule violations addressed file-by-file. diff --git a/scripts/dev/check.sh b/scripts/dev/check.sh index 7978c52..618bbe0 100755 --- a/scripts/dev/check.sh +++ b/scripts/dev/check.sh @@ -1,13 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -# Unified local gate for Phase 0 (env-scoped lint) -if command -v pnpm >/dev/null 2>&1; then - pnpm typecheck - pnpm lint:phase0 - pnpm test:run -else - npm run typecheck --silent - npm run lint:phase0 --silent - npm run test:run --silent -fi -echo "All checks passed." + +echo "== Tests (vitest run) ==" +CI=1 npx vitest run + +echo "== Lint (phase0 scope) ==" +npm run lint:phase0 diff --git a/scripts/dev/ci-smoke-commit.sh b/scripts/dev/ci-smoke-commit.sh new file mode 100755 index 0000000..85bb0f1 --- /dev/null +++ b/scripts/dev/ci-smoke-commit.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Tiny CI trigger helper — appends a timestamp to docs/RELEASING.md and pushes. +set -euo pipefail +BR="${1:-update}" +FILE="docs/RELEASING.md" + +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Not inside a git repo"; exit 1 +fi + +git checkout "$BR" +echo "$(date -u +'%Y-%m-%dT%H:%M:%SZ') – CI smoke" >> "$FILE" +git add "$FILE" +git commit -m "chore(ci): smoke trigger" +git push -u origin "$BR" +echo "Pushed to $BR." diff --git a/scripts/dev/phase1/alt-fix-todo.sh b/scripts/dev/phase1/alt-fix-todo.sh new file mode 100755 index 0000000..75f7243 --- /dev/null +++ b/scripts/dev/phase1/alt-fix-todo.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Turn the img-no-alt scan into a checklist with context. +set -euo pipefail +STAMP="$(date +%Y%m%d-%H%M%S)" +OUT_DIR="reports/phase1-${STAMP}-alt-todo" +SRC_TXT="$(ls -1t reports/phase1-*-img-no-alt.txt 2>/dev/null | head -n1 || true)" + +mkdir -p "$OUT_DIR" +OUT_MD="$OUT_DIR/alt-fix-todo.md" + +if [[ -z "$SRC_TXT" ]]; then + echo "No img-no-alt report found. Run scripts/dev/phase1/list-imgs-missing-alt.sh first." >&2 + exit 1 +fi + +echo "# Alt text fixes — TODO" > "$OUT_MD" +echo "" >> "$OUT_MD" +echo "_Source: $SRC_TXT_" >> "$OUT_MD" +echo "" >> "$OUT_MD" + +while IFS= read -r line; do + file="$(cut -d: -f1 <<<"$line")" + ln_no="$(cut -d: -f2 <<<"$line")" + echo "- [ ] \`$file:$ln_no\`" >> "$OUT_MD" + { + echo "" + echo '```tsx' + nl -ba -w2 -s': ' "$file" | sed -n "$((ln_no-2)),$((ln_no+2))p" + echo '```' + echo "" + } >> "$OUT_MD" +done < "$SRC_TXT" + +echo "Wrote $OUT_MD" diff --git a/scripts/dev/phase1/commit-step1a.sh b/scripts/dev/phase1/commit-step1a.sh new file mode 100755 index 0000000..a45abbd --- /dev/null +++ b/scripts/dev/phase1/commit-step1a.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +branch="${1:-update}" +git add -p || true +git commit -m "Phase 1 — Step 1A: remove @ts-nocheck headers (no behavior change)" || true +echo "Committed. Push with: git push origin ${branch}" +echo "Then open PR using: pr/phase1-step1a.md" diff --git a/scripts/dev/phase1/fail-on-suppressions.sh b/scripts/dev/phase1/fail-on-suppressions.sh new file mode 100755 index 0000000..72724e7 --- /dev/null +++ b/scripts/dev/phase1/fail-on-suppressions.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Fails if TS suppressions are present. Allow N suppressions via --allow=N. +set -euo pipefail + +ALLOW=0 +if [[ "${1-}" =~ --allow=([0-9]+) ]]; then + ALLOW="${BASHREMATCH[1]}" +fi + +ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo .)" +mapfile -t HITS < <(grep -RIn --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=coverage -E '@ts-nocheck|@ts-ignore' "$ROOT/src" || true) + +COUNT="${#HITS[@]}" +echo "Suppressions found: $COUNT (allow <= $ALLOW)" +printf '%s +' "${HITS[@]}" + +if (( COUNT > ALLOW )); then + echo "ERROR: too many suppressions. Fix or raise the allowance." + exit 1 +fi diff --git a/scripts/dev/phase1/fix-mui-imports.sh b/scripts/dev/phase1/fix-mui-imports.sh new file mode 100755 index 0000000..3875dee --- /dev/null +++ b/scripts/dev/phase1/fix-mui-imports.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Phase 1 helper: migrate '@material-ui/*' imports to MUI v5 packages. +# Usage: bash $0 --dry (default) | bash $0 --apply +set -euo pipefail + +MODE="dry" +[[ "${1-}" == "--apply" ]] && MODE="apply" + +ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo .)" +REPORT_DIR="$ROOT/reports/phase1-$(date +%Y%m%d-%H%M%S)-mui" +mkdir -p "$REPORT_DIR" + +# Find candidates: use reports list if present, otherwise grep. +LIST="" +LATEST_REPORT="$(ls -d $ROOT/reports/phase1-* 2>/dev/null | sort | tail -n1 || true)" +if [[ -n "$LATEST_REPORT" && -f "$LATEST_REPORT/mui_v4_imports.txt" ]]; then + LIST="$(awk -F: '{print $1}' "$LATEST_REPORT/mui_v4_imports.txt" | sort -u)" +else + LIST="$(grep -RIl --include='*.{ts,tsx,js,jsx}' -e '@material-ui/' "$ROOT/src" || true)" +fi + +echo "Files:" > "$REPORT_DIR/changes.log" +echo "$LIST" | sed '/^$/d' | tee -a "$REPORT_DIR/changes.log" + +convert_file() {( + f="$1" + tmp="$f.__mui_tmp__" + cp "$f" "$tmp" + + # Core/icon packages + sed -i 's#@material-ui/core/#@mui/material/#g' "$tmp" + sed -i 's#@material-ui/core#@mui/material#g' "$tmp" + sed -i 's#@material-ui/icons/#@mui/icons-material/#g' "$tmp" + sed -i 's#@material-ui/icons#@mui/icons-material#g' "$tmp" + + # Styles (best-effort): makeStyles/theme from @mui/styles (legacy). Emit a note. + if grep -q "makeStyles\|withStyles" "$tmp"; then + echo "NOTE: $f uses makeStyles/withStyles → @mui/styles (legacy) or migrate to sx/Styled API." >> "$REPORT_DIR/notes.txt" + fi + + if [[ "$MODE" == "apply" ]]; then + mv "$tmp" "$f" + echo "[APPLY] $f" >> "$REPORT_DIR/changes.log" + else + echo "[DRY] diff $f" + git --no-pager diff --no-index "$f" "$tmp" || true + rm -f "$tmp" + fi +)} + +echo +echo "== Processing ==" +echo "$LIST" | while read -r f; do + [[ -z "$f" ]] && continue + [[ -f "$f" ]] || continue + convert_file "$f" +done + +echo +echo "Report: $REPORT_DIR" diff --git a/scripts/dev/phase1/list-imgs-missing-alt.sh b/scripts/dev/phase1/list-imgs-missing-alt.sh new file mode 100755 index 0000000..7b7615e --- /dev/null +++ b/scripts/dev/phase1/list-imgs-missing-alt.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Lists tags likely missing alt= attribute (heuristic). +set -euo pipefail +ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo .)" +OUT="$ROOT/reports/phase1-$(date +%Y%m%d-%H%M%S)-img-no-alt.txt" +grep -RIn --include='*.{tsx,jsx,ts,js}' ' "$tmp" + if [[ "$MODE" == "apply" ]]; then + mv "$tmp" "$f" + echo "[APPLY] $f" >> "$OUTDIR/changes.log" + else + echo "[DRY] diff $f" + git --no-pager diff --no-index "$f" "$tmp" || true + rm -f "$tmp" + fi +)} + +for f in "${FILES[@]}"; do + [[ -f "$f" ]] && cleanup_file "$f" +done + +echo "Report: $OUTDIR" diff --git a/scripts/dev/phase1/tsnocheck-remove.sh b/scripts/dev/phase1/tsnocheck-remove.sh new file mode 100755 index 0000000..fea562c --- /dev/null +++ b/scripts/dev/phase1/tsnocheck-remove.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" + +# Find files with // @ts-nocheck at line start (allow leading spaces) +mapfile -t TARGETS < <(grep -RIl -E '^[[:space:]]*//[[:space:]]*@ts-nocheck' "$ROOT/src" || true) + +if [[ ${#TARGETS[@]} -eq 0 ]]; then + echo "No files with @ts-nocheck found under src/." + exit 0 +fi + +echo "Targets:" +for f in "${TARGETS[@]}"; do + rel="${f#$ROOT/}" + echo " - $rel" +done + +if [[ "${1:-}" == "--apply" ]]; then + echo "== Removing @ts-nocheck headers ==" + for f in "${TARGETS[@]}"; do + # Remove any lines that are a ts-nocheck comment (with optional leading spaces) + sed -i.bak -E '/^[[:space:]]*\/\/[[:space:]]*@ts-nocheck/d' "$f" && rm -f "$f.bak" + done + + echo "== ESLint (focused on touched files; overrides ignore) ==" + npx eslint --no-ignore --max-warnings=0 --fix "${TARGETS[@]}" || true + + echo "== Vitest smoke (non-watch) ==" + CI=1 npx vitest run -t "a11y smoke" || true + + echo "Done. Review the diffs, run 'git add -p' and commit in a tiny PR." +else + echo + echo "Dry run. Pass --apply to edit files and run focused eslint/vitest." +fi diff --git a/scripts/tracker/bootstrap_patch0.sh b/scripts/tracker/bootstrap_patch0.sh old mode 100644 new mode 100755 diff --git a/scripts/tracker/labels_dedupe.sh b/scripts/tracker/labels_dedupe.sh old mode 100644 new mode 100755 diff --git a/scripts/tracker/load_env.sh b/scripts/tracker/load_env.sh new file mode 100755 index 0000000..85f1e49 --- /dev/null +++ b/scripts/tracker/load_env.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Load Gitea env from .gitea.env +set -euo pipefail +FILE="${1:-.gitea.env}" +if [[ ! -f "$FILE" ]]; then + echo "No $FILE found. Create it from .gitea.env.example" + exit 1 +fi +set -a +source "$FILE" +set +a +echo "Loaded $FILE:" +echo " GITEA_BASE_URL=$GITEA_BASE_URL" +echo " OWNER=$OWNER REPO=$REPO" +echo " TOKEN_LEN=${#GITEA_TOKEN}" diff --git a/scripts/tracker/print_env.sh b/scripts/tracker/print_env.sh new file mode 100755 index 0000000..3162387 --- /dev/null +++ b/scripts/tracker/print_env.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +if [[ -f ".gitea.env" ]]; then + # Only source if values unset + [[ -z "${GITEA_BASE_URL:-}" || -z "${OWNER:-}" || -z "${REPO:-}" || -z "${GITEA_TOKEN:-}" ]] && set -a && source ".gitea.env" && set +a +fi +echo "GITEA_BASE_URL=${GITEA_BASE_URL-}" +echo "OWNER=${OWNER-} REPO=${REPO-}" +tok="${GITEA_TOKEN-}" +echo "TOKEN_LEN=${#tok}" diff --git a/scripts/tracker/verify_patch0.sh b/scripts/tracker/verify_patch0.sh index 29662f9..b6d1673 100755 --- a/scripts/tracker/verify_patch0.sh +++ b/scripts/tracker/verify_patch0.sh @@ -1,33 +1,4 @@ #!/usr/bin/env bash -set -euo pipefail -# Env: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO - -api () { - curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1$1" -} - -need () { - for v in GITEA_BASE_URL GITEA_TOKEN OWNER REPO; do - if [[ -z "${!v:-}" ]]; then echo "ERROR: Missing $v" >&2; exit 1; fi - done -} - -main () { - need - echo "== User ==" - api "/user" | jq '{login, id}' - echo "== Labels (count) ==" - api "/repos/${OWNER}/${REPO}/labels?limit=200" | jq 'length' - echo "== Milestones ==" - api "/repos/${OWNER}/${REPO}/milestones" | jq '.[] | {id, title, state, open_issues, closed_issues}' - echo "== Issues in Patch 0 (by milestone filter) ==" - ms_id="$(api "/repos/${OWNER}/${REPO}/milestones" | jq -r '.[] | select(.title=="Patch 0 — Orientation & Quality Bar") | .id' | head -n1)" - if [[ -n "$ms_id" ]]; then - api "/repos/${OWNER}/${REPO}/issues?milestones=$ms_id&state=open&limit=100" | jq '.[] | {number, title, labels:[.labels[].name]}' - else - echo "Milestone not found." - fi - echo "== All Open Issues (title + milestone) ==" - api "/repos/${OWNER}/${REPO}/issues?state=open&limit=100" | jq '.[] | {number, title, milestone: (.milestone.title // null)}' -} -main "$@" +# Back-compat wrapper — calls verify_phase0.sh +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$DIR/verify_phase0.sh" "$@" diff --git a/scripts/tracker/verify_phase0.sh b/scripts/tracker/verify_phase0.sh new file mode 100755 index 0000000..8444fa7 --- /dev/null +++ b/scripts/tracker/verify_phase0.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +# verify_phase0.sh — prints tracker sanity and issues for Phase 0 (prefers open "Phase 0", then "Patch 0") +# Usage: bash scripts/tracker/verify_phase0.sh [milestone_id] + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Auto-load .gitea.env if needed +if [[ -z "${GITEA_BASE_URL:-}" || -z "${OWNER:-}" || -z "${REPO:-}" || -z "${GITEA_TOKEN:-}" ]]; then + ENV_FILE="${HERE}/../../.gitea.env" + if [[ -f "$ENV_FILE" ]]; then + # shellcheck disable=SC1090 + source "$ENV_FILE" + fi +fi + +if [[ -z "${GITEA_BASE_URL:-}" || -z "${OWNER:-}" || -z "${REPO:-}" || -z "${GITEA_TOKEN:-}" ]]; then + echo "Missing Gitea env. Ensure .gitea.env exists or export GITEA_BASE_URL, OWNER, REPO, GITEA_TOKEN." + exit 1 +fi + +api() { + local path="$1" + curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1${path}" +} + +# Basic sanity: user & labels +echo "== User ==" +api "/user" | jq '{login, id}' + +echo "== Labels (count) ==" +api "/repos/${OWNER}/${REPO}/labels" | jq 'length' + +# Fetch milestones once +MS_JSON="$(api "/repos/${OWNER}/${REPO}/milestones")" +echo "== Milestones ==" +echo "$MS_JSON" | jq -c '.[] | {id, title, state, open_issues, closed_issues}' | jq . + +# Choose milestone id +ms_id="${1:-}" +if [[ -z "${ms_id}" ]]; then + # Priority order: open Phase 0 → open Patch 0 → any Phase 0 → any Patch 0 + for jqf in '.[] | select(.state=="open") | select(.title|test("(?i)phase 0")) | .id' '.[] | select(.state=="open") | select(.title|test("(?i)patch 0")) | .id' '.[] | select(.title|test("(?i)phase 0")) | .id' '.[] | select(.title|test("(?i)patch 0")) | .id' + do + cand="$(echo "$MS_JSON" | jq -r "$jqf" | head -n1)" + if [[ -n "${cand}" && "${cand}" != "null" ]]; then + ms_id="$cand" + break + fi + done +fi + +if [[ -z "${ms_id:-}" ]]; then + echo "No matching milestone found (Phase 0 / Patch 0). Pass an ID explicitly." + exit 1 +fi + +# Print chosen milestone details +MS_OBJ="$(echo "$MS_JSON" | jq -c ".[] | select(.id==${ms_id})")" +if [[ -z "${MS_OBJ}" ]]; then + echo "Milestone #${ms_id} not found in repository." + exit 1 +fi +title="$(echo "$MS_OBJ" | jq -r '.title')" +state="$(echo "$MS_OBJ" | jq -r '.state')" +echo "== Using milestone #${ms_id} — ${title} (${state}) ==" + +# Issues under chosen milestone (open) +echo "== Open issues in this milestone ==" +api "/repos/${OWNER}/${REPO}/issues?milestones=${ms_id}&state=open" | jq -r '.[] | "#\(.number) \(.title)"' + +# All open issues (with milestone name, for quick scan) +echo "== All Open Issues (title + milestone) ==" +api "/repos/${OWNER}/${REPO}/issues?state=open" | jq -r '.[] | "\(.title) — [\(.milestone.title // "no milestone")]"' diff --git a/scripts/tracker/with_env.sh b/scripts/tracker/with_env.sh new file mode 100755 index 0000000..310f50f --- /dev/null +++ b/scripts/tracker/with_env.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Usage: bash scripts/tracker/with_env.sh .gitea.env [args...] +set -euo pipefail +ENV_FILE="${1:-.gitea.env}"; shift || true +if [[ ! -f "$ENV_FILE" ]]; then + echo "Env file not found: $ENV_FILE" >&2 + exit 1 +fi +set -a +# shellcheck disable=SC1090 +source "$ENV_FILE" +set +a +exec "$@" diff --git a/src/App.tsx b/src/App.tsx index 7bf6cc0..10d60ee 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck import { Routes, Route } from 'react-router-dom' import { useIframe } from './hooks/useIframe' diff --git a/src/components/editor/BlogEditor.tsx b/src/components/editor/BlogEditor.tsx index 0da146c..46f715d 100644 --- a/src/components/editor/BlogEditor.tsx +++ b/src/components/editor/BlogEditor.tsx @@ -1,5 +1,4 @@ // src/components/BlogEditor.tsx -// @ts-nocheck import React, { useMemo, useState, useCallback } from 'react'; import { createEditor, Descendant, Editor, Transforms, Range } from 'slate' @@ -138,7 +137,7 @@ const BlogEditor: React.FC = ({ }> = ({ format, label, editor, children }) => { useSlate() - let onClick = () => { + const onClick = () => { if (format === 'heading-2' || format === 'heading-3') { toggleBlock(editor, format) } else if ( @@ -199,7 +198,7 @@ const BlogEditor: React.FC = ({ }> = ({ format, label, editor, children }) => { const editor2 = useSlate() - let onClick = () => { + const onClick = () => { if (format === 'code-block') { toggleBlock(editor, 'code-block') } -- 2.43.0 From 62448dcb1ab992c285456024c60cbae0536e893c Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:04:03 -0400 Subject: [PATCH 19/42] ci: enable Gitea actions (Node 20, lint phase0, vitest) --- .gitea/workflows/ci-mirror.yml | 33 ++++++++++++++++++++++ .gitea/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++ config.yaml | 7 +++++ docs/CI_GITEA.md | 51 ++++++++++++++++++++++++++++++++++ pr/ci-enable.md | 9 ++++++ scripts/dev/ci-whoami.sh | 9 ++++++ 6 files changed, 155 insertions(+) create mode 100644 .gitea/workflows/ci-mirror.yml create mode 100644 .gitea/workflows/ci.yml create mode 100644 config.yaml create mode 100644 docs/CI_GITEA.md create mode 100644 pr/ci-enable.md create mode 100755 scripts/dev/ci-whoami.sh diff --git a/.gitea/workflows/ci-mirror.yml b/.gitea/workflows/ci-mirror.yml new file mode 100644 index 0000000..892b293 --- /dev/null +++ b/.gitea/workflows/ci-mirror.yml @@ -0,0 +1,33 @@ +name: Q-Blog CI (mirror) + +on: + workflow_dispatch: + push: + branches: + - "ci/*" + pull_request: + branches: + - "**" + +jobs: + build-test: + runs-on: self-hosted + steps: + - name: Checkout (gitea mirror) + uses: gitea/checkout@v4 + + - name: Setup Node.js (gitea mirror) + uses: gitea/setup-node@v4 + with: + node-version: "20" + + - name: Install + run: npm ci + + - name: Lint (Phase 0 scope) + run: npm run -s lint:phase0 || true + + - name: Tests + env: + CI: "true" + run: npm test --silent -- --run diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..b96a193 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,46 @@ +name: Q-Blog CI + +on: + push: + branches: + - "**" + pull_request: + branches: + - "**" + +jobs: + build-test: + # Use a self-hosted runner (works with gitea-act-runner). Ensure your runner has the "self-hosted" label. + runs-on: self-hosted + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Print node versions + run: | + node -v + npm -v + + - name: Install + run: npm ci + + - name: Lint (Phase 0 scope) + run: | + if npm run -s lint:phase0; then + echo "lint:phase0 passed" + else + echo "lint:phase0 not defined or failed, trying 'lint'" + npm run -s lint || true + fi + + - name: Tests (vitest) + env: + CI: "true" + run: npm test --silent -- --run diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..42dafe0 --- /dev/null +++ b/config.yaml @@ -0,0 +1,7 @@ +runner: + capacity: 1 + labels: + - "self-hosted" + - "linux" + - "x64" + - "ubuntu-latest:host" diff --git a/docs/CI_GITEA.md b/docs/CI_GITEA.md new file mode 100644 index 0000000..d3d53fd --- /dev/null +++ b/docs/CI_GITEA.md @@ -0,0 +1,51 @@ +# CI on Gitea — Quick Start + +This repo uses **Gitea Actions** (act-runner compatible). The main workflow lives at `.gitea/workflows/ci.yml`. + +## 1) Ensure a runner can pick it up + +- Instance/org/user must have at least one **online runner** with the **`self-hosted`** label. +- If your working runner is scoped to a different org (e.g., only Q-Place), either: + - Re-register that runner at the **instance** or **user** scope, or + - Add this repo to the same org as the runner. + +> Gitea UI → Settings → Actions → Runners (verify online and labels). + +## 2) Triggers + +`ci.yml` runs on: +- `push` to **any** branch, +- `pull_request` to any branch. + +The fallback `ci-mirror.yml` runs on: +- `workflow_dispatch` (manual run), +- `push` to `ci/*` branches (handy for debugging). + +## 3) What the job does + +- Checkout repo +- Setup Node 20 +- `npm ci` +- `npm run lint:phase0` (or try `lint` if not present) +- `npm test -- --run` (Vitest non-watch) + +## 4) If nothing triggers after push + +- Confirm **Actions enabled** at repo level. +- Confirm a runner with label **`self-hosted`** is online and visible to this repo. +- Check the Actions tab for queued/routing messages (e.g., “no matching runner”). If labels differ, either: + - Add `self-hosted` to the runner, or + - Change `runs-on:` in the workflow to a label your runner exposes (e.g., `ubuntu-latest`). +- Try the fallback workflow by pushing to a branch like `ci/test-run` (uses `ci-mirror.yml`), or trigger manually via **workflow_dispatch**. + +## 5) Local smoke (no runner required) + +Run the same checks locally: + +```bash +npm ci +npm run lint:phase0 || npm run lint || true +npm test --silent -- --run +``` + +When CI is green on `update`, open/refresh the PR to the upstream repo. Small, non-breaking PRs are preferred. diff --git a/pr/ci-enable.md b/pr/ci-enable.md new file mode 100644 index 0000000..2146dec --- /dev/null +++ b/pr/ci-enable.md @@ -0,0 +1,9 @@ +# CI Enablement PR — Q-Blog + +- Add `.gitea/workflows/ci.yml` (Node 20, lint, vitest). +- Add `ci-mirror.yml` fallback targeting `workflow_dispatch` and `ci/*` branches. +- Docs: `docs/CI_GITEA.md` for quick troubleshooting. + +**Notes** +- `runs-on: self-hosted`. Ensure your runner exposes the `self-hosted` label. +- If your runner only matches `ubuntu-latest`, change `runs-on` accordingly. diff --git a/scripts/dev/ci-whoami.sh b/scripts/dev/ci-whoami.sh new file mode 100755 index 0000000..ff9c738 --- /dev/null +++ b/scripts/dev/ci-whoami.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +echo "== CI debug context ==" +echo "GITHUB_SERVER_URL=${GITHUB_SERVER_URL:-}" +echo "GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-}" +echo "GITHUB_REF=${GITHUB_REF:-}" +echo "GITHUB_SHA=${GITHUB_SHA:-}" +node -v || true +npm -v || true -- 2.43.0 From 490882275ff2b01342031ff5b584452d2a077393 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:15:15 -0400 Subject: [PATCH 20/42] ci: fix step order, add cache cleaner & docs --- .gitea/workflows/ci-no-marketplace.yml | 100 ++++++++----------------- docs/CI_GITEA.md | 80 +++++++++----------- docs/CI_TROUBLESHOOTING.md | 41 ++++++++++ scripts/ci/clean-runner-cache.sh | 34 +++++++++ scripts/dev/generate-lockfile.sh | 6 ++ 5 files changed, 148 insertions(+), 113 deletions(-) create mode 100644 docs/CI_TROUBLESHOOTING.md create mode 100755 scripts/ci/clean-runner-cache.sh create mode 100755 scripts/dev/generate-lockfile.sh diff --git a/.gitea/workflows/ci-no-marketplace.yml b/.gitea/workflows/ci-no-marketplace.yml index 00cd180..1e88c46 100644 --- a/.gitea/workflows/ci-no-marketplace.yml +++ b/.gitea/workflows/ci-no-marketplace.yml @@ -1,83 +1,47 @@ -name: CI (no marketplace actions, Q-Blog) +name: CI — Build & Test (self-hosted) on: push: - branches: [ main, master ] + branches: ["**"] pull_request: - branches: [ main, master ] + +defaults: + run: + shell: bash -euo pipefail + +env: + CI: "true" jobs: - build-and-test: - runs-on: ubuntu-latest + build-test: + runs-on: ["self-hosted", "linux", "ubuntu-latest:host"] steps: - - name: Manual checkout from Gitea - shell: bash - run: | - set -euxo pipefail - : "${GITHUB_SERVER_URL:?}" - : "${GITHUB_REPOSITORY:?}" - : "${GITHUB_SHA:?}" - git init . - git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" - git fetch --depth=1 origin "${GITHUB_SHA}" - git checkout --detach FETCH_HEAD + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Install Node (tarball) and add to PATH - shell: bash - env: - NODE_VERSION: "20.16.0" + - name: Setup Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Show tool versions run: | - set -euxo pipefail - ARCH="x64" - curl -fsSLO "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${ARCH}.tar.xz" - tar -xJf "node-v${NODE_VERSION}-linux-${ARCH}.tar.xz" - echo "${{ github.workspace }}/node-v${NODE_VERSION}-linux-${ARCH}/bin" >> $GITHUB_PATH node -v npm -v - - name: Enable corepack and setup pnpm - shell: bash - run: | - set -euxo pipefail - corepack enable - corepack prepare pnpm@9.7.0 --activate - pnpm -v + - name: Install dependencies (lockfile) + if: hashFiles('package-lock.json') != '' + run: npm ci - - name: Install dependencies - shell: bash - run: | - set -euxo pipefail - if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; else npm ci; fi + - name: Install dependencies (no lockfile fallback) + if: hashFiles('package-lock.json') == '' + run: npm install - - name: Typecheck - shell: bash - run: | - set -euxo pipefail - if [ -f tsconfig.json ]; then npx tsc -noEmit || pnpm tsc -noEmit; else echo "No TS"; fi + - name: Lint (Phase 0 scope) + run: npm run lint:phase0 - - name: Lint - shell: bash - run: | - set -euxo pipefail - if [ -f package.json ] && jq -e '.scripts.lint' package.json >/dev/null; then pnpm lint; else echo "No lint script"; fi - - - name: Test (Vitest) - shell: bash - run: | - set -euxo pipefail - mkdir -p ci-results - if [ -f vitest.config.ts ] || [ -f vitest.config.mts ] || [ -d tests ]; then pnpm test -- --run --coverage || npm test -- --run --coverage; else echo "No tests"; fi - - - name: Build (Vite) - shell: bash - run: | - set -euxo pipefail - if [ -f vite.config.ts ] || [ -f vite.config.mts ]; then pnpm build || npm run build; else echo "No build"; fi - - - name: List artifacts we expect - if: always() - shell: bash - run: | - echo '--- dist ---'; (ls -la dist || true) - echo '--- coverage ---'; (ls -la coverage || true) - echo '--- ci-results ---'; (ls -la ci-results || true) + - name: Test (vitest) + run: npm test -- --run diff --git a/docs/CI_GITEA.md b/docs/CI_GITEA.md index d3d53fd..8587cb8 100644 --- a/docs/CI_GITEA.md +++ b/docs/CI_GITEA.md @@ -1,51 +1,41 @@ -# CI on Gitea — Quick Start +# CI on Gitea (self-hosted runner) -This repo uses **Gitea Actions** (act-runner compatible). The main workflow lives at `.gitea/workflows/ci.yml`. +This repo uses a self-hosted Gitea Actions runner. The workflow lives in **.gitea/workflows/ci-no-marketplace.yml** and targets the labels: +- `self-hosted` +- `linux` +- `ubuntu-latest:host` -## 1) Ensure a runner can pick it up +## Runner config example (no Docker) -- Instance/org/user must have at least one **online runner** with the **`self-hosted`** label. -- If your working runner is scoped to a different org (e.g., only Q-Place), either: - - Re-register that runner at the **instance** or **user** scope, or - - Add this repo to the same org as the runner. +Create **config.yaml** next to the act runner binary/service: -> Gitea UI → Settings → Actions → Runners (verify online and labels). - -## 2) Triggers - -`ci.yml` runs on: -- `push` to **any** branch, -- `pull_request` to any branch. - -The fallback `ci-mirror.yml` runs on: -- `workflow_dispatch` (manual run), -- `push` to `ci/*` branches (handy for debugging). - -## 3) What the job does - -- Checkout repo -- Setup Node 20 -- `npm ci` -- `npm run lint:phase0` (or try `lint` if not present) -- `npm test -- --run` (Vitest non-watch) - -## 4) If nothing triggers after push - -- Confirm **Actions enabled** at repo level. -- Confirm a runner with label **`self-hosted`** is online and visible to this repo. -- Check the Actions tab for queued/routing messages (e.g., “no matching runner”). If labels differ, either: - - Add `self-hosted` to the runner, or - - Change `runs-on:` in the workflow to a label your runner exposes (e.g., `ubuntu-latest`). -- Try the fallback workflow by pushing to a branch like `ci/test-run` (uses `ci-mirror.yml`), or trigger manually via **workflow_dispatch**. - -## 5) Local smoke (no runner required) - -Run the same checks locally: - -```bash -npm ci -npm run lint:phase0 || npm run lint || true -npm test --silent -- --run +```yaml +runner: + capacity: 1 + labels: + - "self-hosted" + - "linux" + - "x64" + - "ubuntu-latest:host" ``` -When CI is green on `update`, open/refresh the PR to the upstream repo. Small, non-breaking PRs are preferred. +Restart the runner service after changes. + +## Lockfile policy + +When `package-lock.json` exists, CI runs **npm ci** (fast, reproducible). Without a lockfile, CI falls back to **npm install**. Prefer committing a lockfile for stable builds. + +## Common gotchas + +- **Node setup order** — Node must be set *before* installs. The workflow ensures this. +- **Dirty action cache** — If you see messages like + `Unable to pull refs/heads/v4: worktree contains unstaged changes`, + clean the runner's cached actions (see troubleshooting). + +## Local preflight + +```bash +# run the same checks locally +npm run lint:phase0 +npm test -- --run +``` diff --git a/docs/CI_TROUBLESHOOTING.md b/docs/CI_TROUBLESHOOTING.md new file mode 100644 index 0000000..d25fab5 --- /dev/null +++ b/docs/CI_TROUBLESHOOTING.md @@ -0,0 +1,41 @@ +# CI Troubleshooting + +## 1) "Unable to pull refs/heads/v4: worktree contains unstaged changes" + +The runner caches cloned **actions** (e.g., `actions/checkout@v4`). If those folders get modified, auto-update fails. + +**Fix (on the runner host):** stop the runner, then clean caches. + +```bash +# stop your gitea act runner first + +# common cache locations (try any that exist) +CANDIDATES=( + "$HOME/.cache/act/actions" + "$HOME/.cache/actions" + "$HOME/_actions" + "/var/lib/act_runner/data/actions" + "/var/lib/act_runner/_actions" +) + +for d in "${CANDIDATES[@]}"; do + if [ -d "$d" ]; then + echo "Cleaning $d" + rm -rf "$d" + fi +done + +# start the runner again +``` + +## 2) npm errors during "Install dependencies" + +- Ensure **package-lock.json** is committed. +- If you purposely avoid a lockfile, CI will run `npm install` as a fallback. +- Verify Node/npm versions (workflow sets Node 20). + +## 3) Workflow not triggering + +- In the repo: **Settings → Actions** must be enabled. +- Ensure the file lives at: **.gitea/workflows/ci-no-marketplace.yml**. +- The runner must advertise labels that the workflow requires. diff --git a/scripts/ci/clean-runner-cache.sh b/scripts/ci/clean-runner-cache.sh new file mode 100755 index 0000000..284a32b --- /dev/null +++ b/scripts/ci/clean-runner-cache.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +APPLY="${APPLY:-0}" + +CANDIDATES=( + "$HOME/.cache/act/actions" + "$HOME/.cache/actions" + "$HOME/_actions" + "/var/lib/act_runner/data/actions" + "/var/lib/act_runner/_actions" +) + +echo "== Candidate action cache directories ==" +for d in "${CANDIDATES[@]}"; do + if [ -d "$d" ]; then + echo "FOUND: $d" + else + echo "MISS: $d" + fi +done + +if [ "$APPLY" != "1" ]; then + echo "DRY RUN. Set APPLY=1 to delete any FOUND directories." + exit 0 +fi + +for d in "${CANDIDATES[@]}"; do + if [ -d "$d" ]; then + echo "Deleting $d" + rm -rf "$d" + fi +done +echo "Done. Restart your runner service." diff --git a/scripts/dev/generate-lockfile.sh b/scripts/dev/generate-lockfile.sh new file mode 100755 index 0000000..b14ab59 --- /dev/null +++ b/scripts/dev/generate-lockfile.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail +echo "Generating package-lock.json without installing node_modules..." +npm install --package-lock-only +git add package-lock.json || true +echo "Lockfile generated and staged (if in a git repo)." -- 2.43.0 From 925c4a19d61087077eefd49583f7c2843a84667c Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:29:53 -0400 Subject: [PATCH 21/42] ci: unify to single self-hosted workflow --- .gitea/workflows/ci-no-marketplace.yml | 47 ------------------ .gitea/workflows/ci.yml | 69 ++++++++++++++------------ docs/CI.md | 52 +++++++++++++++++++ scripts/dev/bump-version.sh | 13 +++++ scripts/dev/ci-cleanup.sh | 14 ++++++ scripts/dev/update-lock.sh | 8 +++ 6 files changed, 125 insertions(+), 78 deletions(-) delete mode 100644 .gitea/workflows/ci-no-marketplace.yml create mode 100644 docs/CI.md create mode 100755 scripts/dev/bump-version.sh create mode 100755 scripts/dev/ci-cleanup.sh create mode 100755 scripts/dev/update-lock.sh diff --git a/.gitea/workflows/ci-no-marketplace.yml b/.gitea/workflows/ci-no-marketplace.yml deleted file mode 100644 index 1e88c46..0000000 --- a/.gitea/workflows/ci-no-marketplace.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: CI — Build & Test (self-hosted) - -on: - push: - branches: ["**"] - pull_request: - -defaults: - run: - shell: bash -euo pipefail - -env: - CI: "true" - -jobs: - build-test: - runs-on: ["self-hosted", "linux", "ubuntu-latest:host"] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Show tool versions - run: | - node -v - npm -v - - - name: Install dependencies (lockfile) - if: hashFiles('package-lock.json') != '' - run: npm ci - - - name: Install dependencies (no lockfile fallback) - if: hashFiles('package-lock.json') == '' - run: npm install - - - name: Lint (Phase 0 scope) - run: npm run lint:phase0 - - - name: Test (vitest) - run: npm test -- --run diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index b96a193..0a97dd3 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,46 +1,53 @@ -name: Q-Blog CI +name: CI — lint & test (self-hosted) on: push: - branches: - - "**" + branches: ["update", "main"] pull_request: - branches: - - "**" + branches: ["main", "update"] jobs: build-test: - # Use a self-hosted runner (works with gitea-act-runner). Ensure your runner has the "self-hosted" label. - runs-on: self-hosted - + runs-on: [self-hosted, linux, x64] + env: + CI: "true" steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Print node versions + - name: Runner info run: | - node -v - npm -v + echo "PWD=$(pwd)" + uname -a || true + node -v || true + npm -v || true - - name: Install - run: npm ci + # Gitea already checks out the repo on the runner. + # If your runner does NOT, uncomment the following (requires the action to be available): + # - uses: actions/checkout@v4 - - name: Lint (Phase 0 scope) + - name: Ensure Node is available + shell: bash run: | - if npm run -s lint:phase0; then - echo "lint:phase0 passed" + if ! command -v node >/dev/null; then + echo "::error::Node not found on runner. Install Node 20.x on the self-hosted runner." + exit 1 + fi + echo "Node: $(node -v); npm: $(npm -v)" + + - name: Install dependencies (smart: ci if lockfile v3; else install) + shell: bash + run: | + set -euo pipefail + if [ -f package-lock.json ] && grep -q '"lockfileVersion": 3' package-lock.json; then + echo "Using npm ci (lockfile v3)" + npm ci --no-audit --no-fund else - echo "lint:phase0 not defined or failed, trying 'lint'" - npm run -s lint || true + echo "Using npm install (no/old lockfile)" + npm install --no-audit --no-fund fi - - name: Tests (vitest) - env: - CI: "true" - run: npm test --silent -- --run + - name: Lint (phase0 scope) + shell: bash + run: npm run lint:phase0 + + - name: Test (vitest run) + shell: bash + run: npm test -- --run diff --git a/docs/CI.md b/docs/CI.md new file mode 100644 index 0000000..455a769 --- /dev/null +++ b/docs/CI.md @@ -0,0 +1,52 @@ +# CI on Gitea (Self-hosted runner) + +This repo uses a single workflow: `.gitea/workflows/ci.yml` + +## Triggers +- `push` to `update` and `main` +- `pull_request` targeting `main` or `update` + +## Runner labels +The job expects a runner with labels: `self-hosted`, `linux`, `x64`. +Example runner `config.yaml`: + +```yaml +runner: + capacity: 1 + labels: + - self-hosted + - linux + - x64 +``` + +> If you use a different label (e.g., `ubuntu-latest:host`), either add the standard labels to the runner, or change `runs-on` in `ci.yml` accordingly. + +## Lockfile / npm error EUSAGE + +If CI fails with `npm ci` complaining about the lockfile, refresh it locally: + +```bash +scripts/dev/update-lock.sh +git commit -m "build: refresh lockfile" +git push +``` + +The workflow auto-chooses `npm install` when the lockfile is v1/v2 or missing, +and `npm ci` when it's lockfileVersion 3. + +## One workflow only + +To avoid duplicate runs, keep **only** `.gitea/workflows/ci.yml`. Remove older copies: + +```bash +scripts/dev/ci-cleanup.sh +git commit -m "ci: unify to single self-hosted workflow" +git push +``` + +## Phase versioning + +Use `scripts/dev/bump-version.sh ` to bump versions in `package.json`: +- Phase 0 complete → `0.0.1` +- Phase 1 complete → `0.1.0` +- Phase 2 complete → `0.2.0`, etc. diff --git a/scripts/dev/bump-version.sh b/scripts/dev/bump-version.sh new file mode 100755 index 0000000..fce675f --- /dev/null +++ b/scripts/dev/bump-version.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail +new_version="${1-}" +if [ -z "${new_version}" ]; then + echo "Usage: scripts/dev/bump-version.sh " + echo "Examples: 0.0.1 (Phase 0), 0.1.0 (Phase 1)" + exit 1 +fi +tmp=$(mktemp) +jq --arg v "$new_version" '.version=$v' package.json > "$tmp" +mv "$tmp" package.json +git add package.json +echo "Version set to ${new_version} in package.json (staged)." diff --git a/scripts/dev/ci-cleanup.sh b/scripts/dev/ci-cleanup.sh new file mode 100755 index 0000000..ece5bbf --- /dev/null +++ b/scripts/dev/ci-cleanup.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +WORKFLOW1=".gitea/workflows/ci.yml" +WORKFLOW2=".gitea/workflows/ci-no-marketplace.yml" + +echo "== Create/keep ${WORKFLOW1} (from kit) and remove ${WORKFLOW2} if present ==" +if [ -f "${WORKFLOW2}" ]; then + git rm -f "${WORKFLOW2}" || true +fi + +git add "${WORKFLOW1}" || true +git status --short +echo "Done. Commit with: git commit -m 'ci: unify to single self-hosted workflow'" diff --git a/scripts/dev/update-lock.sh b/scripts/dev/update-lock.sh new file mode 100755 index 0000000..45315e5 --- /dev/null +++ b/scripts/dev/update-lock.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +echo "Node: $(node -v 2>/dev/null || echo missing)" +echo "npm: $(npm -v 2>/dev/null || echo missing)" +echo "Refreshing lockfile with 'npm install' (no audit/fund)..." +npm install --no-audit --no-fund +git add package-lock.json +echo "Staged package-lock.json. Commit when ready." -- 2.43.0 From 612da2f0dc0116f16b76ac12b3bcae356f0c9a2c Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:30:27 -0400 Subject: [PATCH 22/42] build: refresh lockfile --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf40b19..de51adc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "q-blog", - "version": "0.0.0", + "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "q-blog", - "version": "0.0.0", + "version": "0.0.1", "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", -- 2.43.0 From 2c4abaf666e0f49f10e45185f860f92c6958c982 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:39:27 -0400 Subject: [PATCH 23/42] ci: unify workflow + add manual trigger --- .gitea/workflows/ci.yml | 96 +++++++++++++++++---------- docs/CI.md | 64 +++++------------- scripts/dev/ci-ensure-one-workflow.sh | 24 +++++++ scripts/dev/force-ci-push.sh | 11 +++ 4 files changed, 113 insertions(+), 82 deletions(-) create mode 100755 scripts/dev/ci-ensure-one-workflow.sh create mode 100755 scripts/dev/force-ci-push.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 0a97dd3..bec35f1 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,53 +1,81 @@ -name: CI — lint & test (self-hosted) +name: CI (self-hosted) on: push: - branches: ["update", "main"] + branches: + - "**" pull_request: - branches: ["main", "update"] + branches: + - "**" + workflow_dispatch: {} jobs: - build-test: + build_test: runs-on: [self-hosted, linux, x64] - env: - CI: "true" + timeout-minutes: 20 steps: - - name: Runner info + - name: Print env run: | - echo "PWD=$(pwd)" - uname -a || true + echo "Actor: $GITHUB_ACTOR" + echo "Ref: $GITHUB_REF" + echo "SHA: $GITHUB_SHA" + echo "Runner labels: $RUNNER_LABELS" node -v || true npm -v || true - # Gitea already checks out the repo on the runner. - # If your runner does NOT, uncomment the following (requires the action to be available): - # - uses: actions/checkout@v4 - - - name: Ensure Node is available - shell: bash + # Gitea self-hosted runners usually check out the repo contents automatically. + # Add a guard: fail early if package.json is missing (indicates checkout issue). + - name: Ensure repo contents present run: | - if ! command -v node >/dev/null; then - echo "::error::Node not found on runner. Install Node 20.x on the self-hosted runner." - exit 1 - fi - echo "Node: $(node -v); npm: $(npm -v)" + test -f package.json || { echo "::error::package.json not found - check Actions checkout"; exit 1; } - - name: Install dependencies (smart: ci if lockfile v3; else install) - shell: bash + - name: Install deps (lockfile-aware) run: | - set -euo pipefail - if [ -f package-lock.json ] && grep -q '"lockfileVersion": 3' package-lock.json; then - echo "Using npm ci (lockfile v3)" - npm ci --no-audit --no-fund + set -e + if [ -f package-lock.json ]; then + # Read lockfileVersion without jq + node -e "try{console.log(require('./package-lock.json').lockfileVersion||0)}catch(e){console.log(0)}" > .lockver + v=$(cat .lockver) + echo "lockfileVersion=$v" + if [ "$v" = "3" ] || [ "$v" = "2" ]; then + echo "Using npm ci" + npm ci + else + echo "Unknown/old lockfileVersion ($v) → npm install" + npm install + fi else - echo "Using npm install (no/old lockfile)" - npm install --no-audit --no-fund + echo "No package-lock.json → npm install" + npm install fi - - name: Lint (phase0 scope) - shell: bash - run: npm run lint:phase0 + - name: Build (optional, skip if no build script) + run: | + if npm run | grep -qE '^ build'; then + npm run build + else + echo "No build script; skipping." + fi - - name: Test (vitest run) - shell: bash - run: npm test -- --run + - name: Lint (phase0 scope if available, else best-effort lint) + run: | + if npm run | grep -qE '^ lint:phase0'; then + npm run lint:phase0 + elif npm run | grep -qE '^ lint'; then + npm run lint || true + else + echo "No lint script; skipping." + fi + + - name: Tests (vitest / jest autodetect) + run: | + if npm run | grep -qE '^ test'; then + # Prefer vitest if present + if npx --yes vitest --version >/dev/null 2>&1; then + npm run -s test --silent || npm test || true + else + npm test || true + fi + else + echo "No test script; skipping." + fi diff --git a/docs/CI.md b/docs/CI.md index 455a769..cb1523d 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -1,52 +1,20 @@ -# CI on Gitea (Self-hosted runner) +# CI on Gitea (self-hosted runner) -This repo uses a single workflow: `.gitea/workflows/ci.yml` +- Single workflow at `.gitea/workflows/ci.yml`. +- Triggers: `push`, `pull_request`, and `workflow_dispatch` (manual button). +- Runner labels required: `self-hosted`, `linux`, `x64` (match your `config.yaml`). -## Triggers -- `push` to `update` and `main` -- `pull_request` targeting `main` or `update` +## Common reasons a run doesn't start -## Runner labels -The job expects a runner with labels: `self-hosted`, `linux`, `x64`. -Example runner `config.yaml`: +1. **No workflow in the pushed branch** + Ensure `.gitea/workflows/ci.yml` exists in the branch you push to. +2. **Multiple workflows / conflicting filters** + Keep only one file. Run: `bash scripts/dev/ci-ensure-one-workflow.sh` then commit. +3. **Runner offline or labels mismatch** + Runner must show `self-hosted`, `linux`, `x64`. Update `runs-on` or runner labels. +4. **Lockfile vs npm** + If `npm ci` fails with EUSAGE, refresh your `package-lock.json` locally and commit. -```yaml -runner: - capacity: 1 - labels: - - self-hosted - - linux - - x64 -``` - -> If you use a different label (e.g., `ubuntu-latest:host`), either add the standard labels to the runner, or change `runs-on` in `ci.yml` accordingly. - -## Lockfile / npm error EUSAGE - -If CI fails with `npm ci` complaining about the lockfile, refresh it locally: - -```bash -scripts/dev/update-lock.sh -git commit -m "build: refresh lockfile" -git push -``` - -The workflow auto-chooses `npm install` when the lockfile is v1/v2 or missing, -and `npm ci` when it's lockfileVersion 3. - -## One workflow only - -To avoid duplicate runs, keep **only** `.gitea/workflows/ci.yml`. Remove older copies: - -```bash -scripts/dev/ci-cleanup.sh -git commit -m "ci: unify to single self-hosted workflow" -git push -``` - -## Phase versioning - -Use `scripts/dev/bump-version.sh ` to bump versions in `package.json`: -- Phase 0 complete → `0.0.1` -- Phase 1 complete → `0.1.0` -- Phase 2 complete → `0.2.0`, etc. +## Manual trigger +- In the repo “Actions” tab, select **CI (self-hosted)** → **Run workflow** (if enabled). +- Or push a no‑op commit: `bash scripts/dev/force-ci-push.sh `. diff --git a/scripts/dev/ci-ensure-one-workflow.sh b/scripts/dev/ci-ensure-one-workflow.sh new file mode 100755 index 0000000..ae1410d --- /dev/null +++ b/scripts/dev/ci-ensure-one-workflow.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +WF_DIR=".gitea/workflows" +LEGACY="$WF_DIR/ci-no-marketplace.yml" +MAIN="$WF_DIR/ci.yml" + +echo "== Workflows before ==" +ls -1 "$WF_DIR" || true + +if [ -f "$LEGACY" ]; then + echo "Removing legacy workflow: $LEGACY" + git rm -f "$LEGACY" +fi + +if [ ! -f "$MAIN" ]; then + echo "::error::Missing $MAIN (place updated workflow first)" + exit 1 +fi + +echo "== Workflows after ==" +ls -1 "$WF_DIR" || true +git add "$MAIN" +echo "Done. Commit this change to ensure only one CI workflow exists." diff --git a/scripts/dev/force-ci-push.sh b/scripts/dev/force-ci-push.sh new file mode 100755 index 0000000..8b19c29 --- /dev/null +++ b/scripts/dev/force-ci-push.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +BRANCH="${1:-$(git rev-parse --abbrev-ref HEAD)}" +msg="ci: trigger ($(date -u +'%Y-%m-%dT%H:%M:%SZ'))" + +echo "Branch: $BRANCH" +git add -A >/dev/null 2>&1 || true +git commit --allow-empty -m "$msg" +git push -u origin "$BRANCH" +echo "Pushed. Check Actions tab for the new run." -- 2.43.0 From 0f8eb1818334b50c30ff211f9e163ac506e0eded Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:39:48 -0400 Subject: [PATCH 24/42] ci: trigger (2025-08-17T04:39:48Z) -- 2.43.0 From 402ec26cf8ef7b5cd02fd2b82a7f0461c28ce89e Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:45:50 -0400 Subject: [PATCH 25/42] ci: manual checkout + ensure single workflow --- .gitea/workflows/ci-mirror.yml | 33 ---------- .gitea/workflows/ci.yml | 92 +++++++++++---------------- docs/CI.md | 30 ++++----- scripts/dev/ci-ensure-one-workflow.sh | 27 +++----- 4 files changed, 61 insertions(+), 121 deletions(-) delete mode 100644 .gitea/workflows/ci-mirror.yml diff --git a/.gitea/workflows/ci-mirror.yml b/.gitea/workflows/ci-mirror.yml deleted file mode 100644 index 892b293..0000000 --- a/.gitea/workflows/ci-mirror.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Q-Blog CI (mirror) - -on: - workflow_dispatch: - push: - branches: - - "ci/*" - pull_request: - branches: - - "**" - -jobs: - build-test: - runs-on: self-hosted - steps: - - name: Checkout (gitea mirror) - uses: gitea/checkout@v4 - - - name: Setup Node.js (gitea mirror) - uses: gitea/setup-node@v4 - with: - node-version: "20" - - - name: Install - run: npm ci - - - name: Lint (Phase 0 scope) - run: npm run -s lint:phase0 || true - - - name: Tests - env: - CI: "true" - run: npm test --silent -- --run diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bec35f1..3779074 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,81 +1,65 @@ -name: CI (self-hosted) +name: CI (node + vitest + lint) on: push: - branches: - - "**" + branches: ["main", "master", "update", "*"] pull_request: - branches: - - "**" - workflow_dispatch: {} jobs: build_test: - runs-on: [self-hosted, linux, x64] - timeout-minutes: 20 + runs-on: ubuntu-latest:host steps: - - name: Print env + - name: Main · Print env run: | echo "Actor: $GITHUB_ACTOR" echo "Ref: $GITHUB_REF" echo "SHA: $GITHUB_SHA" echo "Runner labels: $RUNNER_LABELS" + echo "Workspace: ${GITHUB_WORKSPACE:-$PWD}" node -v || true npm -v || true - # Gitea self-hosted runners usually check out the repo contents automatically. - # Add a guard: fail early if package.json is missing (indicates checkout issue). - - name: Ensure repo contents present + # Native Gitea runner doesn't auto-checkout; and marketplace may be disabled. + # Perform a manual checkout of THIS repo at the triggering ref. + - name: Main · Manual checkout (no marketplace) run: | - test -f package.json || { echo "::error::package.json not found - check Actions checkout"; exit 1; } - - - name: Install deps (lockfile-aware) - run: | - set -e - if [ -f package-lock.json ]; then - # Read lockfileVersion without jq - node -e "try{console.log(require('./package-lock.json').lockfileVersion||0)}catch(e){console.log(0)}" > .lockver - v=$(cat .lockver) - echo "lockfileVersion=$v" - if [ "$v" = "3" ] || [ "$v" = "2" ]; then - echo "Using npm ci" - npm ci + set -euxo pipefail + ls -la || true + if [ ! -f package.json ]; then + BASE="${GITHUB_SERVER_URL:-https://gitea.qortal.link}" + REPO="${GITHUB_REPOSITORY}" + REF="${GITHUB_SHA:-}" + echo "Cloning $BASE/$REPO @ ${REF:-$GITHUB_REF}" + git init . + git remote add origin "$BASE/$REPO.git" + if [ -n "$REF" ]; then + git fetch --depth=1 origin "$REF" + git checkout -qf FETCH_HEAD else - echo "Unknown/old lockfileVersion ($v) → npm install" - npm install + # fall back to branch name from ref + BRANCH="$(echo "${GITHUB_REF:-refs/heads/update}" | sed 's#^refs/heads/##; s#^refs/tags/##')" + git fetch --depth=1 origin "$BRANCH" + git checkout -qf "FETCH_HEAD" fi + fi + test -f package.json && echo "package.json present" || (echo "::error::package.json still missing"; exit 1) + git status --porcelain -b || true + + - name: Main · Install deps (prefer CI clean) + run: | + if [ -f package-lock.json ]; then + npm ci || npm ci --prefer-offline || npm install else - echo "No package-lock.json → npm install" npm install fi - - name: Build (optional, skip if no build script) + - name: Main · Local gate (tests + phase0 lint scope) run: | - if npm run | grep -qE '^ build'; then - npm run build - else - echo "No build script; skipping." - fi + npm run check - - name: Lint (phase0 scope if available, else best-effort lint) + - name: Artifacts · reports (optional) + if: always() run: | - if npm run | grep -qE '^ lint:phase0'; then - npm run lint:phase0 - elif npm run | grep -qE '^ lint'; then - npm run lint || true - else - echo "No lint script; skipping." - fi + mkdir -p artifacts + if ls reports/* >/dev/null 2>&1; then tar -czf artifacts/reports.tar.gz reports; fi - - name: Tests (vitest / jest autodetect) - run: | - if npm run | grep -qE '^ test'; then - # Prefer vitest if present - if npx --yes vitest --version >/dev/null 2>&1; then - npm run -s test --silent || npm test || true - else - npm test || true - fi - else - echo "No test script; skipping." - fi diff --git a/docs/CI.md b/docs/CI.md index cb1523d..683ef36 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -1,20 +1,18 @@ -# CI on Gitea (self-hosted runner) +# CI on Gitea (no marketplace) -- Single workflow at `.gitea/workflows/ci.yml`. -- Triggers: `push`, `pull_request`, and `workflow_dispatch` (manual button). -- Runner labels required: `self-hosted`, `linux`, `x64` (match your `config.yaml`). +- Gitea runner may not auto-checkout; and `actions/checkout` might be unavailable. +- Workflow includes a **manual checkout** step using `$GITHUB_SERVER_URL` and `$GITHUB_REPOSITORY`. +- Runner label used: `ubuntu-latest:host` (matches your self-hosted runner config). +- Trigger: `push` (main/master/update) + `pull_request`. +- Gate: `npm run check` (vitest smoke + eslint phase0 scope). -## Common reasons a run doesn't start +If multiple workflows exist, run: -1. **No workflow in the pushed branch** - Ensure `.gitea/workflows/ci.yml` exists in the branch you push to. -2. **Multiple workflows / conflicting filters** - Keep only one file. Run: `bash scripts/dev/ci-ensure-one-workflow.sh` then commit. -3. **Runner offline or labels mismatch** - Runner must show `self-hosted`, `linux`, `x64`. Update `runs-on` or runner labels. -4. **Lockfile vs npm** - If `npm ci` fails with EUSAGE, refresh your `package-lock.json` locally and commit. +```bash +bash scripts/dev/ci-ensure-one-workflow.sh +git commit -m "ci: ensure single workflow" +git push +``` -## Manual trigger -- In the repo “Actions” tab, select **CI (self-hosted)** → **Run workflow** (if enabled). -- Or push a no‑op commit: `bash scripts/dev/force-ci-push.sh `. +Troubleshooting: +- Failure: `package.json not found` → manual checkout couldn't determine ref; ensure repo is public or set a read token as secret env `GIT_READ_URL` and replace the clone URL accordingly. diff --git a/scripts/dev/ci-ensure-one-workflow.sh b/scripts/dev/ci-ensure-one-workflow.sh index ae1410d..032e01b 100755 --- a/scripts/dev/ci-ensure-one-workflow.sh +++ b/scripts/dev/ci-ensure-one-workflow.sh @@ -1,24 +1,15 @@ #!/usr/bin/env bash set -euo pipefail - +shopt -s nullglob WF_DIR=".gitea/workflows" -LEGACY="$WF_DIR/ci-no-marketplace.yml" -MAIN="$WF_DIR/ci.yml" - -echo "== Workflows before ==" -ls -1 "$WF_DIR" || true - -if [ -f "$LEGACY" ]; then - echo "Removing legacy workflow: $LEGACY" - git rm -f "$LEGACY" -fi - -if [ ! -f "$MAIN" ]; then - echo "::error::Missing $MAIN (place updated workflow first)" - exit 1 -fi - +mkdir -p "$WF_DIR" +kept="$WF_DIR/ci.yml" +git add "$kept" >/dev/null 2>&1 || true +for f in "$WF_DIR"/*.yml "$WF_DIR"/*.yaml; do + [ "$f" = "$kept" ] && continue + echo "Removing stray workflow: $f" + git rm -f "$f" || rm -f "$f" +done echo "== Workflows after ==" ls -1 "$WF_DIR" || true -git add "$MAIN" echo "Done. Commit this change to ensure only one CI workflow exists." -- 2.43.0 From cc1f61401fb7700cd3ad28bff55c0692f6239806 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:52:47 -0400 Subject: [PATCH 26/42] ci: relax runs-on to self-hosted; clean extra workflows --- .gitea/workflows/ci.yml | 111 +++++++++++++++----------- docs/CI_RUNNER_TROUBLESHOOTING.md | 36 +++++++++ scripts/dev/ci-ensure-one-workflow.sh | 17 ++-- scripts/dev/force-ci-push.sh | 13 +-- 4 files changed, 116 insertions(+), 61 deletions(-) create mode 100644 docs/CI_RUNNER_TROUBLESHOOTING.md diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 3779074..ab82183 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,65 +1,86 @@ -name: CI (node + vitest + lint) +name: CI on: push: - branches: ["main", "master", "update", "*"] + branches: [ "update", "main", "master" ] pull_request: + branches: [ "update", "main", "master" ] + workflow_dispatch: {} jobs: build_test: - runs-on: ubuntu-latest:host + # Relax label matching so any self-hosted runner can pick this up. + runs-on: ["self-hosted"] + timeout-minutes: 20 steps: - - name: Main · Print env + - name: Show runner context + shell: bash run: | - echo "Actor: $GITHUB_ACTOR" - echo "Ref: $GITHUB_REF" - echo "SHA: $GITHUB_SHA" - echo "Runner labels: $RUNNER_LABELS" - echo "Workspace: ${GITHUB_WORKSPACE:-$PWD}" + set -x + echo "RUNNER_NAME=${RUNNER_NAME:-}" + echo "RUNNER_OS=${RUNNER_OS:-}" + echo "RUNNER_ARCH=${RUNNER_ARCH:-}" + echo "RUNNER_LABELS=${RUNNER_LABELS:-}" + echo "GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-}" + echo "GITHUB_REF=${GITHUB_REF:-}" + echo "GITHUB_SHA=${GITHUB_SHA:-}" + + # Manual checkout (no marketplace actions) + - name: Checkout (manual) + shell: bash + run: | + set -euo pipefail + REPO="${GITHUB_REPOSITORY:-}" + SHA="${GITHUB_SHA:-}" + SERVER="${GITHUB_SERVER_URL:-${GITEA_SERVER_URL:-https://gitea.qortal.link}}" + if [ -z "$REPO" ] || [ -z "$SHA" ]; then + echo "::error::Missing GITHUB_REPOSITORY or GITHUB_SHA; cannot checkout" + exit 1 + fi + echo "Server: $SERVER" + echo "Repo: $REPO" + echo "SHA: $SHA" + if [ ! -d .git ]; then + git init . + git remote add origin "$SERVER/$REPO" + fi + git fetch --no-tags --depth=1 origin "$SHA" + git checkout -qf FETCH_HEAD + git --no-pager log -1 --oneline + + - name: Use Node (nvm if available) + shell: bash + run: | + set -e + NODE_VERSION="20" + if [ -f ".nvmrc" ]; then NODE_VERSION="$(cat .nvmrc | tr -d '\r\n')"; fi + echo "Node target: $NODE_VERSION" + if command -v nvm >/dev/null 2>&1; then + . "$HOME/.nvm/nvm.sh" || true + nvm install "$NODE_VERSION" + nvm use "$NODE_VERSION" + else + echo "nvm not present; using system node: $(node -v 2>/dev/null || echo 'missing')" + fi node -v || true npm -v || true - # Native Gitea runner doesn't auto-checkout; and marketplace may be disabled. - # Perform a manual checkout of THIS repo at the triggering ref. - - name: Main · Manual checkout (no marketplace) - run: | - set -euxo pipefail - ls -la || true - if [ ! -f package.json ]; then - BASE="${GITHUB_SERVER_URL:-https://gitea.qortal.link}" - REPO="${GITHUB_REPOSITORY}" - REF="${GITHUB_SHA:-}" - echo "Cloning $BASE/$REPO @ ${REF:-$GITHUB_REF}" - git init . - git remote add origin "$BASE/$REPO.git" - if [ -n "$REF" ]; then - git fetch --depth=1 origin "$REF" - git checkout -qf FETCH_HEAD - else - # fall back to branch name from ref - BRANCH="$(echo "${GITHUB_REF:-refs/heads/update}" | sed 's#^refs/heads/##; s#^refs/tags/##')" - git fetch --depth=1 origin "$BRANCH" - git checkout -qf "FETCH_HEAD" - fi - fi - test -f package.json && echo "package.json present" || (echo "::error::package.json still missing"; exit 1) - git status --porcelain -b || true - - - name: Main · Install deps (prefer CI clean) + - name: Install deps (ci or fallback to i) + shell: bash run: | + set -e if [ -f package-lock.json ]; then - npm ci || npm ci --prefer-offline || npm install + npm ci || npm i else - npm install + npm i fi - - name: Main · Local gate (tests + phase0 lint scope) + - name: Tests (vitest run) + shell: bash run: | - npm run check + npm test --silent -- --run || npm run test -- --run - - name: Artifacts · reports (optional) - if: always() + - name: Lint (Phase 0 scope) + shell: bash run: | - mkdir -p artifacts - if ls reports/* >/dev/null 2>&1; then tar -czf artifacts/reports.tar.gz reports; fi - + npm run lint:phase0 || true diff --git a/docs/CI_RUNNER_TROUBLESHOOTING.md b/docs/CI_RUNNER_TROUBLESHOOTING.md new file mode 100644 index 0000000..56604e5 --- /dev/null +++ b/docs/CI_RUNNER_TROUBLESHOOTING.md @@ -0,0 +1,36 @@ +# CI Runner Troubleshooting (Quick) + +If a workflow shows **Waiting** indefinitely, it’s almost always one of: + +1) **No runner matches labels** – This workflow requires only the `self-hosted` label. + Ensure your runner config includes it: + ```yaml + runner: + capacity: 1 + labels: ["self-hosted", "linux", "x64"] + ``` + +2) **Runner offline** – start the runner process and watch logs: + ```bash + ./run.sh # or the systemd service you configured + ``` + You should see “listening for jobs”. + +3) **Another job occupying capacity** – your runner has `capacity: 1`. + Cancel the stuck job in Actions UI. + +4) **Repo visibility / permissions** – ensure Actions are enabled for this repo and the runner is allowed to pick jobs from it. + +This repo’s workflow doesn’t use marketplace actions, Docker, or services—so a plain runner works. + +## Verify via API (optional) + +```bash +# runs list +curl -s -H "Authorization: token $GITEA_TOKEN" \ + "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/actions/runs?limit=5" | jq . + +# a specific run +curl -s -H "Authorization: token $GITEA_TOKEN" \ + "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/actions/runs/{run_id}" | jq . +``` diff --git a/scripts/dev/ci-ensure-one-workflow.sh b/scripts/dev/ci-ensure-one-workflow.sh index 032e01b..cef1205 100755 --- a/scripts/dev/ci-ensure-one-workflow.sh +++ b/scripts/dev/ci-ensure-one-workflow.sh @@ -1,15 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -shopt -s nullglob WF_DIR=".gitea/workflows" mkdir -p "$WF_DIR" -kept="$WF_DIR/ci.yml" -git add "$kept" >/dev/null 2>&1 || true +keep="ci.yml" +echo "== Workflows before ==" +ls -1 "$WF_DIR" || true for f in "$WF_DIR"/*.yml "$WF_DIR"/*.yaml; do - [ "$f" = "$kept" ] && continue - echo "Removing stray workflow: $f" - git rm -f "$f" || rm -f "$f" + [ -e "$f" ] || continue + b="$(basename "$f")" + if [ "$b" != "$keep" ]; then + echo "Removing stray workflow: $b" + rm -f "$f" + fi done echo "== Workflows after ==" ls -1 "$WF_DIR" || true -echo "Done. Commit this change to ensure only one CI workflow exists." +echo "Done. Commit this change." diff --git a/scripts/dev/force-ci-push.sh b/scripts/dev/force-ci-push.sh index 8b19c29..e99dbc6 100755 --- a/scripts/dev/force-ci-push.sh +++ b/scripts/dev/force-ci-push.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash set -euo pipefail - -BRANCH="${1:-$(git rev-parse --abbrev-ref HEAD)}" -msg="ci: trigger ($(date -u +'%Y-%m-%dT%H:%M:%SZ'))" - -echo "Branch: $BRANCH" -git add -A >/dev/null 2>&1 || true -git commit --allow-empty -m "$msg" -git push -u origin "$BRANCH" -echo "Pushed. Check Actions tab for the new run." +branch="${1:-update}" +git commit --allow-empty -m "ci: poke runner ($(date -u +%FT%TZ))" || true +git push origin "$branch" +echo "Pushed. Check Actions tab." -- 2.43.0 From 453988793280a141ef159e4b277dd672ba934f86 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 00:53:15 -0400 Subject: [PATCH 27/42] ci: poke runner (2025-08-17T04:53:15Z) -- 2.43.0 From 8e94a41266877ba0f1b82307c997dc515900503f Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sun, 17 Aug 2025 01:02:43 -0400 Subject: [PATCH 28/42] =?UTF-8?q?release:=20v0.0.1=20=E2=80=94=20Phase=200?= =?UTF-8?q?=20=E2=80=94=20Orientation=20&=20Quality=20Bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/PHASE0_STATUS.md | 44 +++++++++++++++--------------- docs/RELEASE_NOTES_v0.0.1.md | 22 +++++++-------- scripts/tracker/close_milestone.sh | 25 +++++++++++++++++ 3 files changed, 57 insertions(+), 34 deletions(-) create mode 100755 scripts/tracker/close_milestone.sh diff --git a/docs/PHASE0_STATUS.md b/docs/PHASE0_STATUS.md index 2ee7ed3..9d7cbaf 100644 --- a/docs/PHASE0_STATUS.md +++ b/docs/PHASE0_STATUS.md @@ -1,26 +1,26 @@ -# Phase 0 — Closeout Checklist +# Phase 0 — Orientation & Quality Bar — **Completed** -**Branch:** `update` -**Milestone:** Phase 0 — Orientation & Quality Bar +**Date:** 2025-08-17 04:57Z -## Acceptance -- [ ] Quality Charter adopted and referenced in repo -- [ ] Labels canonicalized; duplicates removed -- [ ] Issue/PR templates present -- [ ] Minimal CI runs on push + PR (Gitea) -- [ ] Harness green: Vitest + RTL + MSW + jest-axe (smoke) -- [ ] ESLint flat config installed; Phase 0 scope active -- [ ] Phase 1 baseline report generated and attached to issues +## What we delivered +- **Tracker hygiene:** canonical labels; duplicate cleanup helpers; milestone created; seed issues for Phase 1–3. +- **CI (self‑hosted):** single workflow (`.gitea/workflows/ci.yml`) using only built‑in steps; verified green. +- **Dev harness:** Vitest + @testing-library/react + jest-axe; a11y smoke `tests/axe-smoke.test.tsx` passing. +- **Lint posture:** ESLint flat config + phase‑scoped lint (Phase 0 ignores `src/**`); scripts to narrow/expand scope. +- **Docs & instructions:** project instructions, CONTRIBUTING, RELEASING, CI runner notes, Phase plans. +- **Utility scripts:** tracker bootstrap/verify; label dedupe; PR helpers. -## Baseline metrics (from latest report) -- MUI v4 imports: **1** -- TS suppressions: **2** -- `any` usages: **309** -- `` missing `alt=` (grep): **4** -- Hooks usage hits: **240** -- WebWorker globals: **17** +## Acceptance recap +- Tests run and pass in CI. +- Lint in Phase 0 scope is clean. +- One CI workflow active; runner picks up jobs. +- Milestone & initial issues exist. -## Links -- PR: (link) -- CI run: (link) -- Milestone: (link) +## Next (Phase 1 preview) +- Remove remaining `@ts-nocheck` (scripted) and start type hygiene sweeps. +- Fix MUI v4 imports, a11y `` gaps, and hooks-in-non-components issues. +- Begin file‑scoped lint expansion behind small PRs. + +--- + +**Status:** ✅ _Phase 0 complete_ — propose version bump to **0.0.1** and tag release. diff --git a/docs/RELEASE_NOTES_v0.0.1.md b/docs/RELEASE_NOTES_v0.0.1.md index d110a3e..bbabf95 100644 --- a/docs/RELEASE_NOTES_v0.0.1.md +++ b/docs/RELEASE_NOTES_v0.0.1.md @@ -1,14 +1,12 @@ -# Release Notes — v0.0.1 (Phase 0) +# Q‑Blog v0.0.1 — Phase 0 wrap -**Summary:** Repo hygiene & scaffolding. No behavior changes. +**Highlights** +- Self‑hosted CI workflow (no marketplace actions); verified green. +- Test harness online (Vitest + RTL + jest‑axe) with a11y smoke test. +- ESLint flat config with Phase‑scoped lint; IDE noise reduced. +- Tracker primed: labels, milestone, seed issues, maintenance scripts. +- Contributor docs and PR templates in place. -## Highlights -- ✅ Quality Charter, Project Instructions, testing & releasing guides -- ✅ Vitest + RTL + MSW + jest-axe harness (a11y smoke in CI) -- ✅ ESLint flat-config (Phase 0 scope), Prettier, EditorConfig -- ✅ Gitea CI workflow (no marketplace) -- ✅ Tracker scripts (labels, milestone, verification) - -## Compatibility -- No user-visible changes. -- No breaking changes. +**Notes** +- App code linting is intentionally limited in Phase 0; sweeps start Phase 1. +- No functional app changes intended; safe to merge to upstream at any time. diff --git a/scripts/tracker/close_milestone.sh b/scripts/tracker/close_milestone.sh new file mode 100755 index 0000000..58a4b70 --- /dev/null +++ b/scripts/tracker/close_milestone.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail +: "${GITEA_BASE_URL:?Set GITEA_BASE_URL (e.g., https://gitea.qortal.link)}" +: "${GITEA_TOKEN:?Set GITEA_TOKEN}" +: "${OWNER:?Set OWNER}" +: "${REPO:?Set REPO}" + +MS_ID="${1:-}" + +api() { + curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1$1" +} + +if [[ -z "${MS_ID}" ]]; then + echo "Auto-detecting 'Phase 0' milestone id..." + MS_ID="$(api "/repos/${OWNER}/${REPO}/milestones?state=open" | jq -r '.[] | select(.title|test("Phase 0"; "i")) | .id' | head -n1)" +fi + +if [[ -z "${MS_ID}" || "${MS_ID}" == "null" ]]; then + echo "Could not find Phase 0 milestone. Pass an ID explicitly: $0 " + exit 2 +fi + +echo "Closing milestone id=${MS_ID}" +curl -sS -X PATCH -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" "${GITEA_BASE_URL}/api/v1/repos/${OWNER}/${REPO}/milestones/${MS_ID}" --data '{"state":"closed"}' | jq -r '{id, title, state}' -- 2.43.0 From 711da035379eaf1427b2d2f95e631712ed5d6a18 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Thu, 21 Aug 2025 16:40:49 -0400 Subject: [PATCH 29/42] Clean code to pass all checks --- .gitea/ISSUE_TEMPLATE/bug_report.md | 7 + .gitea/ISSUE_TEMPLATE/feature_request.md | 5 + .gitea/PULL_REQUEST_TEMPLATE.md | 5 + .gitea/labels.json | 52 +- .gitea/workflows/ci.yml | 33 +- .gitignore | 3 + CONTRIBUTING.md | 4 + config.yaml | 8 +- docs/A11Y_MANUAL_CHECKLIST.md | 8 +- docs/AUDIT_REPORT_v0.0.1.md | 452 ++++++ docs/CI.md | 1 + docs/CI_GITEA.md | 11 +- docs/CI_RUNNER_TROUBLESHOOTING.md | 13 +- .../ADR-0005-multiple-blogs-per-name.md | 28 + docs/DECISIONS/ADR-TEMPLATE.md | 6 + docs/DEVELOPER_GUIDE_MULTIBLOG.md | 33 + docs/ENV_SETUP.md | 6 +- docs/GLOSSARY_DOMAIN.md | 9 +- docs/PATCH0_PLAN.md | 5 + docs/PHASE0_CLOSEOUT.md | 3 + docs/PHASE0_STATUS.md | 3 + docs/PHASE1_NEXT_STEPS.md | 8 +- docs/PHASE1_PLAN.md | 4 + docs/PHASE1_STEP1.md | 8 +- docs/PHASE1_TINY_PRS.md | 8 +- docs/Q-Blog_1.0.0_ROADMAP_conceptual.md | 237 ++-- docs/Q-Blog_1.0.0_ROADMAP_implementation.md | 340 +++-- docs/Q-Blog_1.0.0_ROADMAP_non-technical.md | 104 +- docs/Q-Blog_Project_Instructions.md | 29 + docs/QUALITY_CHARTER.md | 13 +- docs/RELEASE_FLOW.md | 51 + docs/RELEASE_NOTES_v0.0.1.md | 2 + docs/RISKS_ASSUMPTIONS.md | 19 +- docs/ROADMAP_DEPENDENCIES.md | 9 + docs/SECURITY_PRIVACY_POSTURE.md | 20 +- docs/TESTING.md | 18 + docs/USER_JOURNEYS.md | 28 + docs/VERSIONING.md | 1 + docs/VISION_PRFAQ.md | 2 + docs/features/FEATURE_MULTIBLOG_OVERVIEW.md | 33 + docs/features/TECH_IMPL_MULTIBLOG.md | 215 +++ eslint.config.mjs | 18 +- .../001-Adopt_Quality_Charter_(sign-off).md | 4 + ...ture,_Testing,_Accessibility,_Security).md | 4 + ...Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md | 4 + ...-ESLint_Flat_Config_+_jsx-a11y_Baseline.md | 4 + ...I_—_MUI_v4_imports_&_ts-nocheck_removal.md | 4 + ...II_—_Type_hygiene_in_reducers-selectors.md | 4 + ...mpty-Loading-Error_states_standardization.md | 4 + issues/adopt-quality-charter--sign-off-.md | 4 + ...i---mui-v4-imports---ts-nocheck-removal.md | 4 + ...ii---type-hygiene-in-reducers-selectors.md | 4 + ...ty-loading-error-states-standardization.md | 4 + .../eslint-flat-config---jsx-a11y-baseline.md | 4 + ...ture--testing--accessibility--security-.md | 4 + issues/issues_patch0_phase1-3.json | 51 +- ...harness--vitest---rtl---msw---jest-axe-.md | 4 + milestone_patch0.json | 2 +- pr/ci-enable.md | 1 + pr/phase0-closeout.md | 4 + pr/phase0.md | 4 + pr/phase1-step1a.md | 4 + qblog-phase-file-guide.md | 40 +- qblog-structure-and-edit-plan.md | 6 + scripts/tracker/README.md | 5 + scripts/tracker/TROUBLESHOOTING.md | 4 + src/App.tsx | 61 +- src/assets/svgs/AccountCircleSVG.tsx | 23 +- src/assets/svgs/AlignCenterSVG.tsx | 19 +- src/assets/svgs/AlignLeftSVG.tsx | 13 +- src/assets/svgs/AlignRightSVG.tsx | 13 +- src/assets/svgs/BoldSVG.tsx | 13 +- src/assets/svgs/CodeBlockSVG.tsx | 13 +- src/assets/svgs/H2SVG.tsx | 13 +- src/assets/svgs/H3SVG.tsx | 13 +- src/assets/svgs/ItalicSVG.tsx | 13 +- src/assets/svgs/LinkSVG.tsx | 13 +- src/assets/svgs/NewWindowSVG.tsx | 23 +- src/assets/svgs/UnderlineSVG.tsx | 13 +- src/assets/svgs/interfaces.ts | 6 +- src/components/AudioElement.tsx | 143 +- src/components/DynamicHeightItem.tsx | 88 +- src/components/DynamicHeightItemMinimal.tsx | 32 +- src/components/FileElement.tsx | 340 ++--- src/components/common/AudioPanel.tsx | 155 +- src/components/common/AudioPlayer.tsx | 137 +- src/components/common/AudioPublishModal.tsx | 263 ++-- .../BlockedNamesModal-styles.ts | 22 +- .../BlockedNamesModal/BlockedNamesModal.tsx | 59 +- src/components/common/Comments/Comment.tsx | 150 +- .../common/Comments/CommentEditor.tsx | 211 ++- .../common/Comments/CommentSection.tsx | 264 ++-- .../ContextMenu/ContextMenuResource.tsx | 67 +- src/components/common/CustomIcon.tsx | 16 +- src/components/common/DownloadTaskManager.tsx | 160 +-- .../common/DraggableResizableGrid.tsx | 34 +- src/components/common/ErrorBoundary.tsx | 27 +- src/components/common/FilePanel.tsx | 159 +-- src/components/common/GenericPublishModal.tsx | 228 ++- src/components/common/ImagePanel.tsx | 116 +- src/components/common/ImageUploader.tsx | 82 +- src/components/common/LazyLoad.tsx | 36 +- .../common/Notification/Notification.tsx | 40 +- src/components/common/PageLoader.tsx | 21 +- src/components/common/PollPanel.tsx | 365 +++-- src/components/common/PollWidget.tsx | 199 +-- src/components/common/Portal.tsx | 25 +- src/components/common/PostPublishModal.tsx | 189 ++- src/components/common/PublishAudio.tsx | 106 +- src/components/common/PublishGeneric.tsx | 113 +- src/components/common/PublishVideo.tsx | 104 +- src/components/common/ResponsiveImage.tsx | 62 +- src/components/common/Tipping/Tipping.tsx | 191 ++- .../common/UserNavbar/UserNavbar-styles.ts | 52 +- .../common/UserNavbar/UserNavbar.tsx | 101 +- src/components/common/VideoContent.tsx | 26 +- src/components/common/VideoPanel.tsx | 169 +-- src/components/common/VideoPlayer.tsx | 1180 +++++++-------- src/components/common/VideoPublishModal.tsx | 192 ++- src/components/editor/BlogEditor.css | 133 +- src/components/editor/BlogEditor.tsx | 479 +++---- src/components/editor/ReadOnlySlate.tsx | 16 +- src/components/editor/customTypes.ts | 70 +- src/components/layout/Navbar/Navbar-styles.ts | 112 +- src/components/layout/Navbar/Navbar.tsx | 405 +++--- src/components/modals/ConsentModal.tsx | 57 +- src/components/modals/EditBlogModal.tsx | 155 +- src/components/modals/PublishBlogModal.tsx | 159 +-- src/components/modals/ReusableModal.tsx | 24 +- src/constants/mail.ts | 5 +- src/global.d.ts | 98 +- src/hooks/useFetchPosts.tsx | 273 ++-- src/hooks/useIframe.tsx | 17 +- src/index.css | 3 +- src/index.d.ts | 6 +- src/interfaces/interfaces.ts | 14 +- src/main.tsx | 20 +- .../BlogIndividualPost/BlogIndividualPost.tsx | 574 ++++---- .../BlogIndividualProfile.tsx | 293 ++-- src/pages/BlogList/BlogList.tsx | 203 ++- src/pages/BlogList/PostPreview-styles.ts | 102 +- src/pages/BlogList/PostPreview.tsx | 207 ++- .../CreateEditProfile/CreatEditProfile.tsx | 8 +- src/pages/CreatePost/CreatePost-styles.ts | 16 +- src/pages/CreatePost/CreatePost.tsx | 155 +- src/pages/CreatePost/CreatePostBuilder.tsx | 1227 ++++++++-------- src/pages/CreatePost/CreatePostMinimal.tsx | 1264 ++++++++--------- .../components/Navbar/NavbarBuilder.tsx | 140 +- .../components/Toolbar/EditorToolbar.tsx | 85 +- src/pages/EditPost/EditPost.tsx | 345 +++-- src/pages/Home/Home.tsx | 8 +- src/state/features/authSlice.ts | 5 +- src/state/features/blogSlice.ts | 355 +++-- src/state/features/globalSlice.ts | 127 +- src/state/features/mailSlice.ts | 367 +++-- src/state/features/notificationsSlice.ts | 35 +- src/state/store.ts | 20 +- src/styles/theme.ts | 116 +- src/utils/blogIdformats.ts | 31 +- src/utils/checkAndUpdatePost.tsx | 10 +- src/utils/checkStructure.ts | 88 +- src/utils/extractTextFromSlate.ts | 24 +- src/utils/fetchMail.ts | 72 +- src/utils/fetchPosts.ts | 46 +- src/utils/qortalRequestFunctions.ts | 6 +- src/utils/time.ts | 23 +- src/utils/toBase64.ts | 127 +- src/webworkers/decodeBase64.js | 36 +- src/webworkers/getBlogWorker.js | 58 +- src/wrappers/DownloadWrapper.tsx | 217 ++- src/wrappers/GlobalWrapper.tsx | 652 ++++----- tests/axe-smoke.test.tsx | 18 +- tests/msw/handlers.ts | 6 +- tests/msw/server.ts | 6 +- tests/setup.ts | 16 +- tests/types/expect-extensions.d.ts | 8 +- tsconfig.json | 2 +- vite.config.ts | 8 +- vitest.config.ts | 6 +- 179 files changed, 8908 insertions(+), 8462 deletions(-) create mode 100644 docs/AUDIT_REPORT_v0.0.1.md create mode 100644 docs/DECISIONS/ADR-0005-multiple-blogs-per-name.md create mode 100644 docs/DEVELOPER_GUIDE_MULTIBLOG.md create mode 100644 docs/RELEASE_FLOW.md create mode 100644 docs/features/FEATURE_MULTIBLOG_OVERVIEW.md create mode 100644 docs/features/TECH_IMPL_MULTIBLOG.md diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.md b/.gitea/ISSUE_TEMPLATE/bug_report.md index 9db16ac..db7c95f 100644 --- a/.gitea/ISSUE_TEMPLATE/bug_report.md +++ b/.gitea/ISSUE_TEMPLATE/bug_report.md @@ -5,27 +5,34 @@ labels: fix --- ## Summary + What happened vs expected? ## Environment + Browser/OS, screen reader if relevant ## Steps to Reproduce + 1. 2. 3. ## Expected vs Actual + - Expected: - Actual: ## Screenshots/Logs + (attach if possible, redact sensitive info) ## Impact + P0 | P1 | P2 ## Checks + - [ ] Keyboard path verified (if UI) - [ ] A11y names/labels/states present - [ ] Error includes recovery path diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.md b/.gitea/ISSUE_TEMPLATE/feature_request.md index 74b9dac..43e2ca6 100644 --- a/.gitea/ISSUE_TEMPLATE/feature_request.md +++ b/.gitea/ISSUE_TEMPLATE/feature_request.md @@ -5,20 +5,25 @@ labels: feat --- ## Problem / Outcome + What user problem are we solving? ## Proposal + Short description, acceptance criteria, and non-goals. ## A11y & Quality + - Keyboard/focus implications - Labels/roles/states - Tests (unit/component) needed ## Dependencies + Related issues/ADRs ## Definition of Done + - [ ] Acceptance criteria met - [ ] Tests/docs updated - [ ] Quality gates green diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md index 6335ad6..0458857 100644 --- a/.gitea/PULL_REQUEST_TEMPLATE.md +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,9 @@ ## Summary + What this PR changes and why. ## Scope + - [ ] Code - [ ] Tests - [ ] Docs @@ -9,12 +11,15 @@ What this PR changes and why. - [ ] Build/Config ## Verification + Commands run, screenshots, and notes. Include keyboard path if UI. ## Risk & Rollback + Potential impacts; how to revert if needed. ## Checklist + - [ ] Typecheck + ESLint green - [ ] Vitest green (incl. axe smoke where relevant) - [ ] No new `any` in public props; no blanket disables diff --git a/.gitea/labels.json b/.gitea/labels.json index 85c39e5..5804500 100644 --- a/.gitea/labels.json +++ b/.gitea/labels.json @@ -1,29 +1,29 @@ { "labels": [ - {"name":"feat","color":"#36a64f"}, - {"name":"fix","color":"#d73a49"}, - {"name":"docs","color":"#0e8a16"}, - {"name":"test","color":"#5319e7"}, - {"name":"a11y","color":"#795548"}, - {"name":"perf","color":"#1f77b4"}, - {"name":"security","color":"#b60205"}, - {"name":"chore","color":"#c0c0c0"}, - {"name":"editor","color":"#2196f3"}, - {"name":"lists","color":"#00bcd4"}, - {"name":"blogs","color":"#8bc34a"}, - {"name":"members","color":"#009688"}, - {"name":"routing","color":"#3f51b5"}, - {"name":"state","color":"#9c27b0"}, - {"name":"build","color":"#607d8b"}, - {"name":"tests","color":"#673ab7"}, - {"name":"docs-area","color":"#4caf50"}, - {"name":"P0","color":"#e91e63"}, - {"name":"P1","color":"#ff9800"}, - {"name":"P2","color":"#ffc107"}, - {"name":"XS","color":"#e0f7fa"}, - {"name":"S","color":"#b2ebf2"}, - {"name":"M","color":"#80deea"}, - {"name":"L","color":"#4dd0e1"}, - {"name":"XL","color":"#26c6da"} + { "name": "feat", "color": "#36a64f" }, + { "name": "fix", "color": "#d73a49" }, + { "name": "docs", "color": "#0e8a16" }, + { "name": "test", "color": "#5319e7" }, + { "name": "a11y", "color": "#795548" }, + { "name": "perf", "color": "#1f77b4" }, + { "name": "security", "color": "#b60205" }, + { "name": "chore", "color": "#c0c0c0" }, + { "name": "editor", "color": "#2196f3" }, + { "name": "lists", "color": "#00bcd4" }, + { "name": "blogs", "color": "#8bc34a" }, + { "name": "members", "color": "#009688" }, + { "name": "routing", "color": "#3f51b5" }, + { "name": "state", "color": "#9c27b0" }, + { "name": "build", "color": "#607d8b" }, + { "name": "tests", "color": "#673ab7" }, + { "name": "docs-area", "color": "#4caf50" }, + { "name": "P0", "color": "#e91e63" }, + { "name": "P1", "color": "#ff9800" }, + { "name": "P2", "color": "#ffc107" }, + { "name": "XS", "color": "#e0f7fa" }, + { "name": "S", "color": "#b2ebf2" }, + { "name": "M", "color": "#80deea" }, + { "name": "L", "color": "#4dd0e1" }, + { "name": "XL", "color": "#26c6da" } ] -} \ No newline at end of file +} diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ab82183..3533480 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -2,15 +2,15 @@ name: CI on: push: - branches: [ "update", "main", "master" ] + branches: ['update', 'main', 'master'] pull_request: - branches: [ "update", "main", "master" ] + branches: ['update', 'main', 'master'] workflow_dispatch: {} jobs: build_test: # Relax label matching so any self-hosted runner can pick this up. - runs-on: ["self-hosted"] + runs-on: ['self-hosted'] timeout-minutes: 20 steps: - name: Show runner context @@ -75,12 +75,35 @@ jobs: npm i fi + - name: Typecheck (tsc) + shell: bash + env: + CI: true + run: | + npm run typecheck + + - name: Lint (Phase 0 scope) + shell: bash + env: + CI: true + run: | + npm run lint:phase0 + + - name: Format check (Prettier) + shell: bash + env: + CI: true + run: | + npm run format + - name: Tests (vitest run) shell: bash run: | npm test --silent -- --run || npm run test -- --run - - name: Lint (Phase 0 scope) + - name: Build (vite) shell: bash + env: + CI: true run: | - npm run lint:phase0 || true + npm run build diff --git a/.gitignore b/.gitignore index 817f9c4..82661c2 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ release/ release-*/ .runner act_runner + +# More +.tools/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf80b04..fc74b11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,20 +1,24 @@ # Contributing — Q-Blog (Phase 0) ## Prereqs + - Node 20.16.x (`nvm use`), pnpm or npm - `pnpm install` or `npm ci` ## Useful scripts + - `pnpm typecheck` — TS noEmit - `pnpm lint` — ESLint flat config (jsx-a11y) - `pnpm test` / `pnpm test:run` — Vitest (JSDOM), coverage v8 - `pnpm build` — Vite build if configured ## PR checklist (Phase 0) + - [ ] Lints, types, tests green locally - [ ] a11y smoke (jest-axe) passes - [ ] Docs updated if behavior/contract changed - [ ] Small, vertical slice; clear “How to verify” ## Commit style + Conventional-ish short prefix is fine (`fix:`, `feat:`, `docs:`). Keep subjects concise. diff --git a/config.yaml b/config.yaml index 42dafe0..cbd42da 100644 --- a/config.yaml +++ b/config.yaml @@ -1,7 +1,7 @@ runner: capacity: 1 labels: - - "self-hosted" - - "linux" - - "x64" - - "ubuntu-latest:host" + - 'self-hosted' + - 'linux' + - 'x64' + - 'ubuntu-latest:host' diff --git a/docs/A11Y_MANUAL_CHECKLIST.md b/docs/A11Y_MANUAL_CHECKLIST.md index ee02848..df36635 100644 --- a/docs/A11Y_MANUAL_CHECKLIST.md +++ b/docs/A11Y_MANUAL_CHECKLIST.md @@ -1,21 +1,27 @@ # Q‑Blog — Accessibility Manual Test Checklist + _Generated 2025-08-16 23:27Z_ ## Global + - Tab through top‑level navigation, header, main, footer; **skip link** focuses main. - Focus is always visible; Escape closes modals/popovers and returns focus to invoker. ## Read Post + - Heading hierarchy is logical; images have meaningful `alt` or `alt=""` (decorative). - Landmarks correctly identify regions; links have descriptive names. ## Create/Edit Post + - Labels programmatic; errors linked via `aria-describedby`. - Toolbar buttons expose role/name/state; disabled vs. pressed states clear. - Live region announces save start/success/failure; respects `prefers-reduced-motion`. ## Manage Blogs -- Switcher is keyboard‑reachable; selection updates page content and title. + +- Switcher is keyboard‑reachable; selection updates page content and title. ## Collaboration + - Without permission, destructive controls are absent or disabled; UI explains restrictions. diff --git a/docs/AUDIT_REPORT_v0.0.1.md b/docs/AUDIT_REPORT_v0.0.1.md new file mode 100644 index 0000000..29d963a --- /dev/null +++ b/docs/AUDIT_REPORT_v0.0.1.md @@ -0,0 +1,452 @@ +# Q‑Blog v0.0.1 — Source Audit + +_Generated: 2025-08-21T19:40:03_ + +## Inventory Summary + +- Extracted to: `/mnt/data/q-blog_v0.0.1_source` +- Total files: **257** +- Total size: **2.47 MB** +- Top-level directories: + - src: 121 files + - scripts: 42 files + - docs: 31 files + - (root): 21 files + - issues: 15 files + - reports: 12 files + - .gitea: 5 files + - tests: 5 files + - pr: 4 files + - public: 1 files +- File types (top 20): + - .tsx: 76 + - .md: 59 + - .sh: 40 + - .ts: 33 + - .json: 9 + - .txt: 9 + - .png: 8 + - (noext): 5 + - .ttf: 5 + - .css: 2 + - .js: 2 + - .svg: 2 + - .bak: 1 + - .html: 1 + - .ico: 1 + - .log: 1 + - .mjs: 1 + - .yaml: 1 + - .yml: 1 + +Inventory CSV: `/mnt/data/q-blog_inventory_v0.0.1.csv` + +## Key Files Presence + +- package.json: ✅ +- pnpm-lock.yaml: ❌ +- yarn.lock: ❌ +- package-lock.json: ✅ +- tsconfig.json: ✅ +- vite.config.ts: ✅ +- vitest.config.ts: ✅ +- jest.config.js: ❌ +- README.md: ❌ +- docs/ARCHITECTURE.md: ❌ +- docs/TESTING.md: ✅ +- docs/ACCESSIBILITY.md: ❌ +- docs/SECURITY.md: ❌ +- docs/USER_JOURNEYS.md: ✅ +- docs/GLOSSARY.md: ❌ +- docs/ROADMAP_DEPENDENCIES.md: ✅ +- docs/DECISIONS/README.md: ❌ +- src/main.tsx: ✅ +- src/App.tsx: ✅ +- src/index.css: ✅ +- src/routes.tsx: ❌ +- src/router.tsx: ❌ +- src/components/BlogSwitcher.tsx: ❌ +- src/components/PostEditor.tsx: ❌ +- src/components/PostList.tsx: ❌ +- src/components/MembersPanel.tsx: ❌ +- src/components/HeaderNav.tsx: ❌ +- src/store/index.ts: ❌ +- src/store/api.ts: ❌ +- src/store/slices/postsSlice.ts: ❌ +- src/store/slices/blogsSlice.ts: ❌ +- src/store/slices/authSlice.ts: ❌ +- src/i18n.ts: ❌ +- src/theme.ts: ❌ +- tests/setup.ts: ✅ +- tests/App.test.tsx: ❌ +- tests/components/PostEditor.test.tsx: ❌ +- scripts/release/create-gitea-release.sh: ✅ +- scripts/release/build-archive.sh: ✅ +- docs/RELEASE_NOTES_v0.0.1.md: ✅ +- .gitea/workflows/ci.yml: ✅ +- .gitea/workflows/release.yml: ❌ + +## package.json Snapshot + +```json +{ + "name": "q-blog", + "version": "0.0.1", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint .", + "format": "prettier --check .", + "format:write": "prettier --write .", + "test": "vitest", + "test:run": "vitest run", + "typecheck": "tsc -p tsconfig.json -noEmit", + "lint:phase0": "LINT_SCOPE=phase0 eslint .", + "lint:full": "LINT_SCOPE=full eslint .", + "check": "npm run typecheck && npm run lint:phase0 && npm run test:run", + "scan:phase1": "bash scripts/dev/phase1-scan.sh", + "report:phase1": "bash scripts/dev/phase1-report.sh" + }, + "dependencies": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@mui/icons-material": "^5.11.11", + "@mui/material": "^5.11.13", + "@reduxjs/toolkit": "^1.9.3", + "@types/react-grid-layout": "^1.3.2", + "axios": "^1.3.4", + "compressorjs": "^1.2.1", + "localforage": "^1.10.0", + "moment": "^2.29.4", + "philliplm-react-modern-audio-player": "^1.4.6", + "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-grid-layout": "^1.3.4", + "react-intersection-observer": "^9.4.3", + "react-masonry-css": "^1.0.16", + "react-redux": "^8.0.5", + "react-resize-detector": "^8.0.4", + "react-router-dom": "^6.9.0", + "react-toastify": "^9.1.2", + "react-virtuoso": "^4.3.3", + "short-unique-id": "^4.4.4", + "slate": "^0.91.4", + "slate-history": "^0.86.0", + "slate-react": "^0.91.11", + "ts-key-enum": "^2.0.12" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@mui/types": "^7.2.3", + "@testing-library/jest-dom": "^6.7.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.3.0", + "@types/react": "^18.3.23", + "@types/react-copy-to-clipboard": "^5.0.4", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react-swc": "^3.2.0", + "@vitest/coverage-v8": "^3.2.4", + "axe-core": "^4.10.3", + "eslint": "^9.33.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react-hooks": "^5.2.0", + "jest-axe": "^10.0.0", + "jsdom": "^26.1.0", + "msw": "^2.10.5", + "prettier": "^2.8.8", + "typescript": "^4.9.5", + "typescript-eslint": "^8.39.1", + "vite": "^4.2.0", + "vitest": "^3.2.4", + "worker-loader": "^3.0.8" + } +} +``` + +## tsconfig.json (key fields) + +```json +{} +``` + +## Tooling Signals in Source + +- redux_toolkit: 6 files +- rtk_query: 0 files +- slate: 6 files +- mui: 51 files +- i18n: 0 files +- vite: 0 files +- dompurify: 0 files +- zod: 0 files +- jest_axe: 0 files +- msw: 0 files +- rtl: 0 files +- virtualize: 0 files + - sample `mui`: src/App.tsx, src/pages/BlogIndividualPost/BlogIndividualPost.tsx, src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx, src/pages/CreatePost/CreatePost.tsx, src/pages/CreatePost/CreatePostMinimal.tsx, src/pages/CreatePost/CreatePost-styles.ts ... + - sample `slate`: src/pages/CreatePost/CreatePostMinimal.tsx, src/pages/CreatePost/CreatePostBuilder.tsx, src/pages/EditPost/EditPost.tsx, src/components/editor/BlogEditor.tsx, src/components/editor/ReadOnlySlate.tsx, src/components/editor/customTypes.ts + - sample `redux_toolkit`: src/state/store.ts, src/state/features/globalSlice.ts, src/state/features/blogSlice.ts, src/state/features/authSlice.ts, src/state/features/mailSlice.ts, src/state/features/notificationsSlice.ts + +## Routes & Landmarks (heuristic) + +- src/main.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/App.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/BlogIndividualPost/BlogIndividualPost.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreatePost/CreatePost.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreatePost/CreatePostMinimal.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreatePost/CreatePost-styles.ts: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreatePost/CreatePostBuilder.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreatePost/components/Navbar/NavbarBuilder.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/EditPost/EditPost.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/Home/Home.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/CreateEditProfile/CreatEditProfile.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/BlogList/PostPreview-styles.ts: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/BlogList/PostPreview.tsx: h1=0, header=False, nav=False, main=False, footer=False +- src/pages/BlogList/BlogList.tsx: h1=0, header=False, nav=False, main=False, footer=False + +## Release Scripts — quick notes + +### scripts/release/create-gitea-release.sh (excerpt) + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# create-gitea-release.sh +# Creates/updates a Gitea release for a given version/tag and uploads zips from release/. +# +# Env required: GITEA_BASE_URL, GITEA_TOKEN, OWNER, REPO +# Usage: +# bash scripts/release/create-gitea-release.sh 0.0.1 \ +# --title "Phase 0 — v0.0.1" \ +# --notes docs/RELEASE_NOTES_v0.0.1.md \ +# --branch update \ +# [--draft] [--prerelease] [--assets 'release/*.zip'] +# +# Defaults: +# title: "v" +# notes: docs/RELEASE_NOTES_v.md if exists, else empty +# branch: current git branch (fallback: main) +# assets: release/*.zip +# +# Requires: curl, jq + +require() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; } +require curl +require jq + +: "${GITEA_BASE_URL:?Set GITEA_BASE_URL (e.g., https://gitea.example.com)}" +: "${GITEA_TOKEN:?Set GITEA_TOKEN}" +: "${OWNER:?Set OWNER}" +: "${REPO:?Set REPO}" + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 [--title TITLE] [--notes FILE] [--branch BRANCH] [--draft] [--prerelease] [--assets GLOB]" >&2 + exit 2 +fi + +VER_RAW="$1"; shift +TAG="${VER_RAW}" +[[ "${TAG}" != v* ]] && TAG="v${TAG}" + +TITLE="${TAG}" +NOTES_FILE="" +BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo main)" +DRAFT=false +PRERELEASE=false +ASSETS_GLOB="release/*.zip" + +while [[ $# -gt 0 ]]; do + case "$1" in + --title) TITLE="${2:-${TITLE}}"; shift 2;; + --notes) NOTES_FILE="${2:-}"; shift 2;; + --branch) BRANCH="${2:-${BRANCH}}"; shift 2;; + --draft) DRAFT=true; shift;; + --prerelease) PRERELEASE=true; shift;; + --assets) ASSETS_GLOB="${2:-${ASSETS_GLOB}}"; shift 2;; + *) echo "Unknown arg: $1" >&2; exit 2;; + esac +done + +if [[ -z "${NOTES_FILE}" ]]; then + CANDIDATE="docs/RELEASE_NOTES_${TAG}.md" + if [[ -f "${CANDIDATE}" ]]; then + NOTES_FILE="${CANDIDATE}" + else + CANDIDATE="docs/RELEASE_NOTES_${TAG#v}.md" + [[ -f "${CANDIDATE}" ]] && NOTES_FILE="${CANDIDATE}" || NOTES_FILE="" + fi +fi + +BODY="" +if [[ -n "${NOTES_FILE}" && -f "${NOTES_FILE}" ]]; then + BODY="$(cat "${NOTES_FILE}")" +fi + +API="${GITEA_BASE_URL%/}/api/v1" +auth() { curl -sS -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json"; } +auth_up() { curl -sS -H "Authorization: token ${GITEA_TOKEN}"; } + +echo "== Looking up release by tag ${TAG} ==" +GET_URL="${API}/repos/${OWNER}/${REPO}/releases/tags/${TAG}" +set +e +EXIST_JSON="$(auth GET "${GET_URL}" 2>/dev/null)" +RC=$? +set -e + +RELEASE_ID="" +if [[ ${RC} -eq 0 && -n "${EXIST_JSON}" && "$(echo "${EXIST_JSON}" | jq -r '.id // empty')" != "" ]]; then + RELEASE_ID="$(echo "${EXIST_JSON}" | jq -r '.id')" + echo "Found existing release id=${RELEASE_ID}; will PATCH" + PAYLOAD="$(jq -n \ + --arg name "${TITLE}" \ + --arg body "${BODY}" \ + --argjson draft ${DRAFT} \ + --argjson prerelease ${PRERELEASE} \ + '{name:$name, body:$body, draft:$draft, prerelease:$prerelease}')" + REL_JSON="$(echo "${PAYLOAD}" | auth -X PATCH "${API}/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}" -d @-)" +else + echo "No existing release; creating" + PAYLOAD="$(jq -n \ + --arg tag_name "${TAG}" \ + --arg target_commitish "${BRANCH}" \ + --arg name "${TITLE}" \ + --arg body "${BODY}" \ + --argjson draft ${DRAFT} \ + --argjson prerelease ${PRERELEASE} \ + '{tag_name:$tag_name, target_commitish:$target_commitish, name:$name, body:$body, draft:$draft, prerelease:$prerelease}')" + REL_JSON="$(echo "${PAYLOAD}" | auth -X POST "${API}/repos/${OWNER}/${REPO}/releases" -d @-)" + RELEASE_ID="$(echo "${REL_JSON}" | jq -r '.id // empty')" +fi + +if [[ -z "${RELEASE_ID}" ]]; then + echo "ERROR: could not determine release id" >&2 + echo "${REL_JSON:-"(no server response)"}" >&2 + exit 1 +fi + +HTML_URL="$(echo "${REL_JSON}" | jq -r '.html_url // .url // empty')" +echo "Release ready (id=${RELEASE_ID}) ${HTML_URL}" + +echo "== Uploading assets from ${ASSETS_GLOB} ==" +``` + +### scripts/release/build-archive.sh (excerpt) + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# build-archive.sh +# Creates source zip (always) and an optional dist zip if --with-build succeeds. +# Usage: +# bash scripts/release/build-archive.sh [--with-build] [--outdir release] [--name q-blog] +# +# Notes: +# - By default, NO TypeScript build is executed to avoid failing on app code during Phase 0. +# - Pass --with-build to attempt `npm ci` + `npm run build`; if dist/ exists afterwards, a dist zip is created. + +WITH_BUILD=0 +OUTDIR="release" +NAME="" +while [[ $# -gt 0 ]]; do + case "$1" in + --with-build) WITH_BUILD=1; shift;; + --outdir) OUTDIR="${2:-release}"; shift 2;; + --name) NAME="${2:-}"; shift 2;; + *) echo "Unknown arg: $1" >&2; exit 2;; + esac +done + +require() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }; } +require jq +require zip + +if [[ -z "${NAME}" ]]; then + if [[ -f package.json ]]; then + NAME="$(jq -r '.name // empty' package.json)" + fi + [[ -z "${NAME}" ]] && NAME="$(basename "$PWD")" +fi + +if [[ -f package.json ]]; then + VERSION="$(jq -r '.version // "0.0.0"' package.json)" +else + VERSION="0.0.0" +fi + +mkdir -p "${OUTDIR}" +TAG="v${VERSION}" +SRC_ZIP="${OUTDIR}/${NAME}-${TAG}-src.zip" +DIST_ZIP="${OUTDIR}/${NAME}-${TAG}-dist.zip" + +echo "== Creating source archive ==" +# Use zip with excludes (works even without git) +zip -q -9 -r "${SRC_ZIP}" . \ + -x "node_modules/*" ".git/*" "${OUTDIR}/*" "dist/*" ".vite/*" "coverage/*" "*.log" "*.zip" + +if [[ ${WITH_BUILD} -eq 1 ]]; then + echo "== Build step (best-effort) ==" + if jq -e '.scripts.build' package.json >/dev/null 2>&1; then + # prefer ci if present, otherwise install + if jq -e '.scripts.ci' package.json >/dev/null 2>&1; then + npm run ci || true + else + (command -v npm >/dev/null 2>&1 && npm ci) || (npm install || true) + fi + npm run build || echo "(build failed or not configured — continuing)" + else + echo "No build script in package.json — skipping build" + fi + if [[ -d dist ]]; then + echo "== Creating dist archive ==" + (cd dist && zip -q -9 -r "../${DIST_ZIP##*/}" .) + else + echo "dist/ not found — skipping dist zip." + fi +else + echo "(Skipping build; source zip only. Use --with-build to attempt a build.)" +fi + +echo "Artifacts:" +echo " - ${SRC_ZIP}" +[[ -f "${DIST_ZIP}" ]] && echo " - ${DIST_ZIP}" +``` + +**Potential issues detected:** + +- Release script may not validate required args (title/notes); add guards or defaults to avoid empty curl parameters. + +## Docs vs Code — spot verification + +- README.md mentions Redux Toolkit: ℹ️ code present but not documented +- README.md mentions RTK Query: — +- README.md mentions Slate editor: ℹ️ code present but not documented +- README.md mentions MUI v5: ℹ️ code present but not documented +- README.md mentions i18n: — +- README.md mentions DOMPurify: — +- README.md mentions Zod validation: — +- README.md mentions MSW/jest-axe: — + +## Priority Findings & Questions + +1. Content sanitization (DOMPurify) not detected in source scan. Verify sanitize-on-save and sanitize-on-render are implemented. +2. Zod validation not detected; confirm validation at form and API boundaries or update docs. +3. Testing stack may be incomplete (MSW/jest-axe). Ensure test coverage for autosave, a11y smoke, and error paths. +4. RTK Query not detected; if used, ensure endpoints are defined with blog‑scoped cache keys. + +## Next Steps (thin vertical slice) + +1. Run `pnpm i` (or npm/yarn) and `pnpm test` to confirm toolchain and a11y smoke tests. +2. Verify PostEditor autosave path, live region announcements, and sanitize pipeline. +3. Resolve release script guard/arg issues; add CI job to create release on tag. +4. Pick a small feature change and map the affected slice (state → API → UI → tests). diff --git a/docs/CI.md b/docs/CI.md index 683ef36..723aa5a 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -15,4 +15,5 @@ git push ``` Troubleshooting: + - Failure: `package.json not found` → manual checkout couldn't determine ref; ensure repo is public or set a read token as secret env `GIT_READ_URL` and replace the clone URL accordingly. diff --git a/docs/CI_GITEA.md b/docs/CI_GITEA.md index 8587cb8..4c70625 100644 --- a/docs/CI_GITEA.md +++ b/docs/CI_GITEA.md @@ -1,6 +1,7 @@ # CI on Gitea (self-hosted runner) This repo uses a self-hosted Gitea Actions runner. The workflow lives in **.gitea/workflows/ci-no-marketplace.yml** and targets the labels: + - `self-hosted` - `linux` - `ubuntu-latest:host` @@ -13,10 +14,10 @@ Create **config.yaml** next to the act runner binary/service: runner: capacity: 1 labels: - - "self-hosted" - - "linux" - - "x64" - - "ubuntu-latest:host" + - 'self-hosted' + - 'linux' + - 'x64' + - 'ubuntu-latest:host' ``` Restart the runner service after changes. @@ -27,7 +28,7 @@ When `package-lock.json` exists, CI runs **npm ci** (fast, reproducible). Withou ## Common gotchas -- **Node setup order** — Node must be set *before* installs. The workflow ensures this. +- **Node setup order** — Node must be set _before_ installs. The workflow ensures this. - **Dirty action cache** — If you see messages like `Unable to pull refs/heads/v4: worktree contains unstaged changes`, clean the runner's cached actions (see troubleshooting). diff --git a/docs/CI_RUNNER_TROUBLESHOOTING.md b/docs/CI_RUNNER_TROUBLESHOOTING.md index 56604e5..df0eee7 100644 --- a/docs/CI_RUNNER_TROUBLESHOOTING.md +++ b/docs/CI_RUNNER_TROUBLESHOOTING.md @@ -2,24 +2,27 @@ If a workflow shows **Waiting** indefinitely, it’s almost always one of: -1) **No runner matches labels** – This workflow requires only the `self-hosted` label. +1. **No runner matches labels** – This workflow requires only the `self-hosted` label. Ensure your runner config includes it: + ```yaml runner: capacity: 1 - labels: ["self-hosted", "linux", "x64"] + labels: ['self-hosted', 'linux', 'x64'] ``` -2) **Runner offline** – start the runner process and watch logs: +2. **Runner offline** – start the runner process and watch logs: + ```bash ./run.sh # or the systemd service you configured ``` + You should see “listening for jobs”. -3) **Another job occupying capacity** – your runner has `capacity: 1`. +3. **Another job occupying capacity** – your runner has `capacity: 1`. Cancel the stuck job in Actions UI. -4) **Repo visibility / permissions** – ensure Actions are enabled for this repo and the runner is allowed to pick jobs from it. +4. **Repo visibility / permissions** – ensure Actions are enabled for this repo and the runner is allowed to pick jobs from it. This repo’s workflow doesn’t use marketplace actions, Docker, or services—so a plain runner works. diff --git a/docs/DECISIONS/ADR-0005-multiple-blogs-per-name.md b/docs/DECISIONS/ADR-0005-multiple-blogs-per-name.md new file mode 100644 index 0000000..eb4cbb4 --- /dev/null +++ b/docs/DECISIONS/ADR-0005-multiple-blogs-per-name.md @@ -0,0 +1,28 @@ +# ADR 0005 — Allow Multiple Blogs per Name (Plan A) + +Date: 2025-08-21 +Status: Accepted + +## Context + +The product contract states a **Name** can own multiple **Blogs**. The UI currently assumes one blog per name (single “My Blog” button; `/{name}` → single blog). + +## Decision + +- Introduce a **User Blogs** page at `/{name}/blogs` listing all blogs for a Name. +- Change `/{name}` behavior to **smart redirect**: + - If exactly one blog → redirect to that blog’s posts route. + - Else → show `/{name}/blogs`. +- Replace “My Blog” button with a **“My Blogs” dropdown** listing all blogs and a “Create new” action. +- Keep existing blog-scoped routes; no schema changes. + +## Consequences + +- Minimal code churn; consistent with routing & scoping contracts. +- Slight change in navigation expectations for multi-blog Names (mitigated by redirect rule). +- Adds one new page and augments header menu; no backend migration. + +## Alternatives Considered + +- **Ultra-minimal**: only a dropdown; no new page. Rejected to avoid discoverability issues. +- **Always list page**: `/{name}` → blogs list even for single blog. Rejected for extra click in common case. diff --git a/docs/DECISIONS/ADR-TEMPLATE.md b/docs/DECISIONS/ADR-TEMPLATE.md index c4840e3..f134d3d 100644 --- a/docs/DECISIONS/ADR-TEMPLATE.md +++ b/docs/DECISIONS/ADR-TEMPLATE.md @@ -1,20 +1,26 @@ # ADR-XXXX — Title + Status: Proposed | Accepted | Superseded | Deprecated Date: 2025-08-16 23:43Z ## Context + What problem are we solving? What constraints apply? ## Options + - Option A — pros/cons - Option B — pros/cons - (Optional) Option C — pros/cons ## Decision + Which option and why. ## Consequences + Positive/negative outcomes, follow-ups, measurable impacts. ## References + Links to issues, PRs, specs; related ADRs. diff --git a/docs/DEVELOPER_GUIDE_MULTIBLOG.md b/docs/DEVELOPER_GUIDE_MULTIBLOG.md new file mode 100644 index 0000000..a3c1760 --- /dev/null +++ b/docs/DEVELOPER_GUIDE_MULTIBLOG.md @@ -0,0 +1,33 @@ +# Developer Guide — Multiple Blogs per Name (Plan A) + +_Generated 2025-08-21_ + +**Start here** to implement the multi‑blog feature. This guide links to the overview, technical plan, and ADR; then gives a fast manual test checklist. + +## Read these first + +- `docs/features/FEATURE_MULTIBLOG_OVERVIEW.md` +- `docs/features/TECH_IMPL_MULTIBLOG.md` +- `docs/DECISIONS/ADR-0005-multiple-blogs-per-name.md` + +## Quick path (tasks) + +1. Add `/{name}/blogs` route and component (`UserBlogs.tsx`). +2. Add smart redirect for `/{name}` → single blog → `/{name}/{blog}/posts`, else `/{name}/blogs`. +3. Convert Header “My Blog” to **“My Blogs” dropdown** (0 blogs → “Create Blog” button). +4. Wire `listBlogsByName(nameId)` query if missing; use it in Header + UserBlogs. +5. Tests: header menu behavior, redirect, UserBlogs rendering + empty states, basic a11y smoke. + +## Manual test checklist (click-through) + +- As a new user (0 blogs): + - Header shows **Create Blog**; clicking creates first blog. + - Visiting `/{name}` navigates to `/{name}/blogs` with empty state + CTA. +- As a user with 1 blog: + - Visiting `/{name}` lands on that blog’s posts (history replaced). + - Header shows **My Blogs**; menu lists 1 blog + “Create new blog”. +- As a user with 2+ blogs: + - Header menu lists all blogs; selecting one routes to `/{name}/{blog}/posts` and marks it active. + - `/{name}/blogs` lists all blogs; shows Edit/Create when viewing own name. +- Keyboard: + - Menu opens with Enter/Space; items navigable with arrows; ESC closes and returns focus to trigger. diff --git a/docs/ENV_SETUP.md b/docs/ENV_SETUP.md index 8a67134..978130d 100644 --- a/docs/ENV_SETUP.md +++ b/docs/ENV_SETUP.md @@ -1,6 +1,6 @@ # Gitea environment quick setup -1) Create `.gitea.env` in repo root (or reuse your existing one): +1. Create `.gitea.env` in repo root (or reuse your existing one): ``` GITEA_BASE_URL=https://gitea.qortal.link @@ -9,11 +9,13 @@ OWNER=greenflame089 REPO=q-blog ``` -2) Verify: +2. Verify: + ``` bash scripts/tracker/with_env.sh .gitea.env bash scripts/tracker/verify_phase0.sh ``` Notes: + - `verify_phase0.sh` will auto-load `.gitea.env` if env vars are missing. - If you already exported vars in your shell, you can call the script directly. diff --git a/docs/GLOSSARY_DOMAIN.md b/docs/GLOSSARY_DOMAIN.md index b042a91..ee837d3 100644 --- a/docs/GLOSSARY_DOMAIN.md +++ b/docs/GLOSSARY_DOMAIN.md @@ -1,4 +1,5 @@ # Q‑Blog — Glossary & Domain Canon + _Generated 2025-08-16 23:27Z_ **Name** — The account identity under which blogs are created. @@ -11,10 +12,12 @@ _Generated 2025-08-16 23:27Z_ **Invite** — Time‑limited token that assigns a role on acceptance. ### Invariants -- A Post’s `blogId` does not change after creation. -- (`nameId`, `blogHandle`) is unique. + +- A Post’s `blogId` does not change after creation. +- (`nameId`, `blogHandle`) is unique. - All write operations require a role check (server‑enforced). ### Identifier & URL Guidance -- Canonical blog URL: `/{nameHandle}/{blogHandle}/…` (conceptual). + +- Canonical blog URL: `/{nameHandle}/{blogHandle}/…` (conceptual). - Slugs are normalized to lowercase, ASCII, hyphen‑separated; collisions rejected with a helpful message. diff --git a/docs/PATCH0_PLAN.md b/docs/PATCH0_PLAN.md index 28c495f..c151f3d 100644 --- a/docs/PATCH0_PLAN.md +++ b/docs/PATCH0_PLAN.md @@ -1,10 +1,13 @@ # Patch 0 — Orientation & Quality Bar + _Generated 2025-08-16 23:43Z_ ## Goal + Ratify the Quality Charter and spin up the basic governance scaffolding before touching code. ## Tasks + 1. **Adopt Quality Charter** — review, adjust targets if needed, and sign. 2. **Label set** — create canonical labels in the tracker (Area, Type, Priority, Size). 3. **Templates** — add Issue + PR templates with acceptance sections and a11y/security checks. @@ -12,10 +15,12 @@ Ratify the Quality Charter and spin up the basic governance scaffolding before t 5. **Backlog triage** — seed initial issues for Phase 1–3 planning (docs, harness, correctness). ## Label Set (proposed) + - **Type:** feat, fix, chore, docs, test, a11y, perf, security - **Area:** editor, lists, blogs, members, routing, state, build, tests, docs - **Priority:** P0, P1, P2 - **Size:** XS, S, M, L, XL ## Acceptance + - Charter signed; templates merged; labels exist; milestone created; 5–8 seeded issues for Phase 1–3. diff --git a/docs/PHASE0_CLOSEOUT.md b/docs/PHASE0_CLOSEOUT.md index a672c5a..70b53bc 100644 --- a/docs/PHASE0_CLOSEOUT.md +++ b/docs/PHASE0_CLOSEOUT.md @@ -3,6 +3,7 @@ **Goal:** repository hygiene, quality bar, and scaffolding. No behavior changes. ## Must-haves + - [ ] **Tracker**: canonical labels deduped; Phase 0 milestone open; kickoff issues present. - [ ] **Docs**: Quality Charter, Project Instructions, Testing, Releasing. - [ ] **Harness**: Vitest + RTL + MSW + jest-axe; `tests/axe-smoke.test.tsx` passing. @@ -11,8 +12,10 @@ - [ ] **Version**: bump to `0.0.1` via `scripts/release/bump-version.sh phase0`. ## Nice-to-haves + - [ ] ENV helper scripts committed (`scripts/tracker/*`). - [ ] Status doc updated (`docs/PHASE0_STATUS.md`). ## Release notes + Create `docs/RELEASE_NOTES_v0.0.1.md` with one-liners (hygiene, docs, harness, CI). diff --git a/docs/PHASE0_STATUS.md b/docs/PHASE0_STATUS.md index 9d7cbaf..1df567f 100644 --- a/docs/PHASE0_STATUS.md +++ b/docs/PHASE0_STATUS.md @@ -3,6 +3,7 @@ **Date:** 2025-08-17 04:57Z ## What we delivered + - **Tracker hygiene:** canonical labels; duplicate cleanup helpers; milestone created; seed issues for Phase 1–3. - **CI (self‑hosted):** single workflow (`.gitea/workflows/ci.yml`) using only built‑in steps; verified green. - **Dev harness:** Vitest + @testing-library/react + jest-axe; a11y smoke `tests/axe-smoke.test.tsx` passing. @@ -11,12 +12,14 @@ - **Utility scripts:** tracker bootstrap/verify; label dedupe; PR helpers. ## Acceptance recap + - Tests run and pass in CI. - Lint in Phase 0 scope is clean. - One CI workflow active; runner picks up jobs. - Milestone & initial issues exist. ## Next (Phase 1 preview) + - Remove remaining `@ts-nocheck` (scripted) and start type hygiene sweeps. - Fix MUI v4 imports, a11y `` gaps, and hooks-in-non-components issues. - Begin file‑scoped lint expansion behind small PRs. diff --git a/docs/PHASE1_NEXT_STEPS.md b/docs/PHASE1_NEXT_STEPS.md index 835305e..c62941f 100644 --- a/docs/PHASE1_NEXT_STEPS.md +++ b/docs/PHASE1_NEXT_STEPS.md @@ -1,14 +1,16 @@ # Phase 1 — Next Steps (Small, Mergeable PRs) -1) **MUI v4 → v5 import fix** +1. **MUI v4 → v5 import fix** + - Run: `bash scripts/dev/phase1/fix-mui-imports.sh --dry` then `--apply`. - Commit only the touched files + `reports/phase1-*/notes.txt` (optional). -2) **Remove TS suppressions** +2. **Remove TS suppressions** + - Run: `bash scripts/dev/phase1/fail-on-suppressions.sh` (or `--allow=1` while fixing one file per PR). - Convert ignored sections to proper types or refactor. -3) **A11y: `` alt text** +3. **A11y: `` alt text** - Run: `bash scripts/dev/phase1/list-imgs-missing-alt.sh` and fix each instance (`alt` or `alt=""` if decorative). Keep each PR focused (1–5 files), branch from `update`, and let CI run. diff --git a/docs/PHASE1_PLAN.md b/docs/PHASE1_PLAN.md index c335d98..38ea4a3 100644 --- a/docs/PHASE1_PLAN.md +++ b/docs/PHASE1_PLAN.md @@ -1,11 +1,14 @@ # Phase 1 — Baseline Scan & Planning + Goal: capture a **ground-truth snapshot** of technical debt (imports, ts-nochecks, any-usage, a11y hotspots) without changing app code. ## Deliverables + - `reports/phase1-*/` folder with raw findings and a concise `summary.md`. - Package scripts: `scan:phase1`, `report:phase1`. ## What we scan + - **MUI v4 imports** (`@material-ui/*`) — should move to `@mui/material` & friends. - **TS suppression** (`@ts-nocheck`, `@ts-ignore`) — create removal plan. - **`any` usage** — count & top files. @@ -14,6 +17,7 @@ Goal: capture a **ground-truth snapshot** of technical debt (imports, ts-nocheck - **WebWorkers globals** — `self`, `XMLHttpRequest`, `fetch` in `src/webworkers/*`. ## How to run + ```bash # 1) patch scripts into package.json scripts/dev/patch-phase1-scripts.sh diff --git a/docs/PHASE1_STEP1.md b/docs/PHASE1_STEP1.md index 7e5e102..e265a7e 100644 --- a/docs/PHASE1_STEP1.md +++ b/docs/PHASE1_STEP1.md @@ -1,19 +1,21 @@ # Phase 1 — Step 1 (Tiny PRs) -1. **Remove `@ts-nocheck`** +1. **Remove `@ts-nocheck`** + ```bash bash scripts/dev/phase1/tsnocheck-remove.sh git push origin HEAD ``` -2. **Alt text audit** +2. **Alt text audit** + ```bash bash scripts/dev/phase1/list-imgs-missing-alt.sh bash scripts/dev/phase1/alt-fix-todo.sh # open the generated reports/*/alt-fix-todo.md and address each item ``` -3. **MUI v4 imports → v5** (preview then apply) +3. **MUI v4 imports → v5** (preview then apply) ```bash bash scripts/dev/phase1/fix-mui-imports.sh --dry bash scripts/dev/phase1/fix-mui-imports.sh diff --git a/docs/PHASE1_TINY_PRS.md b/docs/PHASE1_TINY_PRS.md index 1c70033..49ba008 100644 --- a/docs/PHASE1_TINY_PRS.md +++ b/docs/PHASE1_TINY_PRS.md @@ -1,15 +1,17 @@ # Phase 1 — Tiny PR checklist -1) **Remove `// @ts-nocheck` (2 files)** +1. **Remove `// @ts-nocheck` (2 files)** + - Dry run: `bash scripts/dev/phase1/remove-ts-nocheck.sh` - Apply: `bash scripts/dev/phase1/remove-ts-nocheck.sh --apply` - Commit on `update`: `git add -A && git commit -m "phase1: drop ts-nocheck headers (no behavior change)" && git push` -2) **MUI v4 → v5 imports (if any)** +2. **MUI v4 → v5 imports (if any)** + - Dry: `bash scripts/dev/phase1/fix-mui-imports.sh --dry` - Apply in a tiny batch (1–3 files): `--apply`, commit & push. -3) **A11y: ** +3. **A11y: ** - `bash scripts/dev/phase1/list-imgs-missing-alt.sh` - Fix each hit with meaningful `alt`, or `alt=""` when decorative. diff --git a/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md b/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md index 4358559..0355c7e 100644 --- a/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md +++ b/docs/Q-Blog_1.0.0_ROADMAP_conceptual.md @@ -8,9 +8,9 @@ **How:** -* Define protected user journeys and SLIs/SLOs (e.g., time-to-interactive, save success rate, keyboard reachability). -* Adopt a single “definition of done” template (accessibility, tests, docs, telemetry hook, error state). -* Establish a lightweight decision record format (context → options → decision → impact) for any cross-cutting choice. +- Define protected user journeys and SLIs/SLOs (e.g., time-to-interactive, save success rate, keyboard reachability). +- Adopt a single “definition of done” template (accessibility, tests, docs, telemetry hook, error state). +- Establish a lightweight decision record format (context → options → decision → impact) for any cross-cutting choice. --- @@ -18,10 +18,10 @@ **How:** -* Author concise, source-of-truth docs: a high-level architecture map (UI/state/data flows), a testing strategy pyramid, an accessibility standard, and contribution rules. -* Record initial decisions (editor stack, state mgmt, theming, data shape conventions). -* Add a changelog discipline (human-readable entries grouped by “Added/Changed/Fixed/Removed/Docs/Tests”). -* Define semantic labels for issues/PRs (type, area, effort) to enable predictable triage. +- Author concise, source-of-truth docs: a high-level architecture map (UI/state/data flows), a testing strategy pyramid, an accessibility standard, and contribution rules. +- Record initial decisions (editor stack, state mgmt, theming, data shape conventions). +- Add a changelog discipline (human-readable entries grouped by “Added/Changed/Fixed/Removed/Docs/Tests”). +- Define semantic labels for issues/PRs (type, area, effort) to enable predictable triage. --- @@ -29,22 +29,26 @@ **How:** -* Testing layers: +- Testing layers: - * **Unit & component:** Vitest + JSDOM + React Testing Library; global test setup for matchers/mocks. - * **Integration/smoke (later):** small number of Playwright/Happy-path flows (create/edit/publish). -* Coverage: + - **Unit & component:** Vitest + JSDOM + React Testing Library; global test setup for matchers/mocks. + - **Integration/smoke (later):** small number of Playwright/Happy-path flows (create/edit/publish). - * Enforce thresholds at the package level; exclude generated/fixture code; surface a coverage report artifact. -* Linting/formatting: +- Coverage: - * ESLint (flat config) with TypeScript, React, and **jsx-a11y**; Prettier for formatting; consistent import ordering. -* Fast feedback: + - Enforce thresholds at the package level; exclude generated/fixture code; surface a coverage report artifact. - * Watch mode locally; pre-push hooks run **lint + typecheck + tests (changed files)**. -* CI scaffolding (conceptual): +- Linting/formatting: - * Separate jobs for install/cache, lint/typecheck, unit/component tests, and (later) e2e smoke; cache node modules; artifact uploads for coverage. + - ESLint (flat config) with TypeScript, React, and **jsx-a11y**; Prettier for formatting; consistent import ordering. + +- Fast feedback: + + - Watch mode locally; pre-push hooks run **lint + typecheck + tests (changed files)**. + +- CI scaffolding (conceptual): + + - Separate jobs for install/cache, lint/typecheck, unit/component tests, and (later) e2e smoke; cache node modules; artifact uploads for coverage. --- @@ -52,22 +56,26 @@ **How:** -* Dependency hygiene: +- Dependency hygiene: - * Align UI toolkit imports to current major; remove legacy namespaces; verify peer-dep ranges; lock versions for determinism. -* Type safety: + - Align UI toolkit imports to current major; remove legacy namespaces; verify peer-dep ranges; lock versions for determinism. - * Replace broad `any`/suppressed regions with minimal interfaces and discriminated unions where helpful. - * Introduce a “strict mode budget”: upgrades gated by keeping type errors at zero. -* State/data flows: +- Type safety: - * Standardize async lifecycles (`idle/loading/success/error`) and empty states; unify error objects (message + code + recoverability). -* Image/media safety: + - Replace broad `any`/suppressed regions with minimal interfaces and discriminated unions where helpful. + - Introduce a “strict mode budget”: upgrades gated by keeping type errors at zero. - * Enforce text alternatives policy; handle failed loads with fallbacks; guard upload constraints (size/type). -* Cross-browser sanity: +- State/data flows: - * Run a small matrix (Chromium/WebKit/Firefox) in CI for a smoke render test. + - Standardize async lifecycles (`idle/loading/success/error`) and empty states; unify error objects (message + code + recoverability). + +- Image/media safety: + + - Enforce text alternatives policy; handle failed loads with fallbacks; guard upload constraints (size/type). + +- Cross-browser sanity: + + - Run a small matrix (Chromium/WebKit/Firefox) in CI for a smoke render test. --- @@ -75,21 +83,25 @@ **How:** -* Semantics & landmarks: +- Semantics & landmarks: - * Use structural regions (header/nav/main/footer) and a skip-to-content anchor; ensure a single H1 per view. -* Keyboard & focus: + - Use structural regions (header/nav/main/footer) and a skip-to-content anchor; ensure a single H1 per view. - * Trap focus in modals/popovers, restore focus on close, visible outlines; consistent Tab/Shift+Tab order; Esc closes transient UI. -* Names/roles/states: +- Keyboard & focus: - * Deterministic accessible names for controls; toggles use `pressed` state; forms have programmatic labels and error/help text wiring. -* Live updates & motion: + - Trap focus in modals/popovers, restore focus on close, visible outlines; consistent Tab/Shift+Tab order; Esc closes transient UI. - * Polite live region for async progress; respect `prefers-reduced-motion`; avoid seizure risk (no flashing). -* Testing: +- Names/roles/states: - * Automated axe checks in component tests for critical views; a short manual keyboard checklist per protected journey. + - Deterministic accessible names for controls; toggles use `pressed` state; forms have programmatic labels and error/help text wiring. + +- Live updates & motion: + + - Polite live region for async progress; respect `prefers-reduced-motion`; avoid seizure risk (no flashing). + +- Testing: + + - Automated axe checks in component tests for critical views; a short manual keyboard checklist per protected journey. --- @@ -97,18 +109,21 @@ **How:** -* Navigation model: +- Navigation model: - * Clarify primary vs. secondary navigation; ensure routes map cleanly to the data model (current blog scope). -* Consistency system: + - Clarify primary vs. secondary navigation; ensure routes map cleanly to the data model (current blog scope). - * Define tokens for spacing/typography/contrast; standardize button/empty/error patterns; unify iconography. -* Editor ergonomics: +- Consistency system: - * Deterministic toolbar enable/disable; clear state for draft vs. published; undo/redo affordances; autosave with visible status. -* Content lifecycle: + - Define tokens for spacing/typography/contrast; standardize button/empty/error patterns; unify iconography. - * Uniform toasts/banners for success/failure; retry affordances on transient failures; protective confirms on destructive actions. +- Editor ergonomics: + + - Deterministic toolbar enable/disable; clear state for draft vs. published; undo/redo affordances; autosave with visible status. + +- Content lifecycle: + + - Uniform toasts/banners for success/failure; retry affordances on transient failures; protective confirms on destructive actions. --- @@ -116,24 +131,29 @@ **How:** -* Domain relationships: +- Domain relationships: - * **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog**; keep **Post → Blog** immutable after creation (prevents cross-blog drift). -* Identifiers & URLs: + - **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog**; keep **Post → Blog** immutable after creation (prevents cross-blog drift). - * Introduce stable blog handles (human-friendly, unique per name) that compose into routes; add collision policy and normalization rules. -* State management: +- Identifiers & URLs: - * Represent “current blog” as first-class app state; selector scoping so lists/metrics derive from the active blog. -* Migrations: + - Introduce stable blog handles (human-friendly, unique per name) that compose into routes; add collision policy and normalization rules. - * Backfill: create a default blog per name and attach historical posts; record migration version on the data root to allow repeatable ops. -* UX: +- State management: - * Blog switcher in global nav; “create blog” guided flow with handle validation and preview; clearly scoped creation forms. -* Indexing & constraints (backend/API expectations): + - Represent “current blog” as first-class app state; selector scoping so lists/metrics derive from the active blog. - * Unique composite index (name, blog handle); foreign key from posts to blog id; server rejects cross-blog updates. +- Migrations: + + - Backfill: create a default blog per name and attach historical posts; record migration version on the data root to allow repeatable ops. + +- UX: + + - Blog switcher in global nav; “create blog” guided flow with handle validation and preview; clearly scoped creation forms. + +- Indexing & constraints (backend/API expectations): + + - Unique composite index (name, blog handle); foreign key from posts to blog id; server rejects cross-blog updates. --- @@ -141,21 +161,25 @@ **How:** -* Role model (lean start): +- Role model (lean start): - * **Owner** (full control), **Editor** (edit any post, manage drafts), **Author** (create/edit own posts), **Viewer** (public, implicit). -* Invitations: + - **Owner** (full control), **Editor** (edit any post, manage drafts), **Author** (create/edit own posts), **Viewer** (public, implicit). - * Tokenized invite flow with expiration; acceptance binds a name to a role on a blog; owner can revoke. -* Enforcement: +- Invitations: - * All write endpoints check role → operation matrix; UI gates controls (disable/hide) but never trusts the client. -* Attribution & activity: + - Tokenized invite flow with expiration; acceptance binds a name to a role on a blog; owner can revoke. - * Store author id and updated-by on posts; optional lightweight activity log for edits/publishes; surface attribution in UI. -* Conflicts: +- Enforcement: - * Last-writer-wins with ETag/if-match semantics or revision numbers; reject stale writes; small conflict UI with diff view (stretch goal). + - All write endpoints check role → operation matrix; UI gates controls (disable/hide) but never trusts the client. + +- Attribution & activity: + + - Store author id and updated-by on posts; optional lightweight activity log for edits/publishes; surface attribution in UI. + +- Conflicts: + + - Last-writer-wins with ETag/if-match semantics or revision numbers; reject stale writes; small conflict UI with diff view (stretch goal). --- @@ -163,21 +187,25 @@ **How:** -* Rendering: +- Rendering: - * Keep lists virtualized; hydrate only what’s visible; memoize high-churn components; defer non-critical work with idle callbacks. -* Network: + - Keep lists virtualized; hydrate only what’s visible; memoize high-churn components; defer non-critical work with idle callbacks. - * Structured fetch layer with timeouts, abort support, and retry/backoff on idempotent GETs; idempotency keys on create/update to prevent dupes. -* Perceived speed: +- Network: - * Optimistic UI where safe (e.g., draft saves); skeletons/spinners with progress semantics; prefetch likely next views. -* Failure handling: + - Structured fetch layer with timeouts, abort support, and retry/backoff on idempotent GETs; idempotency keys on create/update to prevent dupes. - * Standard error surface (banner/toast + inline details); retry path; offline detection with queued drafts. -* Metrics: +- Perceived speed: - * Track Web Vitals and action latencies; set budgets (e.g., LCP, INP) and regressions fail CI perf checks (can be a nightly). + - Optimistic UI where safe (e.g., draft saves); skeletons/spinners with progress semantics; prefetch likely next views. + +- Failure handling: + + - Standard error surface (banner/toast + inline details); retry path; offline detection with queued drafts. + +- Metrics: + + - Track Web Vitals and action latencies; set budgets (e.g., LCP, INP) and regressions fail CI perf checks (can be a nightly). --- @@ -185,15 +213,17 @@ **How:** -* Strings: +- Strings: - * Centralize UI strings under a namespace scheme; ensure interpolation is safe; default English bundle with stubs for others. -* Formatting: + - Centralize UI strings under a namespace scheme; ensure interpolation is safe; default English bundle with stubs for others. - * Use Intl APIs for dates/numbers/plurals; avoid manual formatting; prepare RTL-safe layouts (logical properties). -* Themes: +- Formatting: - * Single source for color/spacing/typography tokens; verify contrast programmatically; support `prefers-color-scheme`. + - Use Intl APIs for dates/numbers/plurals; avoid manual formatting; prepare RTL-safe layouts (logical properties). + +- Themes: + + - Single source for color/spacing/typography tokens; verify contrast programmatically; support `prefers-color-scheme`. --- @@ -201,34 +231,37 @@ **How:** -* Observability: +- Observability: - * Error boundaries with user-friendly fallback views; structured client logs (level, code, context) routed to a collector; anonymized action breadcrumbs for repro. -* Security posture: + - Error boundaries with user-friendly fallback views; structured client logs (level, code, context) routed to a collector; anonymized action breadcrumbs for repro. - * Input validation at boundaries; sanitize rich text; content-security-policy hardening; permission checks at every write; least-privilege tokens. -* Migrations & backups: +- Security posture: - * Versioned migrations with idempotent steps; pre-prod dry run; exportable backup of critical entities; rollback procedure. -* Release discipline: + - Input validation at boundaries; sanitize rich text; content-security-policy hardening; permission checks at every write; least-privilege tokens. - * SemVer tags; changelog; migration notes; upgrade guide; “go/no-go” checklist (tests green, a11y checks pass, metrics within budget). +- Migrations & backups: + + - Versioned migrations with idempotent steps; pre-prod dry run; exportable backup of critical entities; rollback procedure. + +- Release discipline: + + - SemVer tags; changelog; migration notes; upgrade guide; “go/no-go” checklist (tests green, a11y checks pass, metrics within budget). --- ## Cross-cutting Technical Principles -* **Contracts first:** Define data and role contracts before wiring UI; document them in the repo and tests. -* **Small vertical slices:** Ship changes end-to-end (schema → API → state → UI → tests) in thin increments. -* **One source of truth:** Centralize constants (roles, statuses, limits) and import them everywhere. -* **Guardrails by default:** Lint/type/test/a11y checks run locally and in CI; merges require green gates. -* **Telemetry with restraint:** Collect only what’s necessary for quality; feature-flag any new collection. +- **Contracts first:** Define data and role contracts before wiring UI; document them in the repo and tests. +- **Small vertical slices:** Ship changes end-to-end (schema → API → state → UI → tests) in thin increments. +- **One source of truth:** Centralize constants (roles, statuses, limits) and import them everywhere. +- **Guardrails by default:** Lint/type/test/a11y checks run locally and in CI; merges require green gates. +- **Telemetry with restraint:** Collect only what’s necessary for quality; feature-flag any new collection. --- ## Assumptions & Risks -* We control both client and API contracts (or can negotiate them). -* Current data fits a clean migration to per-name default blogs without manual reconciliation. -* Editor content can be sanitized without breaking required formatting. -* Minimal E2E is enough for confidence; we can expand later if flakiness appears. +- We control both client and API contracts (or can negotiate them). +- Current data fits a clean migration to per-name default blogs without manual reconciliation. +- Editor content can be sanitized without breaking required formatting. +- Minimal E2E is enough for confidence; we can expand later if flakiness appears. diff --git a/docs/Q-Blog_1.0.0_ROADMAP_implementation.md b/docs/Q-Blog_1.0.0_ROADMAP_implementation.md index 36fe114..e4eeb11 100644 --- a/docs/Q-Blog_1.0.0_ROADMAP_implementation.md +++ b/docs/Q-Blog_1.0.0_ROADMAP_implementation.md @@ -8,13 +8,13 @@ **Libraries / tools** -* Issue labels & PR templates (tracker-native). -* Metrics: Web Vitals (`web-vitals`), synthetic a11y checks (`axe-core` via CI). +- Issue labels & PR templates (tracker-native). +- Metrics: Web Vitals (`web-vitals`), synthetic a11y checks (`axe-core` via CI). **Artifacts** -* **Quality charter**: SLOs (e.g., “save post success ≥ 99.9%”), accessibility bar (keyboard-only paths pass), perf budgets (LCP/INP). -* **Protected journeys** list: *read post, create post, edit post, manage blogs*. +- **Quality charter**: SLOs (e.g., “save post success ≥ 99.9%”), accessibility bar (keyboard-only paths pass), perf budgets (LCP/INP). +- **Protected journeys** list: _read post, create post, edit post, manage blogs_. **Validation** -* Signed-off charter; journeys ratified in a short doc. +- Signed-off charter; journeys ratified in a short doc. --- @@ -22,13 +22,13 @@ **Libraries / tools** -* Diagrams as code (Mermaid in docs renderer). -* Conventional commits (tooling optional). +- Diagrams as code (Mermaid in docs renderer). +- Conventional commits (tooling optional). **Artifacts** -* **Architecture map** (UI ↔ state ↔ API), **Testing pyramid**, **A11y standard**, **Contributing rules**, **Changelog categories**. -* **Decision Record template** (context → options → decision → impact). +- **Architecture map** (UI ↔ state ↔ API), **Testing pyramid**, **A11y standard**, **Contributing rules**, **Changelog categories**. +- **Decision Record template** (context → options → decision → impact). **Validation** -* New contributor dry run: “can run + test + understand contracts in <30 min.” +- New contributor dry run: “can run + test + understand contracts in <30 min.” --- @@ -36,20 +36,20 @@ **Libraries** -* Unit/Component: **Vitest**, **@testing-library/react**, **@testing-library/user-event**, **@testing-library/jest-dom**. -* A11y lint/checks: **eslint-plugin-jsx-a11y**, **axe-core** or **jest-axe**. -* Mocking: **MSW** (Mock Service Worker) for API. -* Types: TypeScript strict mode. -* Lint/format: **eslint** (flat config), **@typescript-eslint** plugin, **prettier**. +- Unit/Component: **Vitest**, **@testing-library/react**, **@testing-library/user-event**, **@testing-library/jest-dom**. +- A11y lint/checks: **eslint-plugin-jsx-a11y**, **axe-core** or **jest-axe**. +- Mocking: **MSW** (Mock Service Worker) for API. +- Types: TypeScript strict mode. +- Lint/format: **eslint** (flat config), **@typescript-eslint** plugin, **prettier**. **Key setup** -* Global test setup (RTL matchers, MSW server, clock/timers). -* Coverage thresholds (e.g., 80/70/70 lines/branches/functions to start). -* CI lanes: install/cache → typecheck → lint → unit/component → (later) e2e smoke. +- Global test setup (RTL matchers, MSW server, clock/timers). +- Coverage thresholds (e.g., 80/70/70 lines/branches/functions to start). +- CI lanes: install/cache → typecheck → lint → unit/component → (later) e2e smoke. **Tests to seed** -* Smoke renders: home, post list, editor screen. -* Tiny interaction: typing in editor, enabling/disabling toolbar control, save button disabled until valid. +- Smoke renders: home, post list, editor screen. +- Tiny interaction: typing in editor, enabling/disabling toolbar control, save button disabled until valid. **Validation** -* Green **typecheck + lint + tests** on clean checkout; coverage reports generated. +- Green **typecheck + lint + tests** on clean checkout; coverage reports generated. --- @@ -57,28 +57,30 @@ **Libraries** -* RTK ecosystem: **@reduxjs/toolkit** (already present), **RTK Query** for data-fetching (adds caching, retries, invalidation). -* Schema/runtime validation: **Zod** (form/io validation) **and** `zod-to-json-schema` for docs **or** **AJV** if we align with JSON Schema like Q-Chess. (Pick one; see “Contracts” below.) +- RTK ecosystem: **@reduxjs/toolkit** (already present), **RTK Query** for data-fetching (adds caching, retries, invalidation). +- Schema/runtime validation: **Zod** (form/io validation) **and** `zod-to-json-schema` for docs **or** **AJV** if we align with JSON Schema like Q-Chess. (Pick one; see “Contracts” below.) **Contracts / functions** -* **Fetch layer** with cancellation & retries: +- **Fetch layer** with cancellation & retries: - * `fetchJson(req: Request, opts): Promise>` - * `withTimeout(controller, ms): AbortSignal` -* **Async state convention**: `{ status: 'idle'|'loading'|'success'|'error', data?, error? }`. -* **Error object**: `{ code: string; message: string; recoverable: boolean }`. -* **Validation**: + - `fetchJson(req: Request, opts): Promise>` + - `withTimeout(controller, ms): AbortSignal` - * For Zod: `const PostDto = z.object({ id: z.string(), ... })` → `parse(response)`. - * For AJV: compile JSON Schemas once; validate per response. +- **Async state convention**: `{ status: 'idle'|'loading'|'success'|'error', data?, error? }`. +- **Error object**: `{ code: string; message: string; recoverable: boolean }`. +- **Validation**: + + - For Zod: `const PostDto = z.object({ id: z.string(), ... })` → `parse(response)`. + - For AJV: compile JSON Schemas once; validate per response. **Work items** -* Remove legacy UI imports (v4 → v5), delete `ts-nocheck` sections by adding minimal types. -* Replace `any` in reducers/selectors and editor props with discriminated unions & precise payloads. -* Normalize empty/loading/error states for all remote views. + +- Remove legacy UI imports (v4 → v5), delete `ts-nocheck` sections by adding minimal types. +- Replace `any` in reducers/selectors and editor props with discriminated unions & precise payloads. +- Normalize empty/loading/error states for all remote views. **Tests** -* Contract tests: MSW returns both **valid** and **invalid** payloads → validation errors surface as user-friendly messages. -* Reducer tests: transitions for each thunk/query state. +- Contract tests: MSW returns both **valid** and **invalid** payloads → validation errors surface as user-friendly messages. +- Reducer tests: transitions for each thunk/query state. **Validation** -* No suppressed type regions; “known issues” list empty; core routes never hard-crash on bad data. +- No suppressed type regions; “known issues” list empty; core routes never hard-crash on bad data. --- @@ -86,20 +88,20 @@ **Libraries** -* A11y testing: **jest-axe** or **axe-core** integration; **testing-library** queries by role/name. -* Focus management: rely on MUI’s **Dialog/Popover** focus traps; add **focus-trap** only where needed. +- A11y testing: **jest-axe** or **axe-core** integration; **testing-library** queries by role/name. +- Focus management: rely on MUI’s **Dialog/Popover** focus traps; add **focus-trap** only where needed. **Contracts / behaviors** -* **Landmarks**: header/nav/main/footer; **Skip link** available and visible on focus. -* **Focus policy**: focus moves to first interactive element on open; returns to invoker on close. -* **Names/roles/states**: toggles expose `aria-pressed`; inputs have programmatic labels; errors are linked with `aria-describedby`. -* **Live regions**: polite updates for “saving…/uploaded/failed”. -* **Motion/contrast**: respect `prefers-reduced-motion`; enforce contrast tokens. +- **Landmarks**: header/nav/main/footer; **Skip link** available and visible on focus. +- **Focus policy**: focus moves to first interactive element on open; returns to invoker on close. +- **Names/roles/states**: toggles expose `aria-pressed`; inputs have programmatic labels; errors are linked with `aria-describedby`. +- **Live regions**: polite updates for “saving…/uploaded/failed”. +- **Motion/contrast**: respect `prefers-reduced-motion`; enforce contrast tokens. **Tests** -* Keyboard paths for protected journeys (Tab/Shift+Tab order, Escape close). -* axe checks for key pages (no critical violations). -* Live region announces long-running operations (assertions via `toHaveAccessibleDescription` or role queries). +- Keyboard paths for protected journeys (Tab/Shift+Tab order, Escape close). +- axe checks for key pages (no critical violations). +- Live region announces long-running operations (assertions via `toHaveAccessibleDescription` or role queries). **Validation** -* Keyboard-only success across protected journeys; automated checks pass with zero criticals. +- Keyboard-only success across protected journeys; automated checks pass with zero criticals. --- @@ -107,23 +109,25 @@ **Libraries** -* Router (current choice): ensure route guards & prefetch hooks supported. -* Toasts/banners: lightweight (e.g., notistack or MUI Snackbar pattern). +- Router (current choice): ensure route guards & prefetch hooks supported. +- Toasts/banners: lightweight (e.g., notistack or MUI Snackbar pattern). **Contracts / functions** -* **Design tokens**: spacing, radius, typography, color roles (text, surface, brand) centralized. -* **Common UI patterns**: +- **Design tokens**: spacing, radius, typography, color roles (text, surface, brand) centralized. +- **Common UI patterns**: - * Empty state contract: `{ icon?, title, body, primaryAction?, secondaryAction? }` - * Error surface: banner + inline guidance; all errors have recovery action. -* **Editor ergonomics**: + - Empty state contract: `{ icon?, title, body, primaryAction?, secondaryAction? }` + - Error surface: banner + inline guidance; all errors have recovery action. - * Toolbar state machine: `computeToolbarState({ selection, schema }) → { boldEnabled, … }` - * Draft lifecycle: `autosaveDraft(postId, content) → { savedAt }` with debounce and “last saved” indicator. +- **Editor ergonomics**: + + - Toolbar state machine: `computeToolbarState({ selection, schema }) → { boldEnabled, … }` + - Draft lifecycle: `autosaveDraft(postId, content) → { savedAt }` with debounce and “last saved” indicator. **Tests** -* Token snapshots for theme roles; editor toolbar enable/disable based on selection. -* Error/empty states render consistently across pages (table-driven tests). + +- Token snapshots for theme roles; editor toolbar enable/disable based on selection. +- Error/empty states render consistently across pages (table-driven tests). **Validation** -* Navigation clarity (first-click discoverability); zero dead ends; consistent affordances. +- Navigation clarity (first-click discoverability); zero dead ends; consistent affordances. --- @@ -131,26 +135,27 @@ **Libraries** -* State & data: continue **RTK Query** (cache per blog key). -* Validation: same as Phase 3 (Zod/AJV). +- State & data: continue **RTK Query** (cache per blog key). +- Validation: same as Phase 3 (Zod/AJV). **Domain model** -* **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog** (immutable relationship post-creation). -* **Identifiers**: +- **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog** (immutable relationship post-creation). +- **Identifiers**: - * Blog handle: lowercase, slug rules; unique per **Name**; stored canonical form. - * URL composition: `/{name}/{blogHandle}/…` (conceptual, router-agnostic). + - Blog handle: lowercase, slug rules; unique per **Name**; stored canonical form. + - URL composition: `/{name}/{blogHandle}/…` (conceptual, router-agnostic). **Functions / contracts** -* `listBlogs(name): Promise` -* `createBlog(input: { handle; title; visibility }): Promise` -* `switchBlog(handle): void` (updates app-scoped “current blog”) -* `listPosts(blogId, filters)`, `createPost(blogId, input)` -* **Migration**: `backfillDefaultBlogs(): MigrationResult` (idempotent: skip if blog exists) + +- `listBlogs(name): Promise` +- `createBlog(input: { handle; title; visibility }): Promise` +- `switchBlog(handle): void` (updates app-scoped “current blog”) +- `listPosts(blogId, filters)`, `createPost(blogId, input)` +- **Migration**: `backfillDefaultBlogs(): MigrationResult` (idempotent: skip if blog exists) **Tests** -* Migration test with legacy dataset → posts appear under default blog. -* Router/selector scoping: when switching blogs, lists & counts update without leaking data. -* Handle validation: rejects collisions & invalid slugs; normalizes input. +- Migration test with legacy dataset → posts appear under default blog. +- Router/selector scoping: when switching blogs, lists & counts update without leaking data. +- Handle validation: rejects collisions & invalid slugs; normalizes input. **Validation** -* Users can create/switch blogs; all lists/summaries are scoped; legacy users retained seamlessly. +- Users can create/switch blogs; all lists/summaries are scoped; legacy users retained seamlessly. --- @@ -158,36 +163,41 @@ **Libraries** -* Role/permission helpers: in-house constants; optional **casl** if we want DSL-like permissions (not required). +- Role/permission helpers: in-house constants; optional **casl** if we want DSL-like permissions (not required). **Role model** -* **Owner**, **Editor**, **Author**, **Viewer** (implicit). -* Operation matrix: +- **Owner**, **Editor**, **Author**, **Viewer** (implicit). +- Operation matrix: - * Owner: all - * Editor: edit any post, manage drafts - * Author: CRUD own posts + - Owner: all + - Editor: edit any post, manage drafts + - Author: CRUD own posts **Functions / contracts** -* Membership: - * `inviteMember(blogId, name, role): InviteToken` - * `acceptInvite(token): Membership` - * `removeMember(blogId, name): void` - * `listMembers(blogId): Membership[]` -* Authorization guard: +- Membership: - * `can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { user, blog, post? }): boolean` -* Attribution: + - `inviteMember(blogId, name, role): InviteToken` + - `acceptInvite(token): Membership` + - `removeMember(blogId, name): void` + - `listMembers(blogId): Membership[]` - * Post metadata: `{ createdBy, updatedBy, updatedAt, revision }` -* Concurrency: +- Authorization guard: - * `savePost(postId, input, { ifMatchRevision }): { revision }` → 409 on stale; client shows resolve UI. + - `can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { user, blog, post? }): boolean` + +- Attribution: + + - Post metadata: `{ createdBy, updatedBy, updatedAt, revision }` + +- Concurrency: + + - `savePost(postId, input, { ifMatchRevision }): { revision }` → 409 on stale; client shows resolve UI. **Tests** -* Permission matrix table tests (unit): each role × operation result. -* API guard tests via MSW: simulate forbidden responses, assert UI disables and surfaces denial. -* Concurrency tests: stale write attempt returns conflict → user offered resolve path. + +- Permission matrix table tests (unit): each role × operation result. +- API guard tests via MSW: simulate forbidden responses, assert UI disables and surfaces denial. +- Concurrency tests: stale write attempt returns conflict → user offered resolve path. **Validation** -* Owner can manage access; editors/authors can work within role; no unauthorized writes pass. +- Owner can manage access; editors/authors can work within role; no unauthorized writes pass. --- @@ -195,19 +205,19 @@ **Libraries** -* **react-virtuoso** or equivalent (already used) for long lists. -* Data layer retries/backoff: **RTK Query**’s retry plugin or custom wrapper. -* RUM metrics: **web-vitals** + lightweight sender. +- **react-virtuoso** or equivalent (already used) for long lists. +- Data layer retries/backoff: **RTK Query**’s retry plugin or custom wrapper. +- RUM metrics: **web-vitals** + lightweight sender. **Functions / policies** -* **Optimistic updates** where safe (draft save, title edits) with rollback on failure. -* **Retry policy**: GETs exponential backoff; POST/PUT guarded by idempotency keys where applicable. -* **Abortable fetch**: timeouts and user-initiated cancel for uploads. -* **Prefetching**: on-hover/visible prefetch of likely next routes and data keys. +- **Optimistic updates** where safe (draft save, title edits) with rollback on failure. +- **Retry policy**: GETs exponential backoff; POST/PUT guarded by idempotency keys where applicable. +- **Abortable fetch**: timeouts and user-initiated cancel for uploads. +- **Prefetching**: on-hover/visible prefetch of likely next routes and data keys. **Tests** -* Latency injection with MSW to verify spinners/skeletons and cancel/abort behaviors. -* Optimistic update rollback test: forced server failure restores previous UI state. +- Latency injection with MSW to verify spinners/skeletons and cancel/abort behaviors. +- Optimistic update rollback test: forced server failure restores previous UI state. **Validation** -* Subjective feel: interactions seem instant; objective: Web Vitals within budget; error paths recover without data loss. +- Subjective feel: interactions seem instant; objective: Web Vitals within budget; error paths recover without data loss. --- @@ -215,17 +225,17 @@ **Libraries** -* **react-i18next** (already used in sister projects). -* Intl APIs for date/number/plural; fallback polyfills if needed. +- **react-i18next** (already used in sister projects). +- Intl APIs for date/number/plural; fallback polyfills if needed. **Functions / contracts** -* `t('namespace:key', { vars })` usage with explicit, descriptive keys. -* Currency/number/date formatters as wrapper utilities to standardize formatting. -* Theme tokens as a single source of truth; contrast checks (scripted) against WCAG AA. +- `t('namespace:key', { vars })` usage with explicit, descriptive keys. +- Currency/number/date formatters as wrapper utilities to standardize formatting. +- Theme tokens as a single source of truth; contrast checks (scripted) against WCAG AA. **Tests** -* Snapshot tests for key screens in both light/dark with token diffs. -* Formatting tests for pluralization and number/date locales. +- Snapshot tests for key screens in both light/dark with token diffs. +- Formatting tests for pluralization and number/date locales. **Validation** -* Strings externalized; theme passes automated contrast check; RTL-safety spot check on core screens. +- Strings externalized; theme passes automated contrast check; RTL-safety spot check on core screens. --- @@ -233,29 +243,32 @@ **Libraries** -* Error tracking: **Sentry** (or equivalent) with source maps. -* Sanitization: **DOMPurify** for any HTML serialization/render of editor output. -* CSP guidance (server-side; document the policy). +- Error tracking: **Sentry** (or equivalent) with source maps. +- Sanitization: **DOMPurify** for any HTML serialization/render of editor output. +- CSP guidance (server-side; document the policy). **Functions / policies** -* **Error boundaries**: route-level and editor-level with friendly fallback and “copy details”. -* **Client logging**: `logClientError({ code, message, context })` throttle; opt-in breadcrumbs (sanitized, no PII). -* **Security checks**: +- **Error boundaries**: route-level and editor-level with friendly fallback and “copy details”. +- **Client logging**: `logClientError({ code, message, context })` throttle; opt-in breadcrumbs (sanitized, no PII). +- **Security checks**: - * Validate all inputs client-side with Zod/AJV before sending. - * Sanitize rich text on ingest and display; allowlist for marks/nodes. - * Permission checks every write path; never trust client-side hide/disable alone. -* **Migrations**: + - Validate all inputs client-side with Zod/AJV before sending. + - Sanitize rich text on ingest and display; allowlist for marks/nodes. + - Permission checks every write path; never trust client-side hide/disable alone. - * Versioned, idempotent; dry-run flag; record migration status. -* **Release discipline**: +- **Migrations**: - * Version bump + changelog; migration notes; upgrade guide; go/no-go checklist (green gates + budgets met). + - Versioned, idempotent; dry-run flag; record migration status. + +- **Release discipline**: + + - Version bump + changelog; migration notes; upgrade guide; go/no-go checklist (green gates + budgets met). **Tests** -* Error boundary renders fallback on thrown child component; telemetry is called with redacted payload. -* XSS tests: paste malicious payload → sanitized output (no script execution) both save and render paths. -* Migration dry-run/effects tests with sample datasets. + +- Error boundary renders fallback on thrown child component; telemetry is called with redacted payload. +- XSS tests: paste malicious payload → sanitized output (no script execution) both save and render paths. +- Migration dry-run/effects tests with sample datasets. **Validation** -* Crashes captured with actionable context; no stored XSS vectors; release checklist green. +- Crashes captured with actionable context; no stored XSS vectors; release checklist green. --- @@ -263,35 +276,36 @@ **Contracts-first** -* Pick **Zod** *(form/input + client response validation, type inference)* or **JSON Schema + AJV** *(cross-project parity with Q-Chess)*. +- Pick **Zod** _(form/input + client response validation, type inference)_ or **JSON Schema + AJV** _(cross-project parity with Q-Chess)_. - * If Zod: generate JSON Schema for docs via `zod-to-json-schema`. - * If AJV: generate TypeScript types via `json-schema-to-ts`. -* Maintain a single **roles/status/constants** module imported across UI, data, and tests. + - If Zod: generate JSON Schema for docs via `zod-to-json-schema`. + - If AJV: generate TypeScript types via `json-schema-to-ts`. + +- Maintain a single **roles/status/constants** module imported across UI, data, and tests. **Data access layer** -* Prefer **RTK Query** for cache, memoized selectors, retries, and invalidation; treat queries/mutations as the one path to remote state. +- Prefer **RTK Query** for cache, memoized selectors, retries, and invalidation; treat queries/mutations as the one path to remote state. **State shaping** -* Store UI state distinct from server cache; selectors derive view models (small pure functions, unit-tested). +- Store UI state distinct from server cache; selectors derive view models (small pure functions, unit-tested). **Security posture** -* Default-deny in permission checks; treat editor input as untrusted; output sanitize on every render path. +- Default-deny in permission checks; treat editor input as untrusted; output sanitize on every render path. **Testing pyramid (target distributions)** -* Unit (\~60%): pure utils, selectors, reducers, permission guards. -* Component (\~30%): screens/components with RTL (+ axe). -* Integration/E2E (\~10%): happy-path create/edit/publish; blog switch; invite/accept. +- Unit (\~60%): pure utils, selectors, reducers, permission guards. +- Component (\~30%): screens/components with RTL (+ axe). +- Integration/E2E (\~10%): happy-path create/edit/publish; blog switch; invite/accept. **CI** -* Node LTS matrix (current + previous). -* Caching for package manager. -* Artifacts: coverage report, axe report, Web Vitals synthetic (if applicable), build output for preview. +- Node LTS matrix (current + previous). +- Caching for package manager. +- Artifacts: coverage report, axe report, Web Vitals synthetic (if applicable), build output for preview. --- @@ -299,29 +313,45 @@ ```ts // Networking & validation -type Result = { ok: true; data: T } | { ok: false; error: { code: string; message: string; recoverable: boolean } }; -function fetchJson(input: RequestInfo, init?: RequestInit & { timeoutMs?: number }): Promise>; +type Result = + | { ok: true; data: T } + | { ok: false; error: { code: string; message: string; recoverable: boolean } }; +function fetchJson( + input: RequestInfo, + init?: RequestInit & { timeoutMs?: number }, +): Promise>; // Permissions type Role = 'owner' | 'editor' | 'author'; -function can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { role: Role; userId: string; postOwnerId?: string }): boolean; +function can( + op: 'create' | 'edit' | 'publish' | 'manageMembers', + ctx: { role: Role; userId: string; postOwnerId?: string }, +): boolean; // Blog management -function createBlog(input: { handle: string; title: string; visibility: 'public'|'private' }): Promise>; +function createBlog(input: { + handle: string; + title: string; + visibility: 'public' | 'private'; +}): Promise>; function inviteMember(blogId: string, name: string, role: Role): Promise>; -function savePost(postId: string, input: PostInput, opts: { ifMatchRevision: number }): Promise>; +function savePost( + postId: string, + input: PostInput, + opts: { ifMatchRevision: number }, +): Promise>; ``` --- ## Completion Bars (what “done” looks like per category) -* **Correctness:** zero suppressed type regions; known issues = empty; core flows never hard-crash. -* **Testability:** green typecheck/lint/tests on clean checkout; coverage trend up and stable; fast local watch. -* **Accessibility:** keyboard-only paths pass; axe shows no criticals; live region & focus rules verified. -* **Usability:** consistent patterns for empty/error; editor ergonomics predictable; no dead ends. -* **Security:** sanitized rich text; permission matrix enforced server- and client-side; no privilege escalation paths. -* **Performance:** web vitals within budget; optimistic updates/abortable fetches; graceful failure/retry. +- **Correctness:** zero suppressed type regions; known issues = empty; core flows never hard-crash. +- **Testability:** green typecheck/lint/tests on clean checkout; coverage trend up and stable; fast local watch. +- **Accessibility:** keyboard-only paths pass; axe shows no criticals; live region & focus rules verified. +- **Usability:** consistent patterns for empty/error; editor ergonomics predictable; no dead ends. +- **Security:** sanitized rich text; permission matrix enforced server- and client-side; no privilege escalation paths. +- **Performance:** web vitals within budget; optimistic updates/abortable fetches; graceful failure/retry. --- diff --git a/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md b/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md index 81f124f..0c75d77 100644 --- a/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md +++ b/docs/Q-Blog_1.0.0_ROADMAP_non-technical.md @@ -8,10 +8,10 @@ **Goal:** Align on what “top quality” means. -* Define success metrics: a11y baseline, crash/error rate, perf feel (snappy interactions), test coverage targets, doc set. -* Pick a short list of user journeys to protect (e.g., read posts, create post, edit post, manage blogs). +- Define success metrics: a11y baseline, crash/error rate, perf feel (snappy interactions), test coverage targets, doc set. +- Pick a short list of user journeys to protect (e.g., read posts, create post, edit post, manage blogs). **Done when:** -* We have a one-pager of goals/metrics and the protected journeys list. +- We have a one-pager of goals/metrics and the protected journeys list. --- @@ -19,10 +19,10 @@ **Goal:** Make the project legible and predictable. -* Author concise docs (what/why/how): Overview, Architecture sketch, Testing approach, Accessibility expectations, Contributing, Changelog. -* Record a few early decisions (editor, state, data shapes) as short decision notes. +- Author concise docs (what/why/how): Overview, Architecture sketch, Testing approach, Accessibility expectations, Contributing, Changelog. +- Record a few early decisions (editor, state, data shapes) as short decision notes. **Done when:** -* New contributor can read docs and understand how to run, test, and contribute—without asking questions. +- New contributor can read docs and understand how to run, test, and contribute—without asking questions. --- @@ -30,11 +30,11 @@ **Goal:** Establish guardrails to catch regressions early. -* Stand up unit/component test harness and a minimal test pyramid strategy (unit > component > a few integration). -* Add accessibility and code-quality lint passes (no rules list yet—just the intent). +- Stand up unit/component test harness and a minimal test pyramid strategy (unit > component > a few integration). +- Add accessibility and code-quality lint passes (no rules list yet—just the intent). **Done when:** -* “Run tests” and “lint” are routine and green on a clean checkout. -* We can write a simple render test for a page without friction (proof the harness works). +- “Run tests” and “lint” are routine and green on a clean checkout. +- We can write a simple render test for a page without friction (proof the harness works). --- @@ -42,11 +42,11 @@ **Goal:** Remove known hazards and obvious defects. -* Replace legacy/broken imports, remove blanket type-ignores, fix crashing edge cases and flaky flows. -* Normalize basic data flows (loading/error/empty states) so the app never “mystery fails.” +- Replace legacy/broken imports, remove blanket type-ignores, fix crashing edge cases and flaky flows. +- Normalize basic data flows (loading/error/empty states) so the app never “mystery fails.” **Done when:** -* No known red flags remain (e.g., invalid imports, suppressed type errors, missing alt text). -* Core pages load and operate consistently on a fresh profile. +- No known red flags remain (e.g., invalid imports, suppressed type errors, missing alt text). +- Core pages load and operate consistently on a fresh profile. --- @@ -54,11 +54,11 @@ **Goal:** Make the app operable by keyboard and assistive tech. -* Landmarks & navigation: header/nav/main/footer + skip link. -* Focus management for dialogs/menus/editor; visible focus rings. -* Text alternatives, form labels, live region for async work; motion/contrast preferences respected. +- Landmarks & navigation: header/nav/main/footer + skip link. +- Focus management for dialogs/menus/editor; visible focus rings. +- Text alternatives, form labels, live region for async work; motion/contrast preferences respected. **Done when:** -* Keyboard-only journeys succeed; basic automated checks are clean; a short manual checklist passes. +- Keyboard-only journeys succeed; basic automated checks are clean; a short manual checklist passes. --- @@ -66,10 +66,10 @@ **Goal:** Reduce friction and ambiguity before adding new power. -* Clear page hierarchy and navigation; consistent button labels and empty/error states. -* Editor ergonomics: predictable toolbar states, undo/redo clarity, draft/publish feedback. +- Clear page hierarchy and navigation; consistent button labels and empty/error states. +- Editor ergonomics: predictable toolbar states, undo/redo clarity, draft/publish feedback. **Done when:** -* The protected journeys feel obvious and forgiving (you can’t get stuck; errors are actionable). +- The protected journeys feel obvious and forgiving (you can’t get stuck; errors are actionable). --- @@ -77,13 +77,13 @@ **Goal:** Allow one “name” to own several separate blogs. -* Concepts (non-technical): **Name** owns 0..N **Blogs**; **Post** belongs to exactly one **Blog**. -* Identity: pick a human-friendly blog identifier (e.g., “name + blog handle”) that works well in URLs. -* UX: a simple “Blog switcher” and “Create new blog” flow; list scoped to current blog. -* Migration: existing content becomes Blog #1 for each name. +- Concepts (non-technical): **Name** owns 0..N **Blogs**; **Post** belongs to exactly one **Blog**. +- Identity: pick a human-friendly blog identifier (e.g., “name + blog handle”) that works well in URLs. +- UX: a simple “Blog switcher” and “Create new blog” flow; list scoped to current blog. +- Migration: existing content becomes Blog #1 for each name. **Done when:** -* A user can create/switch blogs and see posts scoped appropriately. -* Existing users’ posts are intact under their first blog without surprises. +- A user can create/switch blogs and see posts scoped appropriately. +- Existing users’ posts are intact under their first blog without surprises. --- @@ -91,12 +91,12 @@ **Goal:** Enable collaboration on a blog. -* Roles (simple first): **Owner**, **Editor**, **Author** (can post), **Viewer** (implicit, public). -* Invitations & membership management: add/remove collaborators; show who has access. -* Edit safeguards: clear attribution on posts, activity notes on updates (lightweight). +- Roles (simple first): **Owner**, **Editor**, **Author** (can post), **Viewer** (implicit, public). +- Invitations & membership management: add/remove collaborators; show who has access. +- Edit safeguards: clear attribution on posts, activity notes on updates (lightweight). **Done when:** -* Owner can invite/remove collaborators. -* Collaborators can create/edit posts according to role, and non-members cannot. +- Owner can invite/remove collaborators. +- Collaborators can create/edit posts according to role, and non-members cannot. --- @@ -104,10 +104,10 @@ **Goal:** Keep interactions fast and reliable as features grow. -* Perceived speed: snappy navigation, optimistic updates where safe, progress indicators for async operations. -* Robustness: graceful offline/slow-network handling for key actions; safe retries on saves/uploads. +- Perceived speed: snappy navigation, optimistic updates where safe, progress indicators for async operations. +- Robustness: graceful offline/slow-network handling for key actions; safe retries on saves/uploads. **Done when:** -* Core actions “feel instant” with clear feedback, and temporary failures recover without data loss. +- Core actions “feel instant” with clear feedback, and temporary failures recover without data loss. --- @@ -115,10 +115,10 @@ **Goal:** Make UI text and visuals consistent and adaptable. -* Externalize UI strings; ensure headings/labels follow a consistent style. -* Validate light/dark themes for contrast and motion preferences. +- Externalize UI strings; ensure headings/labels follow a consistent style. +- Validate light/dark themes for contrast and motion preferences. **Done when:** -* Strings are centralized for future i18n; themes meet contrast/motion guardrails. +- Strings are centralized for future i18n; themes meet contrast/motion guardrails. --- @@ -126,33 +126,33 @@ **Goal:** Ship 1.0 with confidence. -* Error boundaries and user-friendly error pages; minimal analytics/telemetry (opt-in) to catch crashes. -* Basic security posture review (inputs, uploads, permission checks). -* Backups/migration notes; upgrade guide; crisp release notes. +- Error boundaries and user-friendly error pages; minimal analytics/telemetry (opt-in) to catch crashes. +- Basic security posture review (inputs, uploads, permission checks). +- Backups/migration notes; upgrade guide; crisp release notes. **Done when:** -* We can trace and act on production errors; permissions are enforced in all write paths; the “1.0 checklist” is fully checked. +- We can trace and act on production errors; permissions are enforced in all write paths; the “1.0 checklist” is fully checked. --- # Why this order? -* **Docs + tests first** reduce rework (catch issues as we fix). -* **Correctness → a11y → UX** ensures polish builds on stable, inclusive foundations. -* **Multi-blog** precedes **Shared blogs** so collaboration lands on the right data model. -* **Perf/Resilience** and **Release** bookend features to keep the app trustworthy. +- **Docs + tests first** reduce rework (catch issues as we fix). +- **Correctness → a11y → UX** ensures polish builds on stable, inclusive foundations. +- **Multi-blog** precedes **Shared blogs** so collaboration lands on the right data model. +- **Perf/Resilience** and **Release** bookend features to keep the app trustworthy. --- ## Acceptance bars (numbers keep us honest) -* Accessibility: keyboard-only passes for protected journeys; automated checks free of critical issues. -* Reliability: zero known crashers in core flows; error states always show recovery actions. -* Testability: green test/lint on clean checkout; steadily rising coverage (we’ll agree targets when we stand up the harness). -* Usability: no dead ends; consistent labels; empty/error states always informative. +- Accessibility: keyboard-only passes for protected journeys; automated checks free of critical issues. +- Reliability: zero known crashers in core flows; error states always show recovery actions. +- Testability: green test/lint on clean checkout; steadily rising coverage (we’ll agree targets when we stand up the harness). +- Usability: no dead ends; consistent labels; empty/error states always informative. --- ## On your two feature asks -* **Multiple blogs per name:** 👍 Fits naturally with Q-Blog’s goals and unlocks better organization. We’ll keep identifiers human-readable and migrate existing content seamlessly. -* **Shared blogs:** 👍 A strong, later-phase addition. We’ll start with a lean role model and keep invites/management simple to avoid UX sprawl. +- **Multiple blogs per name:** 👍 Fits naturally with Q-Blog’s goals and unlocks better organization. We’ll keep identifiers human-readable and migrate existing content seamlessly. +- **Shared blogs:** 👍 A strong, later-phase addition. We’ll start with a lean role model and keep invites/management simple to avoid UX sprawl. diff --git a/docs/Q-Blog_Project_Instructions.md b/docs/Q-Blog_Project_Instructions.md index c2e1e95..a4911f0 100644 --- a/docs/Q-Blog_Project_Instructions.md +++ b/docs/Q-Blog_Project_Instructions.md @@ -1,4 +1,5 @@ # Q-Blog — Project Instructions (1.0 Track) + _Generated 2025-08-16 23:37Z_ > Default instructions for all Q-Blog chats. Optimize for **correctness, accessibility, collaboration, and a focused writing UX**. Keep answers decisive and artifact‑oriented. @@ -6,14 +7,17 @@ _Generated 2025-08-16 23:37Z_ --- ## 1) Intent + Q-Blog is a modern blogging workspace for **individuals and small teams**. Writers can own **multiple blogs under one Name**, and enable **Shared Blogs** with roles (Owner/Editor/Author). North star: **inclusive, predictable publishing** with a resilient, testable UI. --- ## 2) Core flows & stable components (props) + **Flows:** Read → Draft → Edit → Publish → Manage Blogs → Collaborate. **BlogSwitcher** — select active blog; scopes all lists/forms. + ```ts activeBlog: BlogRef | null blogs: BlogSummary[] @@ -23,6 +27,7 @@ disabled?: boolean ``` **PostEditor** — Slate-based editor with autosave and preview. + ```ts mode: 'create' | 'edit' blog: BlogRef @@ -35,6 +40,7 @@ status: 'idle'|'saving'|'publishing'|'error'|'success' ``` **PostList** — virtualized list scoped to active blog. + ```ts blog: BlogRef filters: PostFilters @@ -42,6 +48,7 @@ onOpenPost: (id: Id) => void ``` **MembersPanel** (shared blogs). + ```ts blog: BlogRef members: Membership[] @@ -51,6 +58,7 @@ currentUserRole: Role ``` **Header/Nav** — global actions. + ```ts onOpenMembers?: () => void onCreatePost: () => void @@ -63,6 +71,7 @@ unreadCount?: number --- ## 3) Tech & repo posture + React 18 + TypeScript (strict) + Vite; **MUI v5**; Slate editor; **Redux Toolkit + RTK Query**; i18next (strings). Testing: **Vitest + RTL + user-event + MSW + jest-axe**. Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. Layout: `src/`, `tests/`, `docs/`, `.gitea/`. @@ -70,21 +79,27 @@ Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. La --- ## 4) Behavior contracts + **Blog scoping & routing** + - Every view is scoped to **active blog**. Route params encode scope (conceptually `/{name}/{blog}/...`). Changing blog updates lists/forms without leaks. **Editor & autosave** + - Debounced autosave writes drafts with **polite live region** announcements. Clear “Last saved” time; explicit **Publish** pathway. Toolbar enable/disable is deterministic from selection. **Post lifecycle** + - States: `draft | scheduled | published | archived`. Idempotent publish; show attribution (`createdBy`, `updatedBy`, `updatedAt`). **Media** + - Client validates type/size; shows upload progress; failed uploads are retryable; all `` have alt (or `alt=""` if decorative). --- ## 5) Accessibility (A11y) + - Landmarks: header/nav/main/footer; **Skip link** focuses main. One H1 per route. - Keyboard: all controls reachable; **Esc closes** dialogs/popovers and **restores focus**. - Names/roles/states: explicit labels; toggles expose `aria-pressed`. @@ -95,6 +110,7 @@ Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. La --- ## 6) Data, permissions & contracts + - **Entities**: Name 1..N Blog; Post 1..1 Blog (immutable link). (`nameId`,`blogHandle`) unique. - **Roles**: Owner (all), Editor (edit any, manage drafts), Author (own posts). Deny by default. - **RTK Query** is the only remote path; queries/mutations define cache keys by blog. @@ -104,6 +120,7 @@ Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. La --- ## 7) Testing standards + - Register matchers once in `tests/setup.ts`. Use **accessible queries** (`getByRole`, `getByLabelText`). - Unit: selectors, reducers, guards (`can()`), formatters, sanitizers. - Component: editor toolbar & autosave, blog switch scoping, lists (virtualized), dialogs focus. @@ -114,6 +131,7 @@ Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. La --- ## 8) Performance & resilience + - Virtualize long lists; memoize high-churn components. - Abortable fetch with timeouts; retry/backoff for GETs; idempotency keys for create/update. - Perceived speed: skeletons & optimistic UI (draft saves); defer non-critical work to idle. @@ -122,11 +140,13 @@ Config parity across dev/test/build (aliases, JSX, TS). Alias: **@ → src**. La --- ## 9) TypeScript posture + Strict mode, zero `any` in public props; no `@ts-nocheck`. Prefer discriminated unions for async/status shapes. Treat red squiggles as **must fix** before PR. --- ## 10) Security & content safety + - Sanitize editor output **on save and render** (DOMPurify allowlist). Never render untrusted HTML directly. - Validate inputs at boundaries; never trust client-visible state for authorization (server checks required). - Error boundaries: route-level and editor-level with friendly fallback + “copy details” action. @@ -135,16 +155,19 @@ Strict mode, zero `any` in public props; no `@ts-nocheck`. Prefer discriminated --- ## 11) Internationalization & theming + Centralize strings; use Intl for dates/numbers; prep RTL-safe layouts. Theme tokens (color/spacing/typography) audited for WCAG AA; respect `prefers-color-scheme`. --- ## 12) Docs & decision hygiene + Docs live in `docs/`. Keep: **ARCHITECTURE**, **TESTING**, **ACCESSIBILITY**, **SECURITY**, **USER_JOURNEYS**, **GLOSSARY**, **RISKS**, **ROADMAP_DEPENDENCIES**, **DECISIONS/** (short ADRs). Update docs when behavior or contracts change. --- ## 13) Delivery & workflow (assistants) + - Prepare files locally; share **download links** (send full files once edits exceed a few lines). Keep a resendable copy. - Prefer thin **vertical slices** (schema→API→state→UI→tests→docs). Include acceptance notes + quick verify. - If assumptions are needed, state them briefly and proceed with a concrete artifact. @@ -152,16 +175,19 @@ Docs live in `docs/`. Keep: **ARCHITECTURE**, **TESTING**, **ACCESSIBILITY**, ** --- ## 14) Single source of truth (constants) + Centralize shared constants and import everywhere: roles, statuses, limits (upload sizes), blog handle rules, debounce durations, routes, a11y labels. **UI, data, and tests** must reference the same values. --- ## 15) Versioning & releases + SemVer with human changelogs; each release includes notes, migration steps, and quick verify. CI gates: typecheck, lint (jsx‑a11y), unit/component tests, axe smoke, coverage, build. --- ## 16) Common error → action + - **MUI v4 import fails** → switch to `@mui/material` / `@mui/icons-material`. - **Unexpected HTML execution** → sanitize on save+render; add unit tests with known XSS vectors. - **Data shape mismatch** → validation failed: show friendly error; log redacted details; fix schema or endpoint. @@ -171,7 +197,9 @@ SemVer with human changelogs; each release includes notes, migration steps, and --- ## 17) Checklists + **Before sending anything** + - [ ] No red underlines; strict TS passes. - [ ] Vite build + **Vitest green** (unit & component); axe smoke passes (no criticals). - [ ] Dev/test/build configs match (aliases, JSX, TS options). @@ -181,6 +209,7 @@ SemVer with human changelogs; each release includes notes, migration steps, and - [ ] Editor: autosave status visible; publish path explicit; sanitized output. **Thin vertical delivery** + - [ ] Code + tests + docs move together. - [ ] Include acceptance criteria and quick verification steps. - [ ] If any contract changes, add/adjust a short Decision Record. diff --git a/docs/QUALITY_CHARTER.md b/docs/QUALITY_CHARTER.md index 510cfdc..069cce8 100644 --- a/docs/QUALITY_CHARTER.md +++ b/docs/QUALITY_CHARTER.md @@ -1,17 +1,23 @@ # Q-Blog — Quality Charter (Patch 0) + _Generated 2025-08-16 23:43Z_ ## Purpose + Define the quality bar for Q-Blog 1.0 and the gates that every change must pass. This charter governs scope, acceptance, and sign-off. ## North Star + **Inclusive, predictable publishing** with a resilient, testable UI. ## Protected User Journeys + (See `docs/USER_JOURNEYS.md`; mirrored here for convenience.) -1) Read posts 2) Create post 3) Edit post 4) Manage blogs 5) Collaborate + +1. Read posts 2) Create post 3) Edit post 4) Manage blogs 5) Collaborate ## Service Level Objectives (SLOs) + - **Reliability:** Draft save success ≥ **99.9%** P7D; crash-free sessions ≥ **99%**. - **Accessibility:** Keyboard-only paths pass for protected journeys; **axe** critical violations = **0**. - **Performance:** P75 **LCP < 2.5 s**, **INP < 200 ms**, **CLS < 0.1** on modern desktop; mobile budgets P75 LCP < 3.2 s, INP < 250 ms. @@ -20,6 +26,7 @@ Define the quality bar for Q-Blog 1.0 and the gates that every change must pass. - **Testability:** Coverage gates start **80/70/70** (lines/branches/functions), trend to **90/80/80** by RC. ## Acceptance Gates (per PR) + - Typecheck strict **green**; ESLint (incl. **jsx-a11y**) **green**; Vitest **green**. - No new `any` in public props; no `@ts-nocheck` or blanket disables. - For UI changes: keyboard path verified; aria names/states present; screenshots or notes for key states. @@ -27,16 +34,20 @@ Define the quality bar for Q-Blog 1.0 and the gates that every change must pass. - Docs updated if behavior/contract changes (ADR or section update). ## Measurement & Observability + - **Local/CI:** Web Vitals synthetic where feasible; axe in tests; coverage artifacts exported in CI. - **Runtime (opt-in minimal):** Crash/error codes, action breadcrumbs (no PII), vitals sample. Redaction enforced. ## Roles & Sign-off + - **Quality Owner** (temporary): Project lead (or delegate) confirms gates pass. - **A11y Owner:** Reviews keyboard + axe results for protected journeys. - **Security Owner:** Signs off on sanitization and permission-sensitive changes. ## Out of Scope (1.0) + - Fine-grained per-post ACLs; end-to-end encryption; advanced analytics. ## Change Control + Any change to this charter or to protected journeys requires an ADR (see template) and project sign-off. diff --git a/docs/RELEASE_FLOW.md b/docs/RELEASE_FLOW.md new file mode 100644 index 0000000..315099d --- /dev/null +++ b/docs/RELEASE_FLOW.md @@ -0,0 +1,51 @@ +# Release Flow (Gitea + local scripts) + +This repo uses local scripts to cut releases and create uploads in Gitea. CI stays marketplace‑free and works with your self‑hosted runner. + +## One‑time + +- Ensure env vars are exported in your shell (or place them in `.gitea.env` and `source` it): + ```bash + export GITEA_BASE_URL=https://gitea.qortal.link + export GITEA_TOKEN=... # personal access token with repo scope + export OWNER=greenflame089 + export REPO=q-blog + ``` + +## Phase wrap example (Phase 0 → v0.0.1) + +1. Bump & tag: + +```bash +bash scripts/release/bump-and-tag.sh 0.0.1 "Phase 0 — Orientation & Quality Bar" +``` + +2. Create archives (source only by default; pass `--with-build` later in Phase 1+): + +```bash +bash scripts/release/build-archive.sh +# or with a production build attempt: +# bash scripts/release/build-archive.sh --with-build +``` + +3. Create/Update Gitea release and upload zips: + +```bash +bash scripts/release/create-gitea-release.sh 0.0.1 \ + --title "Phase 0 — v0.0.1" \ + --notes docs/RELEASE_NOTES_v0.0.1.md \ + --branch update +``` + +The script will: + +- create the release if missing (or patch it if it exists), +- upload any `release/*.zip` artifacts, +- print the Release URL on success. + +### Notes + +- We intentionally **skip a TypeScript build by default** in Phase 0 to avoid coupling to app code. Use `--with-build` after the Phase 1 correctness sweeps. +- Artifacts are placed in `release/`: + - `*-src.zip` is always created. + - `*-dist.zip` is created if `dist/` exists (when `--with-build` is used). diff --git a/docs/RELEASE_NOTES_v0.0.1.md b/docs/RELEASE_NOTES_v0.0.1.md index bbabf95..1147528 100644 --- a/docs/RELEASE_NOTES_v0.0.1.md +++ b/docs/RELEASE_NOTES_v0.0.1.md @@ -1,6 +1,7 @@ # Q‑Blog v0.0.1 — Phase 0 wrap **Highlights** + - Self‑hosted CI workflow (no marketplace actions); verified green. - Test harness online (Vitest + RTL + jest‑axe) with a11y smoke test. - ESLint flat config with Phase‑scoped lint; IDE noise reduced. @@ -8,5 +9,6 @@ - Contributor docs and PR templates in place. **Notes** + - App code linting is intentionally limited in Phase 0; sweeps start Phase 1. - No functional app changes intended; safe to merge to upstream at any time. diff --git a/docs/RISKS_ASSUMPTIONS.md b/docs/RISKS_ASSUMPTIONS.md index d397dff..ab99490 100644 --- a/docs/RISKS_ASSUMPTIONS.md +++ b/docs/RISKS_ASSUMPTIONS.md @@ -1,12 +1,13 @@ # Q‑Blog — Risks, Assumptions & Mitigations + _Generated 2025-08-16 23:27Z_ -| ID | Area | Risk/Assumption | Phase | Impact | Mitigation | -|---:|------|------------------|:-----:|--------|------------| -| R1 | Data | Legacy content migration to default blogs may fail on malformed records | 6 | High | Idempotent migrator, dry run, backup + rollback notes | -| R2 | Editor | Rich‑text sanitization strips needed formatting | 10 | Medium | Allowlist tuned with tests; sample content goldens | -| R3 | A11y | Keyboard traps in complex modals/popovers | 4–5 | Medium | Component audits; focus tests; Esc/restore policies | -| R4 | Collab | Permission gaps lead to privilege escalation | 7 | High | Server‑side checks; matrix tests; deny‑by‑default | -| R5 | Perf | Large lists regress INP/LCP | 8 | Medium | Virtualization, prefetch, memoization; vitals budgets | -| A1 | API | We can evolve/extend server contracts | 0 | — | If not, draft shims and versioned adapters | -| A2 | Tooling | CI runners can execute headless browsers for axe/e2e | 2 | — | If flaky, move some checks to nightly | +| ID | Area | Risk/Assumption | Phase | Impact | Mitigation | +| --: | ------- | ----------------------------------------------------------------------- | :---: | ------ | ----------------------------------------------------- | +| R1 | Data | Legacy content migration to default blogs may fail on malformed records | 6 | High | Idempotent migrator, dry run, backup + rollback notes | +| R2 | Editor | Rich‑text sanitization strips needed formatting | 10 | Medium | Allowlist tuned with tests; sample content goldens | +| R3 | A11y | Keyboard traps in complex modals/popovers | 4–5 | Medium | Component audits; focus tests; Esc/restore policies | +| R4 | Collab | Permission gaps lead to privilege escalation | 7 | High | Server‑side checks; matrix tests; deny‑by‑default | +| R5 | Perf | Large lists regress INP/LCP | 8 | Medium | Virtualization, prefetch, memoization; vitals budgets | +| A1 | API | We can evolve/extend server contracts | 0 | — | If not, draft shims and versioned adapters | +| A2 | Tooling | CI runners can execute headless browsers for axe/e2e | 2 | — | If flaky, move some checks to nightly | diff --git a/docs/ROADMAP_DEPENDENCIES.md b/docs/ROADMAP_DEPENDENCIES.md index 6827db3..0b87e7d 100644 --- a/docs/ROADMAP_DEPENDENCIES.md +++ b/docs/ROADMAP_DEPENDENCIES.md @@ -1,7 +1,9 @@ # Q‑Blog — Roadmap Dependencies & Milestones + _Generated 2025-08-16 23:27Z_ ## Dependency Graph (high level) + - Phase 2 depends on Phase 1 docs (testing/a11y standards referenced). - Phase 3 depends on Phase 2 harness (to verify fixes). - Phase 4 (a11y) builds on Phase 3 stability. @@ -10,6 +12,7 @@ _Generated 2025-08-16 23:27Z_ - Phase 10 (release) depends on all prior phases plus observability wiring. ## Milestone Gates + - **M1 — Baseline Ready (P0–P2):** Docs present; typecheck/lint/tests green; coverage report generated. - **M2 — Stable & Accessible (P3–P4):** No crashers; keyboard‑only journeys pass; axe critical=0. - **M3 — UX Solid (P5):** Consistent patterns, no dead ends; editor ergonomics audited. @@ -17,3 +20,9 @@ _Generated 2025-08-16 23:27Z_ - **M5 — Shared Blogs (P7):** Role matrix enforced; invites work. - **M6 — Resilient & Performant (P8–P9):** Vitals within budget; themes/i18n consistent. - **M7 — Release Ready (P10):** Telemetry, security checks, notes/guides done. + +--- + +## Audit Notes (v0.0.1) + +- ⚠️ Note: Release automation script had issues with curl args; roadmap should note fixing release tooling. diff --git a/docs/SECURITY_PRIVACY_POSTURE.md b/docs/SECURITY_PRIVACY_POSTURE.md index 9fe67b4..11572d3 100644 --- a/docs/SECURITY_PRIVACY_POSTURE.md +++ b/docs/SECURITY_PRIVACY_POSTURE.md @@ -1,19 +1,23 @@ # Q‑Blog — Security & Privacy Posture (1.0) + _Generated 2025-08-16 23:27Z_ ## Principles -- **Least privilege** — Roles restrict actions; tokens scoped; client never authoritative. -- **Sanitize everywhere** — Rich text sanitized on save and render (allowlist). -- **Fail safe** — On doubt, deny writes; surface clear errors with next actions. -- **Minimal telemetry** — Only crash/quality signals; no PII; user‑visible policy. + +- **Least privilege** — Roles restrict actions; tokens scoped; client never authoritative. +- **Sanitize everywhere** — Rich text sanitized on save and render (allowlist). +- **Fail safe** — On doubt, deny writes; surface clear errors with next actions. +- **Minimal telemetry** — Only crash/quality signals; no PII; user‑visible policy. - **Defense in depth** — CSP, input validation, dependency hygiene, error boundaries. ## Non‑Goals (1.0) -- End‑to‑end encryption for content. + +- End‑to‑end encryption for content. - Fine‑grained per‑post ACLs (roles are per blog). ## Checklist (Dev) -- Inputs validated client‑side; re‑validated server‑side. -- All writes include role checks and revision/ETag for concurrency. -- Sanitization unit tests cover common XSS vectors. + +- Inputs validated client‑side; re‑validated server‑side. +- All writes include role checks and revision/ETag for concurrency. +- Sanitization unit tests cover common XSS vectors. - Dependencies audited; pinned versions for determinism. diff --git a/docs/TESTING.md b/docs/TESTING.md index 7bcb886..8bd87bf 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,17 +1,35 @@ # Testing — Q-Blog (Phase 0) ## Local + ```bash pnpm typecheck pnpm lint pnpm test:run ``` + MSW is enabled in tests; unhandled requests are bypassed. ## CI (Gitea) + - Workflow: `.gitea/workflows/ci-no-marketplace.yml` - Matrix: single ubuntu, Node 20.16.0 via tarball - Steps: typecheck → lint → vitest (coverage) → build (if Vite config exists) ## a11y smoke + `tests/axe-smoke.test.tsx` runs jest-axe; add more targeted checks as UI grows. + +--- + +## Audit Notes (v0.0.1) + +- ⚠️ Note: MSW/jest-axe not detected in test sources; test stack may be incomplete compared to documentation. +- ⚠️ Note: Ensure axe accessibility smoke tests and MSW handlers are implemented or update this doc. + +## Test Scenarios — Multi‑Blog + +- Header: 0 vs 1 vs N blogs; menu opens/closes; selection navigates and sets active blog. +- Name root redirect: single blog → posts; else → `/{{name}}/blogs`; polite loading text in `aria-live`. +- UserBlogs page: list/empty states; self vs public controls. +- A11y smoke (axe) on header + UserBlogs (when available). diff --git a/docs/USER_JOURNEYS.md b/docs/USER_JOURNEYS.md index ef402ab..234c91e 100644 --- a/docs/USER_JOURNEYS.md +++ b/docs/USER_JOURNEYS.md @@ -1,13 +1,16 @@ # Q‑Blog 1.0 — Personas & Key Journeys + _Generated 2025-08-16 23:27Z_ ## Personas + - **Solo Writer** — Publishes articles and drafts privately before release. - **Team Owner** — Creates a shared blog and invites collaborators. - **Invited Author** — Writes posts on a shared blog; cannot manage members. - **Reader** — Browses and discovers posts; expects fast loads and clear navigation. ## Protected Journeys (1.0) + 1. **Read posts** — Navigate lists → open an article; keyboard and screen reader friendly. 2. **Create post** — Draft, autosave, preview, publish; clear feedback on errors. 3. **Edit post** — Update content; attribution and revision increase predictability. @@ -15,7 +18,32 @@ _Generated 2025-08-16 23:27Z_ 5. **Collaborate** — Invite member; invited author creates/edits within role. ## Acceptance Hints per Journey + - **Read:** Landmarks in place, heading hierarchy valid, link text descriptive, images have alt. - **Create/Edit:** Toolbar states deterministic; autosave status surfaced via live region; error can be retried. - **Manage:** Blog switch affects all scoped views; URLs stable and shareable. - **Collaborate:** Role gating enforced in UI and API; forbidden paths never succeed. + +--- + +## Audit Notes (v0.0.1) + +- ⚠️ Note: PostEditor autosave + live region announcements need verification in code; docs may overstate completeness. + +## Journeys — Multi‑Blog + +### 0 blogs (self) + +- Header shows **Create Blog**. `/{{name}}` → `/{{name}}/blogs` with empty state + CTA. + +### 1 blog + +- `/{{name}}` redirects to `/{{name}}/{{blog}}/posts`. Header shows **My Blogs** (1 item + Create). + +### N blogs + +- Header dropdown lists all blogs (active ✓). `/{{name}}/blogs` lists them with Edit/Create when self. + +### Public view + +- Viewing someone else’s `/{{name}}/blogs` shows a read‑only list; no edit/create controls. diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index f4aa499..9e5a587 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -8,6 +8,7 @@ We map **project phases** to **SemVer**: - ... Rules: + - Use **SemVer**; phases bump the **minor** (except Phase 0 uses `0.0.1`). - Patch bumps (`0.X.Y`) are reserved for small fixes within a phase. - Tag releases and include short notes in `docs/RELEASE_NOTES_vX.Y.Z.md`. diff --git a/docs/VISION_PRFAQ.md b/docs/VISION_PRFAQ.md index cb27ca0..9cd129a 100644 --- a/docs/VISION_PRFAQ.md +++ b/docs/VISION_PRFAQ.md @@ -1,4 +1,5 @@ # Q‑Blog 1.0 — PRFAQ + _Generated 2025-08-16 23:27Z_ ## Press Release (Narrative) @@ -6,6 +7,7 @@ _Generated 2025-08-16 23:27Z_ Today we announce **Q‑Blog 1.0**, a modern, accessible writing workspace that makes it effortless to publish under multiple blogs with a single name, and to collaborate safely through **Shared Blogs**. Q‑Blog pairs a focused editor with strong correctness, accessibility, and testability so writers can publish confidently, teams can coordinate, and readers can trust what they see. Key highlights: + - **Multiple blogs per name** — Organize topics cleanly without creating new accounts. - **Shared blogs** — Invite editors and authors with role‑based permissions. - **Inclusive by design** — Keyboard‑ready, screen‑reader friendly, respectful of motion/contrast preferences. diff --git a/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md b/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md new file mode 100644 index 0000000..ed82fda --- /dev/null +++ b/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md @@ -0,0 +1,33 @@ +# Multiple Blogs per Name — Overview (Plan A: Smart Redirect) + +_Generated 2025-08-21_ + +## Goal + +Allow a single **Name** to own **multiple Blogs**, while preserving existing blog-scoped routes and minimizing churn. Replace the single “My Blog” button with a **“My Blogs” dropdown** and introduce a new **User → Blogs** list page. When a Name has exactly one blog, visiting `/{name}` **auto-redirects** to that blog. + +## Scope + +- **In**: Header/Nav blog access, Name landing behavior, simple blog list page, light routing changes, a11y behavior. +- **Out**: Post editor behavior, post list virtualization, roles/permissions model changes, server protocol changes (unless list-by-name is missing). + +## High-level UX + +- Header/Nav: + - 0 blogs → Button shows **Create Blog** (primary action). Dropdown disabled/hidden. + - ≥1 blog → Button shows **My Blogs**. Dropdown lists each blog (title + id). Bottom row = **Create new blog**. +- Name route: + - `/{name}`: + - If blog count === 1 → redirect to `/{name}/{blog}/posts` (existing default route). + - If blog count !== 1 → show **User Blogs** page (`/{name}/blogs`). +- User Blogs page: + - Public, lists all blogs for the Name. If viewer is same user, show **Edit** per blog and **Create Blog** CTA. +- A11y: + - Dropdown uses proper menu roles, keyboard navigation, and focus return. + - User Blogs page has a single **H1**, semantic list, clear button labels. + +## Benefits + +- Aligns with entity contract (**Name 1..N Blog**). +- Minimizes refactors; relies on existing blog-scoped routing and components. +- Backward-compatible for deep links. diff --git a/docs/features/TECH_IMPL_MULTIBLOG.md b/docs/features/TECH_IMPL_MULTIBLOG.md new file mode 100644 index 0000000..a5fe4b3 --- /dev/null +++ b/docs/features/TECH_IMPL_MULTIBLOG.md @@ -0,0 +1,215 @@ +# Implementation Guide — Multiple Blogs per Name (Plan A) + +_Generated 2025-08-21_ + +> **AAA mode:** Actionable, artifact-oriented, and accessible. This guide defines exact files to create or modify, contracts, a11y rules, redirects, and tests. + +--- + +## Contracts & Routing + +### Entities (unchanged) + +- **Name**: can own multiple **Blog** records. +- **Blog**: immutable link to its **Name**; addressed by `blogHandle` (aka blog id). +- **Post**: 1..1 Blog (unchanged). + +### Routes (final form) + +- `/{name}` + - If **hasExactlyOneBlog(name)** → redirect (replace history) to `/{name}/{blog}/posts` (or current posts index). + - Else → route to **User Blogs** page (`/{name}/blogs`). +- `/{name}/blogs` → **User Blogs** page (new). +- `/{name}/{blog}/*` → **existing blog-scoped routes** (unchanged). + +**Assumption:** Existing default blog landing is `/{name}/{blog}/posts`. Adjust if your index differs. + +--- + +## UI Changes + +### 1) Header/Nav — “My Blogs” dropdown + +**File:** `src/components/HeaderNav.tsx` (or equivalent container for auth controls) + +- Replace single “My Blog”/“Create Blog” button with a **dropdown** (same pattern as the Authenticate/Names dropdown). +- **State inputs:** + - `currentUser?: NameRef` + - `userBlogs: BlogSummary[]` — list of blogs for current user (title + `blogHandle`). + - `onCreateBlog: () => void` + - `onSelectBlog: (blog: BlogRef) => void` → navigates to `/{name}/{blog}/posts` and updates active blog in store. +- **Label logic:** + - `userBlogs.length === 0` → show **Create Blog** (primary button), hide dropdown caret. + - `userBlogs.length >= 1` → show **My Blogs** with caret; open menu on click. +- **Menu content (for ≥1):** + - One item per blog: `Title · blogHandle` (truncate cautiously). Current active shows a ✓. + - Divider + - **Create new blog** (button-like menu item). +- **A11y:** + - Use proper `role="menu"`/`menuitem"`, `aria-haspopup="menu"`, `aria-expanded`, `aria-controls`. + - Focus returns to the trigger on close. ESC closes. + - Ensure high-contrast tokens; respect `prefers-reduced-motion` (no aggressive animation). + +> **Note:** If a reusable `BlogSwitcher` exists, consider refactoring the above as a `UserBlogSwitcher` variant that consumes the current user’s blogs. + +### 2) User Blogs page (new) + +**File:** `src/pages/UserBlogs.tsx` (or `src/routes/UserBlogs.tsx`) + +- **H1:** “{name}’s Blogs” +- **List:** Card per blog with title, `blogHandle`, optional description/meta. +- **Actions:** + - View → pushes route `/{name}/{blog}/posts`. + - If `currentUser.nameId === params.nameId` → buttons: **Edit**, **Create Blog** (page-level CTA). +- **Empty states:** + - Self (0 blogs): “You don’t have any blogs yet.” + prominent **Create Blog**. + - Other user (0 blogs): “No public blogs yet.” + +**A11y:** One H1, list semantics, buttons with explicit labels (include blog title in `aria-label`). + +--- + +## Store & Data + +### Selectors + +- `selectCurrentUser()` → NameRef / undefined. +- `selectUserBlogs(nameId)` → BlogSummary[] (RTK Query / slice selector). +- `selectActiveBlog()` → BlogRef | null (unchanged). + +### RTK Query (if missing) + +- **Endpoint:** `listBlogsByName(nameId)` + - Cache key: `['blogs', nameId]`. + - Normalized response: `BlogSummary[]` (include `blogHandle`, `title`). +- Consumers: + - Header dropdown fetches `listBlogsByName(currentUser.nameId)` when authenticated. + - `UserBlogs` page fetches `listBlogsByName(params.nameId)`. + +> If an endpoint already exists, reuse it. Otherwise add it in `src/store/api.ts` with blog-scoped cache keys documented in Q‑Blog. + +--- + +## Routing Changes + +**File:** `src/router.tsx` (or where routes are defined) + +1. Add route for `/{name}/blogs` → `UserBlogs` component. +2. Update `/{name}` route element to perform **smart redirect**: + - On mount: + - Fetch `blogs = listBlogsByName(params.nameId)`. + - If `blogs.length === 1` → `navigate('/{name}/' + blogs[0].blogHandle + '/posts', { replace: true })`. + - Else → `navigate('/{name}/blogs', { replace: true })`. + - **Loading state:** show a lightweight spinner/skeleton with polite live region (“Loading blogs…”). + +> Keep deep links to `/{name}/{blog}/…` working as today. + +--- + +## Permissions + +- Editing controls on `UserBlogs` are visible only when viewing your own Name (`currentUser.nameId === params.nameId`). +- All blog/post routes retain existing guards (no change). + +--- + +## Edge Cases & Behavior + +- **0 blogs (self):** Header shows **Create Blog**. `/{name}` goes to `/blogs` with the “create” empty state. +- **1 blog:** Header shows **My Blogs**; menu has one item + “Create new”. Name root redirects to that blog. +- **N blogs (large):** Menu scrolls; consider collapsed ids or optional search later (not part of this change). +- **Anonymous viewer:** Can see another user’s `/{name}/blogs`, no edit/create controls. +- **Broken blogHandle:** If a listed blog 404s, show error boundary as today; menu item should not break the app. + +--- + +## Accessibility Requirements + +- Dropdown follows WAI-ARIA menu pattern; keyboard: ArrowUp/Down, Enter, ESC. +- Focus management: opening menu moves focus to first item; closing returns to trigger. +- Single H1 on `UserBlogs`. Buttons have informative `aria-label`s (“View blog Alpha (alpha)”). +- Live region only for the brief loading text on Name root redirect; no chatty updates. + +--- + +## Testing Plan (Vitest + RTL + user-event + MSW + jest-axe) + +> If MSW/jest-axe aren’t present yet, include unit tests now and add MSW/a11y smoke later. Aim to cover behavior, not visuals. + +1. **Header/Menu** + + - 0 blogs → shows “Create Blog” button, no dropdown. + - 1+ blogs → shows “My Blogs”; opens menu; selecting a blog navigates to proper route and sets active blog. + - Keyboard navigation: items reachable via arrows; ESC closes; focus returns to trigger. + +2. **Name Root Redirect** + + - 1 blog → navigates to blog posts route (replace history). + - 0 or >1 → navigates to `/{name}/blogs`. + - Loading state renders politely with an `aria-live="polite"` region text. + +3. **UserBlogs Page** + + - Renders list matching `listBlogsByName` response. + - Self-view shows Edit + Create; other-view hides them. + - Empty states render correct CTAs/messages. + +4. **A11y/axe Smoke** + - Header and `UserBlogs` produce no critical axe violations. + +--- + +## Step-by-step Changes (files) + +1. **Create:** `src/pages/UserBlogs.tsx` + + - Functional component rendering list of blogs by `params.nameId`. + - Uses RTK Query selector/hook to fetch `listBlogsByName`. + - Buttons: View, (conditional) Edit; page-level Create when self. + +2. **Modify:** `src/router.tsx` + + - Add `/{name}/blogs` route. + - Update `/{name}` route element to perform smart redirect based on fetched blog count. + +3. **Modify:** `src/components/HeaderNav.tsx` + + - Replace single button with dropdown: + - When 0 blogs → primary **Create Blog** button. + - When ≥1 blogs → **My Blogs** dropdown listing blog items + “Create new blog”. + - Wire `onSelectBlog` to navigate and set active blog; wire `onCreateBlog` to current flow (dialog/route). + +4. **Add (if missing):** `src/store/api.ts` + + - Endpoint: `listBlogsByName(nameId)` with `providesTags: (nameId) => ['blogs', nameId]` or equivalent. + - Types: `BlogSummary` (title, blogHandle). + +5. **Tests:** + + - `tests/routes/UserBlogs.test.tsx` + - `tests/components/HeaderNav.multiblog.test.tsx` + - `tests/routes/NameRootRedirect.test.tsx` + +6. **Docs:** + - Link this guide from `docs/USER_JOURNEYS.md` and `docs/ARCHITECTURE.md` after merge. + +--- + +## Acceptance Criteria + +- Header shows **Create Blog** when user has 0 blogs; shows **My Blogs** dropdown when ≥1. +- Selecting a blog from the dropdown navigates to `/{name}/{blog}/posts` and marks it active. +- Visiting `/{name}`: + - If exactly one blog exists → silently lands on that blog’s posts list. + - Else → shows `/{name}/blogs` page listing all blogs for that Name. +- `UserBlogs` page renders with a single H1, accessible list, and correct empty states. +- Unit/component tests pass; a11y smoke has no critical issues. +- No regressions to existing blog-scoped routes; deep links remain valid. + +--- + +## Rollout Notes + +- No data migrations; purely UI/routing. +- If telemetry exists, consider logging menu usage and redirect outcomes to validate adoption. +- Update release notes after merge. diff --git a/eslint.config.mjs b/eslint.config.mjs index f65cebc..5aefeda 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,11 +1,11 @@ // eslint.config.mjs — Phase 0 env-scoped ignores -import js from '@eslint/js' -import tseslint from 'typescript-eslint' -import reactHooks from 'eslint-plugin-react-hooks' -import jsxA11y from 'eslint-plugin-jsx-a11y' +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import reactHooks from 'eslint-plugin-react-hooks'; +import jsxA11y from 'eslint-plugin-jsx-a11y'; // Toggle with LINT_SCOPE=phase0|full (default phase0 during Phase 0) -const SCOPE = process.env.LINT_SCOPE || 'phase0' +const SCOPE = process.env.LINT_SCOPE || 'phase0'; const baseIgnores = [ 'node_modules/**', 'dist/**', @@ -15,9 +15,9 @@ const baseIgnores = [ 'eslint.config.mjs', 'vite.config.*', 'vitest.config.*', -] -const phase0Extra = ['src/**'] // exclude app during Phase 0 sweeps -const always = ['**/*.d.ts'] // we don’t lint type decl files +]; +const phase0Extra = ['src/**']; // exclude app during Phase 0 sweeps +const always = ['**/*.d.ts']; // we don’t lint type decl files export default [ { ignores: [...baseIgnores, ...(SCOPE === 'phase0' ? phase0Extra : []), ...always] }, @@ -41,4 +41,4 @@ export default [ 'jsx-a11y/label-has-associated-control': 'error', }, }, -] +]; diff --git a/issues/001-Adopt_Quality_Charter_(sign-off).md b/issues/001-Adopt_Quality_Charter_(sign-off).md index 9ef74c4..8613272 100644 --- a/issues/001-Adopt_Quality_Charter_(sign-off).md +++ b/issues/001-Adopt_Quality_Charter_(sign-off).md @@ -1,19 +1,23 @@ # Adopt Quality Charter (sign-off) ## Goal + Ratify the Quality Charter and set acceptance gates for Q-Blog 1.0. ## Tasks + - Review reliability, a11y, perf, security, and testability targets - Adjust thresholds if needed - Record sign-off and owners (Quality, A11y, Security) ## Acceptance + - Charter updated (if needed) and marked signed - Owners assigned and documented - See docs/QUALITY_CHARTER.md. ## Labels + Type: docs · Area: docs · Priority: P0 · Size: S **Labels:** docs, P0, S, docs-area diff --git a/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md b/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md index b6dbff2..693739b 100644 --- a/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md +++ b/issues/002-Finalize_Foundation_Docs_(Architecture,_Testing,_Accessibility,_Security).md @@ -1,9 +1,11 @@ # Finalize Foundation Docs (Architecture, Testing, Accessibility, Security) ## Goal + Publish concise, source-of-truth docs to guide development. ## Includes + - ARCHITECTURE (UI ↔ state ↔ data, Name/Blog/Post model) - TESTING (pyramid, coverage, fixtures/mocks, MSW policy) - ACCESSIBILITY (landmarks, focus, live regions, motion/contrast) @@ -11,11 +13,13 @@ Publish concise, source-of-truth docs to guide development. - USER_JOURNEYS & GLOSSARY (personas, invariants) ## Acceptance + - Docs present in `docs/`, linked from README - Each doc has a short "How to verify" section - See docs/QUALITY_CHARTER.md. ## Labels + Type: docs · Area: docs · Priority: P1 · Size: M **Labels:** docs, P1, M, docs-area diff --git a/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md b/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md index 1a66f96..02f623e 100644 --- a/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md +++ b/issues/003-Stand_up_Test_Harness_(Vitest_+_RTL_+_MSW_+_jest-axe).md @@ -1,20 +1,24 @@ # Stand up Test Harness (Vitest + RTL + MSW + jest-axe) ## Goal + Enable fast, realistic tests with accessibility checks. ## Tasks + - Configure Vitest (jsdom), alias @→src, coverage thresholds - Global test setup with RTL, user-event, jest-dom, jest-axe - MSW server for success/error/invalid payloads - Seed tests: app smoke, editor minimal, a11y smoke ## Acceptance + - `pnpm test` runs green; coverage report produced - At least 3 smoke tests and 1 a11y test in place - See docs/QUALITY_CHARTER.md. ## Labels + Type: test · Area: tests · Priority: P0 · Size: M **Labels:** test, P0, M, tests diff --git a/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md b/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md index adb34f4..2175fdd 100644 --- a/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md +++ b/issues/004-ESLint_Flat_Config_+_jsx-a11y_Baseline.md @@ -1,19 +1,23 @@ # ESLint Flat Config + jsx-a11y Baseline ## Goal + Catch accessibility and code-quality issues early. ## Tasks + - Add flat ESLint config with TypeScript/React/jsx-a11y - Prettier integration; import/order; testing-library plugin - Fix initial lint errors (no blanket disables) ## Acceptance + - `pnpm lint` green on clean checkout - No `@ts-nocheck`; no new `any` in public props - See docs/QUALITY_CHARTER.md. ## Labels + Type: chore · Area: build · Priority: P0 · Size: S **Labels:** chore, P0, S, build diff --git a/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md b/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md index c9aa07c..a6b49af 100644 --- a/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md +++ b/issues/005-Correctness_Sweep_I_—_MUI_v4_imports_&_ts-nocheck_removal.md @@ -1,19 +1,23 @@ # Correctness Sweep I — MUI v4 imports & ts-nocheck removal ## Goal + Remove known correctness hazards blocking development. ## Targets + - Replace `@material-ui/*` imports with `@mui/*` - Remove `@ts-nocheck` in editor and app; add minimal types - Ensure app still builds/boots after changes ## Acceptance + - No remaining v4 imports; no ts-nocheck in core paths - Smoke tests green; manual run shows editor screen - See docs/QUALITY_CHARTER.md. ## Labels + Type: fix · Area: editor · Priority: P0 · Size: M **Labels:** fix, P0, M, editor diff --git a/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md b/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md index 68bde5b..6227c28 100644 --- a/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md +++ b/issues/006-Correctness_Sweep_II_—_Type_hygiene_in_reducers-selectors.md @@ -1,19 +1,23 @@ # Correctness Sweep II — Type hygiene in reducers/selectors ## Goal + Reduce `any` usage and adopt consistent async state shapes. ## Tasks + - Replace common `any` with discriminated unions/interfaces - Standardize async lifecycles (idle/loading/success/error) - Normalize error object shape (code/message/recoverable) ## Acceptance + - Reducer/selector tests added - No new `any` in reducers/selectors; stricter types compile - See docs/QUALITY_CHARTER.md. ## Labels + Type: fix · Area: state · Priority: P1 · Size: M **Labels:** fix, P1, M, state diff --git a/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md b/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md index 2bed6bd..eecfb41 100644 --- a/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md +++ b/issues/007-Correctness_Sweep_III_—_Empty-Loading-Error_states_standardization.md @@ -1,19 +1,23 @@ # Correctness Sweep III — Empty/Loading/Error states standardization ## Goal + Users never experience a “mystery fail.” ## Tasks + - Implement consistent empty/loading/error components - Wire to data flows across list/detail/editor - Ensure retry paths for transient failures ## Acceptance + - Error surfaces include a recovery action - Smoke tests cover all three states on a list screen - See docs/QUALITY_CHARTER.md. ## Labels + Type: fix · Area: lists · Priority: P1 · Size: M **Labels:** fix, P1, M, lists diff --git a/issues/adopt-quality-charter--sign-off-.md b/issues/adopt-quality-charter--sign-off-.md index 9ef74c4..8613272 100644 --- a/issues/adopt-quality-charter--sign-off-.md +++ b/issues/adopt-quality-charter--sign-off-.md @@ -1,19 +1,23 @@ # Adopt Quality Charter (sign-off) ## Goal + Ratify the Quality Charter and set acceptance gates for Q-Blog 1.0. ## Tasks + - Review reliability, a11y, perf, security, and testability targets - Adjust thresholds if needed - Record sign-off and owners (Quality, A11y, Security) ## Acceptance + - Charter updated (if needed) and marked signed - Owners assigned and documented - See docs/QUALITY_CHARTER.md. ## Labels + Type: docs · Area: docs · Priority: P0 · Size: S **Labels:** docs, P0, S, docs-area diff --git a/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md b/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md index c9aa07c..a6b49af 100644 --- a/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md +++ b/issues/correctness-sweep-i---mui-v4-imports---ts-nocheck-removal.md @@ -1,19 +1,23 @@ # Correctness Sweep I — MUI v4 imports & ts-nocheck removal ## Goal + Remove known correctness hazards blocking development. ## Targets + - Replace `@material-ui/*` imports with `@mui/*` - Remove `@ts-nocheck` in editor and app; add minimal types - Ensure app still builds/boots after changes ## Acceptance + - No remaining v4 imports; no ts-nocheck in core paths - Smoke tests green; manual run shows editor screen - See docs/QUALITY_CHARTER.md. ## Labels + Type: fix · Area: editor · Priority: P0 · Size: M **Labels:** fix, P0, M, editor diff --git a/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md b/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md index 68bde5b..6227c28 100644 --- a/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md +++ b/issues/correctness-sweep-ii---type-hygiene-in-reducers-selectors.md @@ -1,19 +1,23 @@ # Correctness Sweep II — Type hygiene in reducers/selectors ## Goal + Reduce `any` usage and adopt consistent async state shapes. ## Tasks + - Replace common `any` with discriminated unions/interfaces - Standardize async lifecycles (idle/loading/success/error) - Normalize error object shape (code/message/recoverable) ## Acceptance + - Reducer/selector tests added - No new `any` in reducers/selectors; stricter types compile - See docs/QUALITY_CHARTER.md. ## Labels + Type: fix · Area: state · Priority: P1 · Size: M **Labels:** fix, P1, M, state diff --git a/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md b/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md index 2bed6bd..eecfb41 100644 --- a/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md +++ b/issues/correctness-sweep-iii---empty-loading-error-states-standardization.md @@ -1,19 +1,23 @@ # Correctness Sweep III — Empty/Loading/Error states standardization ## Goal + Users never experience a “mystery fail.” ## Tasks + - Implement consistent empty/loading/error components - Wire to data flows across list/detail/editor - Ensure retry paths for transient failures ## Acceptance + - Error surfaces include a recovery action - Smoke tests cover all three states on a list screen - See docs/QUALITY_CHARTER.md. ## Labels + Type: fix · Area: lists · Priority: P1 · Size: M **Labels:** fix, P1, M, lists diff --git a/issues/eslint-flat-config---jsx-a11y-baseline.md b/issues/eslint-flat-config---jsx-a11y-baseline.md index adb34f4..2175fdd 100644 --- a/issues/eslint-flat-config---jsx-a11y-baseline.md +++ b/issues/eslint-flat-config---jsx-a11y-baseline.md @@ -1,19 +1,23 @@ # ESLint Flat Config + jsx-a11y Baseline ## Goal + Catch accessibility and code-quality issues early. ## Tasks + - Add flat ESLint config with TypeScript/React/jsx-a11y - Prettier integration; import/order; testing-library plugin - Fix initial lint errors (no blanket disables) ## Acceptance + - `pnpm lint` green on clean checkout - No `@ts-nocheck`; no new `any` in public props - See docs/QUALITY_CHARTER.md. ## Labels + Type: chore · Area: build · Priority: P0 · Size: S **Labels:** chore, P0, S, build diff --git a/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md b/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md index b6dbff2..693739b 100644 --- a/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md +++ b/issues/finalize-foundation-docs--architecture--testing--accessibility--security-.md @@ -1,9 +1,11 @@ # Finalize Foundation Docs (Architecture, Testing, Accessibility, Security) ## Goal + Publish concise, source-of-truth docs to guide development. ## Includes + - ARCHITECTURE (UI ↔ state ↔ data, Name/Blog/Post model) - TESTING (pyramid, coverage, fixtures/mocks, MSW policy) - ACCESSIBILITY (landmarks, focus, live regions, motion/contrast) @@ -11,11 +13,13 @@ Publish concise, source-of-truth docs to guide development. - USER_JOURNEYS & GLOSSARY (personas, invariants) ## Acceptance + - Docs present in `docs/`, linked from README - Each doc has a short "How to verify" section - See docs/QUALITY_CHARTER.md. ## Labels + Type: docs · Area: docs · Priority: P1 · Size: M **Labels:** docs, P1, M, docs-area diff --git a/issues/issues_patch0_phase1-3.json b/issues/issues_patch0_phase1-3.json index af266bc..9490988 100644 --- a/issues/issues_patch0_phase1-3.json +++ b/issues/issues_patch0_phase1-3.json @@ -3,72 +3,37 @@ { "title": "Adopt Quality Charter (sign-off)", "body": "## Goal\nRatify the Quality Charter and set acceptance gates for Q-Blog 1.0.\n\n## Tasks\n- Review reliability, a11y, perf, security, and testability targets\n- Adjust thresholds if needed\n- Record sign-off and owners (Quality, A11y, Security)\n\n## Acceptance\n- Charter updated (if needed) and marked signed\n- Owners assigned and documented\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: docs \u00b7 Area: docs \u00b7 Priority: P0 \u00b7 Size: S", - "labels": [ - "docs", - "P0", - "S", - "docs-area" - ] + "labels": ["docs", "P0", "S", "docs-area"] }, { "title": "Finalize Foundation Docs (Architecture, Testing, Accessibility, Security)", "body": "## Goal\nPublish concise, source-of-truth docs to guide development.\n\n## Includes\n- ARCHITECTURE (UI \u2194 state \u2194 data, Name/Blog/Post model)\n- TESTING (pyramid, coverage, fixtures/mocks, MSW policy)\n- ACCESSIBILITY (landmarks, focus, live regions, motion/contrast)\n- SECURITY & PRIVACY (sanitization, permission checks, CSP posture)\n- USER_JOURNEYS & GLOSSARY (personas, invariants)\n\n## Acceptance\n- Docs present in `docs/`, linked from README\n- Each doc has a short \"How to verify\" section\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: docs \u00b7 Area: docs \u00b7 Priority: P1 \u00b7 Size: M", - "labels": [ - "docs", - "P1", - "M", - "docs-area" - ] + "labels": ["docs", "P1", "M", "docs-area"] }, { "title": "Stand up Test Harness (Vitest + RTL + MSW + jest-axe)", "body": "## Goal\nEnable fast, realistic tests with accessibility checks.\n\n## Tasks\n- Configure Vitest (jsdom), alias @\u2192src, coverage thresholds\n- Global test setup with RTL, user-event, jest-dom, jest-axe\n- MSW server for success/error/invalid payloads\n- Seed tests: app smoke, editor minimal, a11y smoke\n\n## Acceptance\n- `pnpm test` runs green; coverage report produced\n- At least 3 smoke tests and 1 a11y test in place\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: test \u00b7 Area: tests \u00b7 Priority: P0 \u00b7 Size: M", - "labels": [ - "test", - "P0", - "M", - "tests" - ] + "labels": ["test", "P0", "M", "tests"] }, { "title": "ESLint Flat Config + jsx-a11y Baseline", "body": "## Goal\nCatch accessibility and code-quality issues early.\n\n## Tasks\n- Add flat ESLint config with TypeScript/React/jsx-a11y\n- Prettier integration; import/order; testing-library plugin\n- Fix initial lint errors (no blanket disables)\n\n## Acceptance\n- `pnpm lint` green on clean checkout\n- No `@ts-nocheck`; no new `any` in public props\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: chore \u00b7 Area: build \u00b7 Priority: P0 \u00b7 Size: S", - "labels": [ - "chore", - "P0", - "S", - "build" - ] + "labels": ["chore", "P0", "S", "build"] }, { "title": "Correctness Sweep I \u2014 MUI v4 imports & ts-nocheck removal", "body": "## Goal\nRemove known correctness hazards blocking development.\n\n## Targets\n- Replace `@material-ui/*` imports with `@mui/*`\n- Remove `@ts-nocheck` in editor and app; add minimal types\n- Ensure app still builds/boots after changes\n\n## Acceptance\n- No remaining v4 imports; no ts-nocheck in core paths\n- Smoke tests green; manual run shows editor screen\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: fix \u00b7 Area: editor \u00b7 Priority: P0 \u00b7 Size: M", - "labels": [ - "fix", - "P0", - "M", - "editor" - ] + "labels": ["fix", "P0", "M", "editor"] }, { "title": "Correctness Sweep II \u2014 Type hygiene in reducers/selectors", "body": "## Goal\nReduce `any` usage and adopt consistent async state shapes.\n\n## Tasks\n- Replace common `any` with discriminated unions/interfaces\n- Standardize async lifecycles (idle/loading/success/error)\n- Normalize error object shape (code/message/recoverable)\n\n## Acceptance\n- Reducer/selector tests added\n- No new `any` in reducers/selectors; stricter types compile\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: fix \u00b7 Area: state \u00b7 Priority: P1 \u00b7 Size: M", - "labels": [ - "fix", - "P1", - "M", - "state" - ] + "labels": ["fix", "P1", "M", "state"] }, { "title": "Correctness Sweep III \u2014 Empty/Loading/Error states standardization", "body": "## Goal\nUsers never experience a \u201cmystery fail.\u201d\n\n## Tasks\n- Implement consistent empty/loading/error components\n- Wire to data flows across list/detail/editor\n- Ensure retry paths for transient failures\n\n## Acceptance\n- Error surfaces include a recovery action\n- Smoke tests cover all three states on a list screen\n- See docs/QUALITY_CHARTER.md.\n\n## Labels\nType: fix \u00b7 Area: lists \u00b7 Priority: P1 \u00b7 Size: M", - "labels": [ - "fix", - "P1", - "M", - "lists" - ] + "labels": ["fix", "P1", "M", "lists"] } ] -} \ No newline at end of file +} diff --git a/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md b/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md index 1a66f96..02f623e 100644 --- a/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md +++ b/issues/stand-up-test-harness--vitest---rtl---msw---jest-axe-.md @@ -1,20 +1,24 @@ # Stand up Test Harness (Vitest + RTL + MSW + jest-axe) ## Goal + Enable fast, realistic tests with accessibility checks. ## Tasks + - Configure Vitest (jsdom), alias @→src, coverage thresholds - Global test setup with RTL, user-event, jest-dom, jest-axe - MSW server for success/error/invalid payloads - Seed tests: app smoke, editor minimal, a11y smoke ## Acceptance + - `pnpm test` runs green; coverage report produced - At least 3 smoke tests and 1 a11y test in place - See docs/QUALITY_CHARTER.md. ## Labels + Type: test · Area: tests · Priority: P0 · Size: M **Labels:** test, P0, M, tests diff --git a/milestone_patch0.json b/milestone_patch0.json index e680ca3..3fe0391 100644 --- a/milestone_patch0.json +++ b/milestone_patch0.json @@ -2,4 +2,4 @@ "title": "Patch 0 \u2014 Orientation & Quality Bar", "description": "# Patch 0 \u2014 Orientation & Quality Bar\n_Generated 2025-08-16 23:43Z_\n\n## Goal\nRatify the Quality Charter and spin up the basic governance scaffolding before touching code.\n\n## Tasks\n1. **Adopt Quality Charter** \u2014 review, adjust targets if needed, and sign.\n2. **Label set** \u2014 create canonical labels in the tracker (Area, Type, Priority, Size).\n3. **Templates** \u2014 add Issue + PR templates with acceptance sections and a11y/security checks.\n4. **Milestone** \u2014 create `Patch 0` milestone with this checklist as description.\n5. **Backlog triage** \u2014 seed initial issues for Phase 1\u20133 planning (docs, harness, correctness).\n\n## Label Set (proposed)\n- **Type:** feat, fix, chore, docs, test, a11y, perf, security\n- **Area:** editor, lists, blogs, members, routing, state, build, tests, docs\n- **Priority:** P0, P1, P2\n- **Size:** XS, S, M, L, XL\n\n## Acceptance\n- Charter signed; templates merged; labels exist; milestone created; 5\u20138 seeded issues for Phase 1\u20133.\n", "state": "open" -} \ No newline at end of file +} diff --git a/pr/ci-enable.md b/pr/ci-enable.md index 2146dec..b992c61 100644 --- a/pr/ci-enable.md +++ b/pr/ci-enable.md @@ -5,5 +5,6 @@ - Docs: `docs/CI_GITEA.md` for quick troubleshooting. **Notes** + - `runs-on: self-hosted`. Ensure your runner exposes the `self-hosted` label. - If your runner only matches `ubuntu-latest`, change `runs-on` accordingly. diff --git a/pr/phase0-closeout.md b/pr/phase0-closeout.md index 1af893a..e481bc8 100644 --- a/pr/phase0-closeout.md +++ b/pr/phase0-closeout.md @@ -1,19 +1,23 @@ **Title:** Phase 0 — Closeout (v0.0.1) **Summary** + - Repo hygiene, docs, test harness, ESLint flat config, CI workflow - No behavior changes **What’s included** + - Docs: Charter, Instructions, Testing, Releasing - Harness: Vitest + RTL + MSW + jest-axe (a11y smoke) - CI: Gitea workflow (test + lint:phase0) - Version: bump to `0.0.1` **Verification** + - `npm run test` → green - `npm run lint:phase0` → clean - CI green on PR **Notes** + - App code (`src/**`) linting deferred to Phase 1–3 sweeps. diff --git a/pr/phase0.md b/pr/phase0.md index f3e2883..67c4fd3 100644 --- a/pr/phase0.md +++ b/pr/phase0.md @@ -1,12 +1,14 @@ # Phase 0 — Orientation & Quality Bar **Summary** + - Add CI workflow (no marketplace actions) - Establish lint/format/test baseline - Seed a11y smoke + MSW harness - Tracker hygiene scripts **Changes** + - ESLint flat + jsx-a11y, Prettier, EditorConfig - Vitest config (JSDOM, coverage, @ alias) - tests: setup + a11y smoke + MSW @@ -15,9 +17,11 @@ - Repo hygiene: `.nvmrc`, `.npmrc`, CODEOWNERS, CONTRIBUTING **How to verify** + ```bash nvm use pnpm install pnpm typecheck && pnpm lint && pnpm test:run ``` + Confirm CI runs on PR and shows typecheck/lint/test/build steps. diff --git a/pr/phase1-step1a.md b/pr/phase1-step1a.md index 4162be7..841072c 100644 --- a/pr/phase1-step1a.md +++ b/pr/phase1-step1a.md @@ -1,16 +1,20 @@ **Title:** Phase 1 — Step 1A: Remove `@ts-nocheck` headers **Summary** + - Remove blanket `@ts-nocheck` headers to restore type safety. - No behavior changes. **Details** + - Files touched: see diff (e.g., `src/App.tsx`, `src/components/editor/BlogEditor.tsx`). - Leave per-line todo comments in follow-ups instead of blanket disables. **Verification** + - `scripts/dev/check.sh` (Vitest run + lint:phase0) → green - App still builds locally (if applicable): `npm run dev` quick smoke **Follow-ups (tracked in Phase 1 issues)** + - Type hygiene & rule violations addressed file-by-file. diff --git a/qblog-phase-file-guide.md b/qblog-phase-file-guide.md index 2df50a3..6202c13 100644 --- a/qblog-phase-file-guide.md +++ b/qblog-phase-file-guide.md @@ -7,17 +7,21 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 0 ### Edit + - _No edits in this phase._ ### Create + - **docs/QUALITY_CHARTER.md** — Quality goals, SLOs, protected journeys; living doc. ## Phase 1 ### Edit + - **README.md** — If present: align quickstart/run/test steps with new tooling; link core docs. ### Create + - **docs/ARCHITECTURE.md** — High-level UI/state/data map; domain model for Name/Blog/Post. - **docs/TESTING.md** — Pyramid, coverage thresholds, fixtures/mocks guidance. - **docs/ACCESSIBILITY.md** — Landmarks, focus/focus return, live regions, motion/contrast, keyboard checklist. @@ -29,11 +33,13 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 2 ### Edit + - **package.json** — Add scripts (typecheck/lint/test/coverage), devDeps (Vitest/RTL/MSW/axe), align TS/Vite versions. -- **tsconfig.json** — Enable strict, noImplicitAny; add path alias '@/*' to 'src/*'. +- **tsconfig.json** — Enable strict, noImplicitAny; add path alias '@/_' to 'src/_'. - **vite.config.ts** — Add @ alias; ensure React plugin; keep SWC or standard plugin as chosen. ### Create + - **eslint.config.js** — Flat ESLint (TS, React, jsx-a11y); import/order; testing-library plugin. - **vitest.config.ts** — jsdom env, alias '@'→ 'src', coverage thresholds, setup file. - **tests/setup.ts** — RTL matchers, MSW server, jest-axe/axe; test-time polyfills. @@ -43,7 +49,8 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 3 ### Edit -- **src/components/editor/BlogEditor.tsx** — Replace @material-ui/* with @mui/* equivalents; fix icon imports. + +- **src/components/editor/BlogEditor.tsx** — Replace @material-ui/_ with @mui/_ equivalents; fix icon imports. - **src/App.tsx** — Remove '@ts-nocheck'; add minimal types or local @ts-expect-error with rationale. - **src/components/editor/BlogEditor.tsx** — Remove '@ts-nocheck'; add minimal types or local @ts-expect-error with rationale. - **src/components/AudioElement.tsx** — Tighten ': any' types—start with reducers/selectors/editor props. @@ -108,11 +115,13 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a - **index.html** — Verify lang, meta, and root element a11y (no functional change). ### Create + - **src/components/system/ErrorBoundary.tsx** — Top-level error boundary with fallback UI and error reporting hook. ## Phase 4 ### Edit + - **src/pages/BlogIndividualPost/BlogIndividualPost.tsx** — Add meaningful alt text or alt='' for decorative images. - **src/pages/CreatePost/CreatePostMinimal.tsx** — Add meaningful alt text or alt='' for decorative images. - **src/pages/CreatePost/CreatePostBuilder.tsx** — Add meaningful alt text or alt='' for decorative images. @@ -121,10 +130,11 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a - **src/components/common/ResponsiveImage.tsx** — Add meaningful alt text or alt='' for decorative images. - **src/components/common/Tipping/Tipping.tsx** — Add meaningful alt text or alt='' for decorative images. - **src/App.tsx** — Ensure page landmarks and skip link mount; set document titles per route. -- **src/components/common/*** — Normalize labels, aria-* states; ensure focus-visible rings remain visible. -- **src/components/modals/*** — Trap focus, restore on close; Esc to dismiss. +- **src/components/common/\*** — Normalize labels, aria-\* states; ensure focus-visible rings remain visible. +- **src/components/modals/\*** — Trap focus, restore on close; Esc to dismiss. ### Create + - **src/components/a11y/SkipToContentLink.tsx** — Visible on focus; anchors main content. - **src/components/a11y/LiveRegion.tsx** — Polite status updates (saving, uploaded, failed). - **tests/a11y/keyboard.journeys.test.tsx** — Tab order, focus trap/restore across protected journeys. @@ -132,13 +142,15 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 5 ### Edit + - **src/components/layout/Header.tsx** — Clarify nav, add Blog switch entry placeholder, consistent naming. - **src/components/layout/Nav.tsx** — Primary/secondary nav separation; active route indication. - **src/components/editor/Toolbar.tsx** — Deterministic enable/disable states; accessible names. - **src/components/editor/EditorSurface.tsx** — Autosave status UI, debounced save feedback. -- **src/pages/*** — Consistent empty/error state components with clear actions. +- **src/pages/\*** — Consistent empty/error state components with clear actions. ### Create + - **src/components/feedback/Toast.tsx** — Unified success/error toasts (MUI Snackbar pattern). - **src/components/states/EmptyState.tsx** — Shared empty-state component with title/body/actions. - **src/components/states/ErrorBanner.tsx** — Shared error banner with retry hook. @@ -147,13 +159,15 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 6 ### Edit + - **src/pages/PostList.tsx** — Scope list to current blog; update queries/selectors. - **src/pages/CreatePost.tsx** — Bind creation to current blog; default blog auto-select. - **src/wrappers/GlobalWrapper.tsx** — Provide current blog context from state for children. -- **src/routes/*** — Reflect blog handle in routes (router params). +- **src/routes/\*** — Reflect blog handle in routes (router params). - **docs/ARCHITECTURE.md** — Update domain model and routing implications. ### Create + - **src/features/blogs/domain.ts** — Blog entity types; handle normalization/validation. - **src/features/blogs/api.ts** — listBlogs/createBlog endpoints (RTK Query). - **src/features/blogs/state.ts** — Current blog state, selectors, actions. @@ -165,11 +179,13 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 7 ### Edit + - **src/components/posts/PostEditor.tsx** — Gate edit/publish controls by role; show attribution. - **src/components/posts/PostListItem.tsx** — Show author/updatedBy; role-sensitive actions. - **docs/DECISIONS/ADR-0002-permissions-model.md** — Fill in finalized operation matrix and tradeoffs. ### Create + - **src/features/permissions/roles.ts** — Role constants and operation matrix. - **src/features/permissions/can.ts** — `can(op, ctx)` guard utilities. - **src/features/memberships/api.ts** — invite/accept/remove/list endpoints. @@ -180,11 +196,13 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 8 ### Edit + - **src/components/lists/VirtualizedList.tsx** — Ensure virtualization for long lists (react-virtuoso). -- **src/features/*/api.ts** — Adopt retry/backoff for idempotent GET; idempotency keys for mutations where applicable. -- **src/components/uploads/*** — Make uploads abortable; show progress via LiveRegion. +- **src/features/\*/api.ts** — Adopt retry/backoff for idempotent GET; idempotency keys for mutations where applicable. +- **src/components/uploads/\*** — Make uploads abortable; show progress via LiveRegion. ### Create + - **src/lib/net/fetchJson.ts** — Abortable fetch with timeout and structured errors. - **src/lib/net/retry.ts** — Backoff strategy; idempotency helpers. - **src/components/states/SkeletonList.tsx** — Skeletons for perceived speed on lists. @@ -193,10 +211,12 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 9 ### Edit + - **src/theme/tokens.ts** — Centralize color/spacing/typography tokens; enforce contrast. -- **src/components/layout/*** — Replace literals with t('…'); ensure logical properties for RTL prep. +- **src/components/layout/\*** — Replace literals with t('…'); ensure logical properties for RTL prep. ### Create + - **src/i18n/index.ts** — i18next init; namespace scaffolding. - **src/i18n/en/common.json** — Default strings. - **tests/i18n/formatting.test.ts** — Plural/date/number formatting sanity. @@ -204,11 +224,13 @@ This guide lists files to **edit** and **create** for each roadmap phase, with a ## Phase 10 ### Edit + - **src/components/editor/Renderer.tsx** — Sanitize HTML on render. - **src/components/editor/Serializer.ts** — Sanitize on save/serialize pipeline. - **docs/QUALITY_CHARTER.md** — Mark budgets and acceptance as met; note metrics. ### Create + - **src/components/system/RouteErrorBoundary.tsx** — Route-level error boundaries with friendly fallback. - **src/lib/logging/clientLog.ts** — Client error/event logger with throttling. - **src/lib/security/sanitizeHtml.ts** — DOMPurify wrapper with allowlist for editor output. diff --git a/qblog-structure-and-edit-plan.md b/qblog-structure-and-edit-plan.md index d9a3139..ea3fed8 100644 --- a/qblog-structure-and-edit-plan.md +++ b/qblog-structure-and-edit-plan.md @@ -3,10 +3,12 @@ _Generated: 2025-08-16T23:19:33.635026Z_ ## Conventions reference (from sister apps) + - Q-Place/Q-Chess use **eslint.config.js**, **vitest.config.ts**, **tests/setup.ts**, and a **docs/** folder with architecture/decisions. - CI lives under **.gitea/**; tests run with Vitest + React Testing Library; a11y linting via jsx-a11y and axe checks. ## Full file list (current repository) + ```text .gitignore .prettierrc.json @@ -140,6 +142,7 @@ vite.config.ts ``` ## Files to EDIT (with reasons) + - **index.html** — Verify a11y root attributes and mount point; no functional change expected. - **package.json** — Add test/lint scripts; add Vitest/RTL/axe; upgrade TypeScript/Vite to match sister apps; add eslint/jsx-a11y. - **src/App.tsx** — Remove '@ts-nocheck'; add minimal types/props. @@ -201,9 +204,11 @@ vite.config.ts - **vite.config.ts** — Add path alias '@' → 'src'; keep React SWC; verify base path. ## Files to RENAME + - **src/pages/CreateEditProfile/CreatEditProfile.tsx → src/pages/CreateEditProfile/CreateEditProfile.tsx** — Fix spelling; align filename with folder and imports. ## New files to ADD (to align with Q-Place/Q-Chess) + - **eslint.config.js** — Flat ESLint config with TypeScript, React, jsx-a11y; shared rules. - **vitest.config.ts** — Vitest setup (jsdom), coverage thresholds, alias '@' to 'src'. - **tests/setup.ts** — Global test setup: RTL matchers, MSW, jest-axe/axe-core. @@ -219,6 +224,7 @@ vite.config.ts - **docs/RELEASE_NOTES.md** — Human-readable changes per release. ## Notes + - Many files flagged for `: any` should be addressed opportunistically: start with reducers/selectors and editor props, then components used across pages. - For `` elements, provide descriptive `alt` where meaningful; use `alt=""` (decorative) when the image conveys no unique information. - Replace the single `eslint-disable` by fixing the underlying pattern (likely a hook dependency or exhaustive-deps issue). diff --git a/scripts/tracker/README.md b/scripts/tracker/README.md index 11d076f..3c43f24 100644 --- a/scripts/tracker/README.md +++ b/scripts/tracker/README.md @@ -1,16 +1,20 @@ # Tracker Bootstrap — Patch 0 + Generated 2025-08-16 23:51Z ## What this does + - Creates labels (from `.gitea/labels.json`) - Creates milestone **Patch 0 — Orientation & Quality Bar** (from `milestone_patch0.json`) - Creates 7 kickoff issues (from `issues/issues_patch0_phase1-3.json`) and assigns them to the milestone ## Requirements + - Gitea with API access - Tools: `curl`, `jq` ## Usage + ```bash export GITEA_BASE_URL="https://gitea.example.com" export GITEA_TOKEN="YOUR_TOKEN" @@ -20,6 +24,7 @@ bash scripts/tracker/bootstrap_patch0.sh ``` ## Files + - `.gitea/labels.json` - `milestone_patch0.json` - `issues/issues_patch0_phase1-3.json` diff --git a/scripts/tracker/TROUBLESHOOTING.md b/scripts/tracker/TROUBLESHOOTING.md index 1dbfefb..69e0b7d 100644 --- a/scripts/tracker/TROUBLESHOOTING.md +++ b/scripts/tracker/TROUBLESHOOTING.md @@ -1,20 +1,24 @@ # Tracker Bootstrap — Troubleshooting (v4, 2025-08-17 00:53Z) **Close duplicate milestone (keep lower ID):** + ```bash curl -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/milestones/2" --data '{"state":"closed"}' ``` **List duplicate labels by name:** + ```bash curl -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/labels" | jq 'group_by(.name) | map(select(length>1) | {name:.[0].name, ids: map(.id)})' ``` **Delete a duplicate label by ID:** + ```bash curl -X DELETE -H "Authorization: token $GITEA_TOKEN" "$GITEA_BASE_URL/api/v1/repos/$OWNER/$REPO/labels/" ``` **Re-run bootstrap safely:** + - Script skips existing labels by exact name. - Issues are created **without labels** to avoid server 422; add labels via UI later if desired. diff --git a/src/App.tsx b/src/App.tsx index 10d60ee..aabcb7e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,23 @@ - -import { Routes, Route } from 'react-router-dom' -import { useIframe } from './hooks/useIframe' -import { BlogIndividualPost } from './pages/BlogIndividualPost/BlogIndividualPost' -import { BlogIndividualProfile } from './pages/BlogIndividualProfile/BlogIndividualProfile' -import { BlogList } from './pages/BlogList/BlogList' -import { CreatePost } from './pages/CreatePost/CreatePost' -import { CreatEditProfile } from './pages/CreateEditProfile/CreatEditProfile' -import { ThemeProvider } from '@mui/material/styles' -import { CssBaseline } from '@mui/material' -import { lightTheme, darkTheme } from './styles/theme' -import { store } from './state/store' -import { Provider } from 'react-redux' -import GlobalWrapper from './wrappers/GlobalWrapper' -import DownloadWrapper from './wrappers/DownloadWrapper' -import Notification from './components/common/Notification/Notification' -import { useState } from 'react' +import { Routes, Route } from 'react-router-dom'; +import { useIframe } from './hooks/useIframe'; +import { BlogIndividualPost } from './pages/BlogIndividualPost/BlogIndividualPost'; +import { BlogIndividualProfile } from './pages/BlogIndividualProfile/BlogIndividualProfile'; +import { BlogList } from './pages/BlogList/BlogList'; +import { CreatePost } from './pages/CreatePost/CreatePost'; +import { CreatEditProfile } from './pages/CreateEditProfile/CreatEditProfile'; +import { ThemeProvider } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import { lightTheme, darkTheme } from './styles/theme'; +import { store } from './state/store'; +import { Provider } from 'react-redux'; +import GlobalWrapper from './wrappers/GlobalWrapper'; +import DownloadWrapper from './wrappers/DownloadWrapper'; +import Notification from './components/common/Notification/Notification'; +import { useState } from 'react'; function App() { - const themeColor = window._qdnTheme - useIframe() + const themeColor = (window as any)._qdnTheme as string | undefined; + useIframe(); // const [colorTheme, setColorTheme] = useState('dark') @@ -35,32 +34,20 @@ function App() { - } - /> - } - /> + } /> + } /> } /> } /> } /> - } - /> - } - /> + } /> + } /> } /> - ) + ); } -export default App +export default App; diff --git a/src/assets/svgs/AccountCircleSVG.tsx b/src/assets/svgs/AccountCircleSVG.tsx index deb88bd..de57301 100644 --- a/src/assets/svgs/AccountCircleSVG.tsx +++ b/src/assets/svgs/AccountCircleSVG.tsx @@ -1,25 +1,16 @@ interface AccountCircleSVGProps { - color: string - height: string - width: string + color: string; + height: string; + width: string; } -export const AccountCircleSVG: React.FC = ({ - color, - height, - width -}) => { +export const AccountCircleSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/AlignCenterSVG.tsx b/src/assets/svgs/AlignCenterSVG.tsx index b78991b..3e6cf5b 100644 --- a/src/assets/svgs/AlignCenterSVG.tsx +++ b/src/assets/svgs/AlignCenterSVG.tsx @@ -1,21 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; -export const AlignCenterSVG: React.FC = ({ - color, - height, - width -}) => { +export const AlignCenterSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/AlignLeftSVG.tsx b/src/assets/svgs/AlignLeftSVG.tsx index 4ac2eb4..f3bbc50 100644 --- a/src/assets/svgs/AlignLeftSVG.tsx +++ b/src/assets/svgs/AlignLeftSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const AlignLeftSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/AlignRightSVG.tsx b/src/assets/svgs/AlignRightSVG.tsx index 2a2eea8..5119992 100644 --- a/src/assets/svgs/AlignRightSVG.tsx +++ b/src/assets/svgs/AlignRightSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const AlignRightSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/BoldSVG.tsx b/src/assets/svgs/BoldSVG.tsx index 8d06816..b064109 100644 --- a/src/assets/svgs/BoldSVG.tsx +++ b/src/assets/svgs/BoldSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const BoldSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/CodeBlockSVG.tsx b/src/assets/svgs/CodeBlockSVG.tsx index 1fad02f..d7690ad 100644 --- a/src/assets/svgs/CodeBlockSVG.tsx +++ b/src/assets/svgs/CodeBlockSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const CodeBlockSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/H2SVG.tsx b/src/assets/svgs/H2SVG.tsx index 0328fc3..72fefec 100644 --- a/src/assets/svgs/H2SVG.tsx +++ b/src/assets/svgs/H2SVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const H2SVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/H3SVG.tsx b/src/assets/svgs/H3SVG.tsx index 7268032..f329f8a 100644 --- a/src/assets/svgs/H3SVG.tsx +++ b/src/assets/svgs/H3SVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const H3SVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/ItalicSVG.tsx b/src/assets/svgs/ItalicSVG.tsx index 50a5493..53c88e9 100644 --- a/src/assets/svgs/ItalicSVG.tsx +++ b/src/assets/svgs/ItalicSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const ItalicSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/LinkSVG.tsx b/src/assets/svgs/LinkSVG.tsx index 9994f43..665b867 100644 --- a/src/assets/svgs/LinkSVG.tsx +++ b/src/assets/svgs/LinkSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const LinkSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/NewWindowSVG.tsx b/src/assets/svgs/NewWindowSVG.tsx index c97d483..550631e 100644 --- a/src/assets/svgs/NewWindowSVG.tsx +++ b/src/assets/svgs/NewWindowSVG.tsx @@ -1,25 +1,16 @@ interface NewWindowSVGProps { - color: string - height: string - width: string + color: string; + height: string; + width: string; } -export const NewWindowSVG: React.FC = ({ - color, - height, - width -}) => { +export const NewWindowSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/UnderlineSVG.tsx b/src/assets/svgs/UnderlineSVG.tsx index a65f597..bfeff41 100644 --- a/src/assets/svgs/UnderlineSVG.tsx +++ b/src/assets/svgs/UnderlineSVG.tsx @@ -1,17 +1,12 @@ -import { SVGProps } from './interfaces' +import { SVGProps } from './interfaces'; export const UnderlineSVG: React.FC = ({ color, height, width }) => { return ( - + - ) -} + ); +}; diff --git a/src/assets/svgs/interfaces.ts b/src/assets/svgs/interfaces.ts index 6a0d5e1..90bd438 100644 --- a/src/assets/svgs/interfaces.ts +++ b/src/assets/svgs/interfaces.ts @@ -1,5 +1,5 @@ export interface SVGProps { - color: string - height: string - width: string + color?: string; + height: string; + width: string; } diff --git a/src/components/AudioElement.tsx b/src/components/AudioElement.tsx index 5b807ee..7d2696c 100644 --- a/src/components/AudioElement.tsx +++ b/src/components/AudioElement.tsx @@ -1,17 +1,14 @@ -import * as React from 'react' -import { styled, useTheme } from '@mui/material/styles' -import Box from '@mui/material/Box' -import Typography from '@mui/material/Typography' +import * as React from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; -import AudiotrackIcon from '@mui/icons-material/Audiotrack' -import { MyContext } from '../wrappers/DownloadWrapper' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../state/store' -import { CircularProgress } from '@mui/material' -import { - setCurrAudio, - setShowingAudioPlayer -} from '../state/features/globalSlice' +import AudiotrackIcon from '@mui/icons-material/Audiotrack'; +import { MyContext } from '../wrappers/DownloadWrapper'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../state/store'; +import { CircularProgress } from '@mui/material'; +import { setCurrAudio, setShowingAudioPlayer } from '../state/features/globalSlice'; const Widget = styled('div')(({ theme }) => ({ padding: 16, @@ -25,9 +22,9 @@ const Widget = styled('div')(({ theme }) => ({ background: 'skyblue', transition: '0.2s all', '&:hover': { - opacity: 0.75 - } -})) + opacity: 0.75, + }, +})); const CoverImage = styled('div')({ width: 100, @@ -38,25 +35,25 @@ const CoverImage = styled('div')({ borderRadius: 8, backgroundColor: 'rgba(0,0,0,0.08)', '& > img': { - width: '100%' - } -}) + width: '100%', + }, +}); const TinyText = styled(Typography)({ fontSize: '0.75rem', opacity: 0.38, fontWeight: 500, - letterSpacing: 0.2 -}) + letterSpacing: 0.2, +}); interface IAudioElement { - onClick: () => void - title: string - description: string - author: string - audioInfo?: any - postId?: string - user?: string + onClick: () => void; + title: string; + description: string; + author: string; + audioInfo?: any; + postId?: string; + user?: string; } export default function AudioElement({ @@ -66,35 +63,35 @@ export default function AudioElement({ author, audioInfo, postId, - user + user, }: IAudioElement) { - const { downloadVideo } = React.useContext(MyContext) - const [isLoading, setIsLoading] = React.useState(false) - const { downloads } = useSelector((state: RootState) => state.global) - const reDownload = React.useRef(false) + const { downloadVideo } = React.useContext(MyContext); + const [isLoading, setIsLoading] = React.useState(false); + const { downloads } = useSelector((state: RootState) => state.global); + const reDownload = React.useRef(false); - const dispatch = useDispatch() + const dispatch = useDispatch(); const download = React.useMemo(() => { - if (!downloads || !audioInfo?.identifier) return {} - const findDownload = downloads[audioInfo?.identifier] + if (!downloads || !audioInfo?.identifier) return {}; + const findDownload = downloads[audioInfo?.identifier]; - if (!findDownload) return {} - return findDownload - }, [downloads, audioInfo]) + if (!findDownload) return {}; + return findDownload; + }, [downloads, audioInfo]); const resourceStatus = React.useMemo(() => { - return download?.status || {} - }, [download]) + return download?.status || {}; + }, [download]); const handlePlay = () => { - if (!postId) return - const { name, service, identifier } = audioInfo + if (!postId) return; + const { name, service, identifier } = audioInfo; if (download && resourceStatus?.status === 'READY') { - dispatch(setShowingAudioPlayer(true)) - dispatch(setCurrAudio(identifier)) - return + dispatch(setShowingAudioPlayer(true)); + dispatch(setCurrAudio(identifier)); + return; } - setIsLoading(true) + setIsLoading(true); downloadVideo({ name, service, @@ -104,28 +101,25 @@ export default function AudioElement({ user, audioTitle: title, audioDescription: description, - audioAuthor: author - } - }) - dispatch(setCurrAudio(identifier)) - dispatch(setShowingAudioPlayer(true)) - } + audioAuthor: author, + }, + }); + dispatch(setCurrAudio(identifier)); + dispatch(setShowingAudioPlayer(true)); + }; React.useEffect(() => { if (resourceStatus?.status === 'READY') { - setIsLoading(false) + setIsLoading(false); } - }, [resourceStatus]) + }, [resourceStatus]); React.useEffect(() => { - if ( - resourceStatus?.status === 'DOWNLOADED' && - reDownload?.current === false - ) { - handlePlay() - reDownload.current = true + if (resourceStatus?.status === 'DOWNLOADED' && reDownload?.current === false) { + handlePlay(); + reDownload.current = true; } - }, [handlePlay, resourceStatus]) + }, [handlePlay, resourceStatus]); return ( @@ -142,16 +136,12 @@ export default function AudioElement({ - + {author} @@ -162,8 +152,7 @@ export default function AudioElement({ - {((resourceStatus.status && resourceStatus?.status !== 'READY') || - isLoading) && ( + {((resourceStatus.status && resourceStatus?.status !== 'READY') || isLoading) && ( @@ -190,15 +179,14 @@ export default function AudioElement({ component="div" sx={{ color: 'white', - fontSize: '14px' + fontSize: '14px', }} > {resourceStatus?.status === 'REFETCHING' ? ( <> <> {( - (resourceStatus?.localChunkCount / - resourceStatus?.totalChunkCount) * + (resourceStatus?.localChunkCount / resourceStatus?.totalChunkCount) * 100 )?.toFixed(0)} % @@ -211,8 +199,7 @@ export default function AudioElement({ ) : resourceStatus?.status !== 'READY' ? ( <> {( - (resourceStatus?.localChunkCount / - resourceStatus?.totalChunkCount) * + (resourceStatus?.localChunkCount / resourceStatus?.totalChunkCount) * 100 )?.toFixed(0)} % @@ -226,5 +213,5 @@ export default function AudioElement({ )} - ) + ); } diff --git a/src/components/DynamicHeightItem.tsx b/src/components/DynamicHeightItem.tsx index a6ea00d..67503d0 100644 --- a/src/components/DynamicHeightItem.tsx +++ b/src/components/DynamicHeightItem.tsx @@ -1,17 +1,17 @@ -import React, { useRef, useState, useEffect } from 'react' -import ReactResizeDetector from 'react-resize-detector' -import { Layouts, Layout } from 'react-grid-layout' +import React, { useRef, useState, useEffect } from 'react'; +import ReactResizeDetector from 'react-resize-detector'; +import { Layouts, Layout } from 'react-grid-layout'; interface DynamicHeightItemProps { - children: React.ReactNode - layouts: Layouts - setLayouts: (layouts: any) => void - i: string - breakpoint: keyof Layouts - rows?: number - count?: number - type?: string - padding?: number + children: React.ReactNode; + layouts: Layouts; + setLayouts: (layouts: any) => void; + i: string; + breakpoint: keyof Layouts; + rows?: number; + count?: number; + type?: string; + padding?: number; } const DynamicHeightItem: React.FC = ({ @@ -23,74 +23,70 @@ const DynamicHeightItem: React.FC = ({ rows = 1, count, type, - padding + padding, }) => { - const [height, setHeight] = useState(rows * 150) - const ref = useRef(null) + const [height, setHeight] = useState(rows * 150); + const ref = useRef(null); useEffect(() => { if (ref.current) { - setHeight(ref.current.clientHeight) + setHeight(ref.current.clientHeight); } - }, [ref.current]) + }, [ref.current]); const onResize = () => { if (ref.current) { - setHeight(ref.current.clientHeight) + setHeight(ref.current.clientHeight); } - } + }; const getBreakpoint = (screenWidth: number) => { if (screenWidth >= 996) { - return 'md' + return 'md'; } else if (screenWidth >= 768) { - return 'sm' + return 'sm'; } else { - return 'xs' + return 'xs'; } - } + }; useEffect(() => { - const widthWin = window.innerWidth - let newBreakpoint = breakpoint + const widthWin = window.innerWidth; + let newBreakpoint = breakpoint; // if (!newBreakpoint) { // newBreakpoint = getBreakpoint(widthWin) // } setLayouts((prev: any) => { - const newLayouts: any = { ...prev } - newLayouts[newBreakpoint] = newLayouts[newBreakpoint]?.map( - (item: Layout) => { - if (item.i === i) { - let constantNum = 25 + const newLayouts: any = { ...prev }; + newLayouts[newBreakpoint] = newLayouts[newBreakpoint]?.map((item: Layout) => { + if (item.i === i) { + let constantNum = 25; - return { - ...item, - h: Math.ceil(height / (rows * constantNum)) // Adjust this value based on your rowHeight and the number of rows the element spans - } - } - return item + return { + ...item, + h: Math.ceil(height / (rows * constantNum)), // Adjust this value based on your rowHeight and the number of rows the element spans + }; } - ) - return newLayouts - }) - }, [height, breakpoint, count, setLayouts]) - - + return item; + }); + return newLayouts; + }); + }, [height, breakpoint, count, setLayouts]); return (
{children}
- ) -} + ); +}; -export default DynamicHeightItem +export default DynamicHeightItem; diff --git a/src/components/DynamicHeightItemMinimal.tsx b/src/components/DynamicHeightItemMinimal.tsx index b80d53f..92d3cfc 100644 --- a/src/components/DynamicHeightItemMinimal.tsx +++ b/src/components/DynamicHeightItemMinimal.tsx @@ -1,17 +1,17 @@ -import React, { useRef, useState, useEffect } from 'react' -import ReactResizeDetector from 'react-resize-detector' -import { Layouts, Layout } from 'react-grid-layout' +import React, { useRef, useState, useEffect } from 'react'; +import ReactResizeDetector from 'react-resize-detector'; +import { Layouts, Layout } from 'react-grid-layout'; interface DynamicHeightItemProps { - children: React.ReactNode - layouts: Layouts - setLayouts: (layouts: any) => void - i: string - breakpoint: keyof Layouts - rows?: number - count?: number - type?: string - padding?: number + children: React.ReactNode; + layouts: Layouts; + setLayouts: (layouts: any) => void; + i: string; + breakpoint: keyof Layouts; + rows?: number; + count?: number; + type?: string; + padding?: number; } export const DynamicHeightItemMinimal: React.FC = ({ @@ -23,17 +23,17 @@ export const DynamicHeightItemMinimal: React.FC = ({ rows = 1, count, type, - padding + padding, }) => { return (
{children}
- ) -} + ); +}; diff --git a/src/components/FileElement.tsx b/src/components/FileElement.tsx index a38c7bb..9dda2ff 100644 --- a/src/components/FileElement.tsx +++ b/src/components/FileElement.tsx @@ -1,22 +1,16 @@ -import * as React from 'react' -import { styled, useTheme } from '@mui/material/styles' -import Box from '@mui/material/Box' -import Typography from '@mui/material/Typography' -import AudiotrackIcon from '@mui/icons-material/Audiotrack' -import { MyContext } from '../wrappers/DownloadWrapper' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../state/store' -import { CircularProgress } from '@mui/material' -import AttachFileIcon from '@mui/icons-material/AttachFile' -import { - setCurrAudio, - setShowingAudioPlayer -} from '../state/features/globalSlice' -import { - base64ToUint8Array, - objectToUint8ArrayFromResponse -} from '../utils/toBase64' -import { setNotification } from '../state/features/notificationsSlice' +import * as React from 'react'; +import { styled, useTheme } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import AudiotrackIcon from '@mui/icons-material/Audiotrack'; +import { MyContext } from '../wrappers/DownloadWrapper'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../state/store'; +import { CircularProgress } from '@mui/material'; +import AttachFileIcon from '@mui/icons-material/AttachFile'; +import { setCurrAudio, setShowingAudioPlayer } from '../state/features/globalSlice'; +import { base64ToUint8Array, objectToUint8ArrayFromResponse } from '../utils/toBase64'; +import { setNotification } from '../state/features/notificationsSlice'; const Widget = styled('div')(({ theme }) => ({ padding: 8, @@ -30,9 +24,9 @@ const Widget = styled('div')(({ theme }) => ({ background: '#03a9f4', transition: '0.2s all', '&:hover': { - opacity: 0.75 - } -})) + opacity: 0.75, + }, +})); const CoverImage = styled('div')({ width: 40, @@ -43,36 +37,36 @@ const CoverImage = styled('div')({ borderRadius: 8, backgroundColor: 'rgba(0,0,0,0.08)', '& > img': { - width: '100%' - } -}) + width: '100%', + }, +}); const TinyText = styled(Typography)({ fontSize: '0.75rem', opacity: 0.38, fontWeight: 500, - letterSpacing: 0.2 -}) + letterSpacing: 0.2, +}); interface IAudioElement { - title: string - description?: string - author?: string - fileInfo?: any - postId?: string - user?: string - children?: React.ReactNode - mimeType?: string - disable?: boolean - mode?: string - otherUser?: string + title: string; + description?: string; + author?: string; + fileInfo?: any; + postId?: string; + user?: string; + children?: React.ReactNode; + mimeType?: string; + disable?: boolean; + mode?: string; + otherUser?: string; } interface CustomWindow extends Window { - showSaveFilePicker: any // Replace 'any' with the appropriate type if you know it + showSaveFilePicker: any; // Replace 'any' with the appropriate type if you know it } -const customWindow = window as unknown as CustomWindow +const customWindow = window as unknown as CustomWindow; export default function FileElement({ title, @@ -85,68 +79,64 @@ export default function FileElement({ mimeType, disable, mode, - otherUser + otherUser, }: IAudioElement) { - const { downloadVideo } = React.useContext(MyContext) - const [isLoading, setIsLoading] = React.useState(false) - const [fileProperties, setFileProperties] = React.useState(null) - const [downloadLoader, setDownloadLoader] = React.useState(false) + const { downloadVideo } = React.useContext(MyContext); + const [isLoading, setIsLoading] = React.useState(false); + const [fileProperties, setFileProperties] = React.useState(null); + const [downloadLoader, setDownloadLoader] = React.useState(false); - const [pdfSrc, setPdfSrc] = React.useState('') - const { downloads } = useSelector((state: RootState) => state.global) - const { user: username } = useSelector((state: RootState) => state.auth) + const [pdfSrc, setPdfSrc] = React.useState(''); + const { downloads } = useSelector((state: RootState) => state.global); + const { user: username } = useSelector((state: RootState) => state.auth); - const dispatch = useDispatch() + const dispatch = useDispatch(); const download = React.useMemo(() => { - if (!downloads || !fileInfo?.identifier) return {} - const findDownload = downloads[fileInfo?.identifier] + if (!downloads || !fileInfo?.identifier) return {}; + const findDownload = downloads[fileInfo?.identifier]; - if (!findDownload) return {} - return findDownload - }, [downloads, fileInfo]) + if (!findDownload) return {}; + return findDownload; + }, [downloads, fileInfo]); const resourceStatus = React.useMemo(() => { - return download?.status || {} - }, [download]) + return download?.status || {}; + }, [download]); const saveFileToDisk = async (blob: any, fileName: any) => { try { const fileHandle = await customWindow.showSaveFilePicker({ suggestedName: fileName, types: [ { - description: 'File' - } - ] - }) + description: 'File', + }, + ], + }); const writeFile = async (fileHandle: any, contents: any) => { - const writable = await fileHandle.createWritable() - await writable.write(contents) - await writable.close() - } - writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')) + const writable = await fileHandle.createWritable(); + await writable.write(contents); + await writable.close(); + }; + writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')); } catch (error) { - console.log(error) + console.log(error); } - } + }; const handlePlay = async () => { - if (disable) return - if ( - resourceStatus?.status === 'READY' && - download?.url && - download?.blogPost?.filename - ) { - if (downloadLoader) return + if (disable) return; + if (resourceStatus?.status === 'READY' && download?.url && download?.blogPost?.filename) { + if (downloadLoader) return; dispatch( setNotification({ msg: 'Saving file... please wait', - alertType: 'info' - }) - ) + alertType: 'info', + }), + ); try { - const { name, service, identifier } = fileInfo + const { name, service, identifier } = fileInfo; - setDownloadLoader(true) - const url = `/arbitrary/${service}/${name}/${identifier}` + setDownloadLoader(true); + const url = `/arbitrary/${service}/${name}/${identifier}`; fetch(url) .then((response) => response.blob()) .then(async (blob) => { @@ -154,77 +144,77 @@ export default function FileElement({ action: 'SAVE_FILE', blob, filename: download?.blogPost?.filename, - mimeType: download?.blogPost?.mimeType || '' - }) + mimeType: download?.blogPost?.mimeType || '', + }); // saveAs(blob, download?.blogPost?.filename) }) .catch((error) => { - console.error('Error fetching the video:', error) + console.error('Error fetching the video:', error); // clearInterval(intervalId) }) .finally(() => { - setDownloadLoader(false) - }) + setDownloadLoader(false); + }); } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to send message', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to send message', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to send message', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); } finally { if (mode === 'mail') { - setDownloadLoader(false) + setDownloadLoader(false); } } - return + return; } - if (!postId) return - const { name, service, identifier } = fileInfo - let filename = fileProperties?.filename - let mimeType = fileProperties?.mimeType + if (!postId) return; + const { name, service, identifier } = fileInfo; + let filename = fileProperties?.filename; + let mimeType = fileProperties?.mimeType; if (!fileProperties) { try { dispatch( setNotification({ msg: 'Downloading file... please wait', - alertType: 'info' - }) - ) + alertType: 'info', + }), + ); let res = await qortalRequest({ action: 'GET_QDN_RESOURCE_PROPERTIES', name: name, service: service, - identifier: identifier - }) - setFileProperties(res) - filename = res?.filename - mimeType = res?.mimeType + identifier: identifier, + }); + setFileProperties(res); + filename = res?.filename; + mimeType = res?.mimeType; } catch (error: any) { dispatch( setNotification({ msg: error?.message || 'Error with download. Please try again', - alertType: 'error' - }) - ) + alertType: 'error', + }), + ); } } - if (!filename) return + if (!filename) return; - setIsLoading(true) + setIsLoading(true); downloadVideo({ name, service, @@ -236,20 +226,16 @@ export default function FileElement({ audioDescription: description, audioAuthor: author, filename, - mimeType - } - }) - } + mimeType, + }, + }); + }; React.useEffect(() => { - if ( - resourceStatus?.status === 'READY' && - download?.url && - download?.blogPost?.filename - ) { - setIsLoading(false) + if (resourceStatus?.status === 'READY' && download?.url && download?.blogPost?.filename) { + setIsLoading(false); } - }, [resourceStatus, download]) + }, [resourceStatus, download]); return ( {children && ( @@ -267,25 +253,22 @@ export default function FileElement({ display: 'flex', alignItems: 'center', position: 'relative', - gap: '7px' + gap: '7px', }} > {children}{' '} - {(resourceStatus.status && resourceStatus?.status !== 'READY') || - isLoading ? ( + {(resourceStatus.status && resourceStatus?.status !== 'READY') || isLoading ? ( ) : resourceStatus?.status === 'READY' ? ( <> Ready to save: click here - {downloadLoader && ( - - )} + {downloadLoader && } ) : null} @@ -297,22 +280,18 @@ export default function FileElement({ - + {author} {title} @@ -321,7 +300,7 @@ export default function FileElement({ noWrap letterSpacing={-0.25} sx={{ - fontSize: '14px' + fontSize: '14px', }} > {description} @@ -331,7 +310,7 @@ export default function FileElement({ noWrap letterSpacing={-0.25} sx={{ - fontSize: '12px' + fontSize: '12px', }} > {mimeType} @@ -339,8 +318,7 @@ export default function FileElement({ )}
- {((resourceStatus.status && resourceStatus?.status !== 'READY') || - isLoading) && ( + {((resourceStatus.status && resourceStatus?.status !== 'READY') || isLoading) && ( @@ -367,15 +345,14 @@ export default function FileElement({ component="div" sx={{ color: 'white', - fontSize: '14px' + fontSize: '14px', }} > {resourceStatus?.status === 'REFETCHING' ? ( <> <> {( - (resourceStatus?.localChunkCount / - resourceStatus?.totalChunkCount) * + (resourceStatus?.localChunkCount / resourceStatus?.totalChunkCount) * 100 )?.toFixed(0)} % @@ -388,8 +365,7 @@ export default function FileElement({ ) : resourceStatus?.status !== 'READY' ? ( <> {( - (resourceStatus?.localChunkCount / - resourceStatus?.totalChunkCount) * + (resourceStatus?.localChunkCount / resourceStatus?.totalChunkCount) * 100 )?.toFixed(0)} % @@ -401,45 +377,41 @@ export default function FileElement({ )} )} - {resourceStatus?.status === 'READY' && - download?.url && - download?.blogPost?.filename && ( - + - - Ready to save: click here - - {downloadLoader && ( - - )} - - )} + Ready to save: click here + + {downloadLoader && } +
+ )} )} - ) + ); } diff --git a/src/components/common/AudioPanel.tsx b/src/components/common/AudioPanel.tsx index 1d3d50b..5ea34e1 100644 --- a/src/components/common/AudioPanel.tsx +++ b/src/components/common/AudioPanel.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react' -import { styled, Box } from '@mui/system' +import React, { useState, useEffect } from 'react'; +import { styled, Box } from '@mui/system'; import { Drawer, List, @@ -8,22 +8,22 @@ import { Typography, ButtonBase, Button, - Tooltip -} from '@mui/material' -import VideoCallIcon from '@mui/icons-material/VideoCall' -import VideoModal from './VideoPublishModal' -import { useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import { AudioModal } from './AudioPublishModal' -import AudioFileIcon from '@mui/icons-material/AudioFile' + Tooltip, +} from '@mui/material'; +import VideoCallIcon from '@mui/icons-material/VideoCall'; +import VideoModal from './VideoPublishModal'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import { AudioModal } from './AudioPublishModal'; +import AudioFileIcon from '@mui/icons-material/AudioFile'; interface VideoPanelProps { - onSelect: (video: Video) => void - height?: string - width?: string + onSelect: (video: Video) => void; + height?: string; + width?: string; } interface VideoApiResponse { - videos: Video[] + videos: Video[]; } const Panel = styled('div')` @@ -49,7 +49,7 @@ const Panel = styled('div')` &::-webkit-scrollbar-thumb:hover { background-color: #555; } -` +`; const PublishButton = styled(Button)` /* position: absolute; @@ -58,25 +58,19 @@ const PublishButton = styled(Button)` right: 0; margin: auto; */ max-width: 80%; -` +`; -export const AudioPanel: React.FC = ({ - onSelect, - height, - width -}) => { - const [isOpen, setIsOpen] = useState(false) - const [videos, setVideos] = useState([]) - const [isOpenVideoModal, setIsOpenVideoModal] = useState(false) - const { user } = useSelector((state: RootState) => state.auth) - const [editVideoIdentifier, setEditVideoIdentifier] = useState< - string | null | undefined - >() +export const AudioPanel: React.FC = ({ onSelect, height, width }) => { + const [isOpen, setIsOpen] = useState(false); + const [videos, setVideos] = useState([]); + const [isOpenVideoModal, setIsOpenVideoModal] = useState(false); + const { user } = useSelector((state: RootState) => state.auth); + const [editVideoIdentifier, setEditVideoIdentifier] = useState(); const fetchVideos = React.useCallback(async (): Promise => { - if (!user?.name) return [] + if (!user?.name) return []; - let res = [] + let res = []; try { // res = await qortalRequest({ // action: 'LIST_QDN_RESOURCES', @@ -88,34 +82,34 @@ export const AudioPanel: React.FC = ({ // reverse: true // }) const res2 = await fetch( - `/arbitrary/resources?&service=AUDIO&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true` - ) - const resData = await res2.json() + `/arbitrary/resources?&service=AUDIO&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`, + ); + const resData = await res2.json(); if (Array.isArray(resData)) { - res = resData + res = resData; } } catch (error) {} // Replace this URL with the actual API endpoint - return res - }, [user]) + return res; + }, [user]); useEffect(() => { - fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)) - }, []) + fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)); + }, []); const handleToggle = () => { - setIsOpen(!isOpen) - } + setIsOpen(!isOpen); + }; const handleClick = (video: Video) => { - onSelect(video) - } + onSelect(video); + }; return ( @@ -124,7 +118,7 @@ export const AudioPanel: React.FC = ({ sx={{ height: height || '30px', width: width || 'auto', - cursor: 'pointer' + cursor: 'pointer', }} > @@ -133,12 +127,12 @@ export const AudioPanel: React.FC = ({ open={isOpen} onClose={handleToggle} ModalProps={{ - keepMounted: true // Better performance on mobile + keepMounted: true, // Better performance on mobile }} sx={{ '& .MuiPaper-root': { - width: '400px' - } + width: '400px', + }, }} > @@ -147,21 +141,13 @@ export const AudioPanel: React.FC = ({ display: 'flex', flexDirection: 'column', alignItems: 'center', - flex: '0 0' + flex: '0 0', }} > - + Select Audio - + List of audios in QDN under your name @@ -172,15 +158,12 @@ export const AudioPanel: React.FC = ({ display: 'flex', flexDirection: 'column', flex: '1', - overflow: 'auto' + overflow: 'auto', }} > {videos.map((video) => ( - handleClick(video)} - sx={{ width: '100%' }} - > + handleClick(video)} sx={{ width: '100%' }}> = ({ size="small" variant="contained" onClick={() => { - setEditVideoIdentifier(video.identifier) - setIsOpenVideoModal(true) + setEditVideoIdentifier(video.identifier); + setIsOpenVideoModal(true); }} > Edit @@ -204,14 +187,14 @@ export const AudioPanel: React.FC = ({ width: '100%', display: 'flex', justifyContent: 'center', - flex: '0 0 50px' + flex: '0 0 50px', }} > { - setEditVideoIdentifier(null) - setIsOpenVideoModal(true) + setEditVideoIdentifier(null); + setIsOpenVideoModal(true); }} > Publish new audio file @@ -221,33 +204,33 @@ export const AudioPanel: React.FC = ({ { - setIsOpenVideoModal(false) - setEditVideoIdentifier(null) + setIsOpenVideoModal(false); + setEditVideoIdentifier(null); }} open={isOpenVideoModal} onPublish={(value) => { - fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)) - setIsOpenVideoModal(false) + fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)); + setIsOpenVideoModal(false); }} editVideoIdentifier={editVideoIdentifier} /> - ) -} + ); +}; // Add this to your 'types.ts' file export interface Video { - name: string - service: string - identifier: string + name: string; + service: string; + identifier: string; metadata: { - title: string - description: string - tags: string[] - category: string - categoryName: string - } - size: number - created: number - updated: number + title: string; + description: string; + tags: string[]; + category: string; + categoryName: string; + }; + size: number; + created: number; + updated: number; } diff --git a/src/components/common/AudioPlayer.tsx b/src/components/common/AudioPlayer.tsx index 7cac33e..9b75f1a 100644 --- a/src/components/common/AudioPlayer.tsx +++ b/src/components/common/AudioPlayer.tsx @@ -1,23 +1,14 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react' -import { Box, IconButton, Slider } from '@mui/material' -import { CircularProgress, Typography } from '@mui/material' -import AudioPlyr from 'philliplm-react-modern-audio-player' -import LinearProgress from '@mui/material/LinearProgress' +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, IconButton, Slider } from '@mui/material'; +import { CircularProgress, Typography } from '@mui/material'; +import AudioPlyr from 'philliplm-react-modern-audio-player'; +import LinearProgress from '@mui/material/LinearProgress'; -import { - PlayArrow, - Pause, - VolumeUp, - Fullscreen, - PictureInPicture -} from '@mui/icons-material' -import { styled } from '@mui/system' -import { - removeAudio, - setShowingAudioPlayer -} from '../../state/features/globalSlice' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' +import { PlayArrow, Pause, VolumeUp, Fullscreen, PictureInPicture } from '@mui/icons-material'; +import { styled } from '@mui/system'; +import { removeAudio, setShowingAudioPlayer } from '../../state/features/globalSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; const VideoContainer = styled(Box)` position: relative; @@ -29,13 +20,13 @@ const VideoContainer = styled(Box)` height: 100%; margin: 20px 0px; z-index: 501; -` +`; const VideoElement = styled('video')` width: 100%; height: auto; background: rgb(33, 33, 33); -` +`; const ControlsContainer = styled(Box)` position: absolute; @@ -47,86 +38,78 @@ const ControlsContainer = styled(Box)` right: 0; padding: 8px; background-color: rgba(0, 0, 0, 0.6); -` +`; interface VideoPlayerProps { - src?: string - poster?: string - name?: string - identifier?: string - service?: string - autoplay?: boolean - title?: string - description?: string - playlist?: IPlaylist[] - currAudio: number | null + src?: string; + poster?: string; + name?: string; + identifier?: string; + service?: string; + autoplay?: boolean; + title?: string; + description?: string; + playlist?: IPlaylist[]; + currAudio: number | null; } export interface IPlaylist { - name: string - identifier: string - service: string - title: string - description: string + name: string; + identifier: string; + service: string; + title: string; + description: string; } interface CustomWindow extends Window { - _qdnTheme: any // Replace 'any' with the appropriate type if you know it + _qdnTheme: any; // Replace 'any' with the appropriate type if you know it } -const customWindow = window as unknown as CustomWindow -const themeColor = customWindow?._qdnTheme +const customWindow = window as unknown as CustomWindow; +const themeColor = customWindow?._qdnTheme; export const AudioPlayer: React.FC = ({ currAudio }) => { - const [isLoading, setIsLoading] = useState(false) - const { downloads, showingAudioPlayer } = useSelector( - (state: RootState) => state.global - ) - const dispatch = useDispatch() + const [isLoading, setIsLoading] = useState(false); + const { downloads, showingAudioPlayer } = useSelector((state: RootState) => state.global); + const dispatch = useDispatch(); const downloadsLength: number = useMemo( () => Object.keys(downloads) .map((item) => { - return downloads[item] + return downloads[item]; }) .filter( (download: any) => - download?.service === 'AUDIO' && - download?.status?.status === 'READY' && - !!download.url + download?.service === 'AUDIO' && download?.status?.status === 'READY' && !!download.url, ).length, - [downloads] - ) + [downloads], + ); const audioPlayList = useMemo(() => { const filterAudios = Object.keys(downloads) .map((item) => { - return downloads[item] + return downloads[item]; }) .filter( (download: any) => - download?.service === 'AUDIO' && - download?.url && - download?.status?.status === 'READY' - ) + download?.service === 'AUDIO' && download?.url && download?.status?.status === 'READY', + ); return filterAudios.map((audio: any, index: number) => { return { name: audio?.blogPost?.audioTitle, src: audio?.url, id: index + 1, identifier: audio?.identifier, - description: audio?.blogPost?.audioDescription || '' - } - }) - }, [downloadsLength]) + description: audio?.blogPost?.audioDescription || '', + }; + }); + }, [downloadsLength]); const currAudioMemo: number | null = useMemo(() => { - const findIndex = audioPlayList.findIndex( - (item) => item?.identifier === currAudio - ) + const findIndex = audioPlayList.findIndex((item) => item?.identifier === currAudio); if (findIndex !== -1) { - return findIndex + return findIndex; } - return null - }, [audioPlayList, currAudio]) + return null; + }, [audioPlayList, currAudio]); if (isLoading) return ( @@ -142,45 +125,45 @@ export const AudioPlayer: React.FC = ({ currAudio }) => { display: 'flex', flexDirection: 'column', justifyContent: 'center', - alignItems: 'flex-start' + alignItems: 'flex-start', }} > Loading playlist... - ) + ); - if (audioPlayList.length === 0 || !showingAudioPlayer) return null + if (audioPlayList.length === 0 || !showingAudioPlayer) return null; return ( { - dispatch(setShowingAudioPlayer(false)) + dispatch(setShowingAudioPlayer(false)); }} // rootContainerProps={{ // colorScheme: theme, @@ -188,5 +171,5 @@ export const AudioPlayer: React.FC = ({ currAudio }) => { // }} /> - ) -} + ); +}; diff --git a/src/components/common/AudioPublishModal.tsx b/src/components/common/AudioPublishModal.tsx index f733868..ea29406 100644 --- a/src/components/common/AudioPublishModal.tsx +++ b/src/components/common/AudioPublishModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState } from 'react'; import { Box, Button, @@ -12,28 +12,28 @@ import { SelectChangeEvent, OutlinedInput, Chip, - IconButton -} from '@mui/material' -import { styled } from '@mui/system' -import { useDropzone } from 'react-dropzone' -import { toBase64 } from '../../utils/toBase64' -import AddIcon from '@mui/icons-material/Add' -import CloseIcon from '@mui/icons-material/Close' -import { usePublishAudio } from './PublishAudio' + IconButton, +} from '@mui/material'; +import { styled } from '@mui/system'; +import { useDropzone } from 'react-dropzone'; +import { toBase64 } from '../../utils/toBase64'; +import AddIcon from '@mui/icons-material/Add'; +import CloseIcon from '@mui/icons-material/Close'; +import { usePublishAudio } from './PublishAudio'; const StyledModal = styled(Modal)(({ theme }) => ({ display: 'flex', alignItems: 'center', - justifyContent: 'center' -})) + justifyContent: 'center', +})); const ChipContainer = styled(Box)({ display: 'flex', flexWrap: 'wrap', '& > *': { - margin: '4px' - } -}) + margin: '4px', + }, +}); const ModalContent = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -41,58 +41,55 @@ const ModalContent = styled(Box)(({ theme }) => ({ borderRadius: theme.spacing(1), width: '40%', '&:focus': { - outline: 'none' - } -})) + outline: 'none', + }, +})); interface VideoModalProps { - open: boolean - onClose: () => void - onPublish: (value: any) => void - editVideoIdentifier?: string | null | undefined + open: boolean; + onClose: () => void; + onPublish: (value: any) => void; + editVideoIdentifier?: string | null | undefined; } interface SelectOption { - id: string - name: string + id: string; + name: string; } -async function addAudioCoverImage( - base64Audio: string, - coverImageBase64: string -): Promise { +async function addAudioCoverImage(base64Audio: string, coverImageBase64: string): Promise { // Decode the base64 audio data const audioData: Uint8Array = new Uint8Array( atob(base64Audio) .split('') - .map((char) => char.charCodeAt(0)) - ) + .map((char) => char.charCodeAt(0)), + ); - const decoder: TextDecoder = new TextDecoder('utf-8') - const decodedAudioData: string = decoder.decode(audioData) + const decoder: TextDecoder = new TextDecoder('utf-8'); + const decodedAudioData: string = decoder.decode(audioData); // Create a Blob object from the decoded audio data - const blob: Blob = new Blob([decodedAudioData], { type: 'audio/mpeg' }) + const blob: Blob = new Blob([decodedAudioData], { type: 'audio/mpeg' }); // Create a new file name for the audio with cover image - const fileName: string = 'audio-with-cover.mp3' + const fileName: string = 'audio-with-cover.mp3'; // Create a new FormData object to hold the file and metadata - const formData: FormData = new FormData() - formData.append('file', blob, fileName) + const formData: FormData = new FormData(); + formData.append('file', blob, fileName); // Create a new image object from the base64 data - const image: HTMLImageElement = new Image() - image.src = `data:image/png;base64,${coverImageBase64}` + const image: HTMLImageElement = new Image(); + image.src = `data:image/png;base64,${coverImageBase64}`; // Wait for the image to load before getting its dimensions await new Promise((resolve) => { - image.onload = () => resolve(null) - }) + image.onload = () => resolve(null); + }); // Get the image dimensions - const width: number = image.width - const height: number = image.height + const width: number = image.width; + const height: number = image.height; // Create a new metadata object with the image dimensions const metadata: any = { @@ -106,110 +103,106 @@ async function addAudioCoverImage( description: 'Cover Image', data: coverImageBase64, width: width, - height: height - } - } + height: height, + }, + }; // Set the metadata on the file - formData.set('metadata', JSON.stringify(metadata)) + formData.set('metadata', JSON.stringify(metadata)); // Create a new URL object for the file - const url: string = URL.createObjectURL(blob) + const url: string = URL.createObjectURL(blob); // Create a download link for the file - const link: HTMLAnchorElement = document.createElement('a') - link.href = url - link.download = fileName - link.click() + const link: HTMLAnchorElement = document.createElement('a'); + link.href = url; + link.download = fileName; + link.click(); // Read the downloaded file and return its contents as a base64 string - const fileReader: FileReader = new FileReader() - fileReader.readAsDataURL(blob) + const fileReader: FileReader = new FileReader(); + fileReader.readAsDataURL(blob); return await new Promise((resolve, reject) => { fileReader.onload = () => { - const base64: string | undefined = fileReader.result?.toString() + const base64: string | undefined = fileReader.result?.toString(); if (base64 !== undefined) { - resolve(base64) + resolve(base64); } else { - reject(new Error('Failed to read downloaded file.')) + reject(new Error('Failed to read downloaded file.')); } - } - fileReader.onerror = () => reject(fileReader.error) - }) + }; + fileReader.onerror = () => reject(fileReader.error); + }); } export const AudioModal: React.FC = ({ open, onClose, onPublish, - editVideoIdentifier + editVideoIdentifier, }) => { - const [file, setFile] = useState(null) - const [title, setTitle] = useState('') - const [description, setDescription] = useState('') - const [selectedOption, setSelectedOption] = useState( - null - ) - const [inputValue, setInputValue] = useState('') - const [chips, setChips] = useState([]) + const [file, setFile] = useState(null); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [selectedOption, setSelectedOption] = useState(null); + const [inputValue, setInputValue] = useState(''); + const [chips, setChips] = useState([]); - const [options, setOptions] = useState([]) - const [tags, setTags] = useState([]) - const { publishAudio } = usePublishAudio() + const [options, setOptions] = useState([]); + const [tags, setTags] = useState([]); + const { publishAudio } = usePublishAudio(); const { getRootProps, getInputProps } = useDropzone({ accept: { - 'audio/*': [] + 'audio/*': [], }, maxFiles: 1, onDrop: (acceptedFiles) => { - setFile(acceptedFiles[0]) - } - }) + setFile(acceptedFiles[0]); + }, + }); const handleTitleChange = (event: React.ChangeEvent) => { - setTitle(event.target.value) - } + setTitle(event.target.value); + }; - const handleDescriptionChange = ( - event: React.ChangeEvent - ) => { - setDescription(event.target.value) - } + const handleDescriptionChange = (event: React.ChangeEvent) => { + setDescription(event.target.value); + }; const handleOptionChange = (event: SelectChangeEvent) => { - const optionId = event.target.value - const selectedOption = options.find((option) => option.id === optionId) - setSelectedOption(selectedOption || null) - } + const optionId = event.target.value; + const selectedOption = options.find((option) => option.id === optionId); + setSelectedOption(selectedOption || null); + }; const handleChipDelete = (index: number) => { - const newChips = [...chips] - newChips.splice(index, 1) - setChips(newChips) - } + const newChips = [...chips]; + newChips.splice(index, 1); + setChips(newChips); + }; const handleSubmit = async () => { - const missingFields = [] + const missingFields = []; - if (!title) missingFields.push('title') - if (!file) missingFields.push('file') + if (!title) missingFields.push('title'); + if (!file) missingFields.push('file'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; - return + return; } - if (!file) return + if (!file) return; - const formattedTags: { [key: string]: string } = {} + const formattedTags: { [key: string]: string } = {}; chips.forEach((tag, i) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); try { - const base64 = await toBase64(file) - if (typeof base64 !== 'string') return - const base64String = base64.split(',')[1] + const base64 = await toBase64(file); + if (typeof base64 !== 'string') return; + const base64String = base64.split(',')[1]; const res = await publishAudio({ editVideoIdentifier, @@ -217,63 +210,61 @@ export const AudioModal: React.FC = ({ description, base64: base64String, category: selectedOption?.id || '', - ...formattedTags - }) - onPublish(res) - setFile(null) - setTitle('') - setDescription('') - onClose() + ...formattedTags, + }); + onPublish(res); + setFile(null); + setTitle(''); + setDescription(''); + onClose(); } catch (error) {} - } + }; const handleInputChange = (event: any) => { - setInputValue(event.target.value) - } + setInputValue(event.target.value); + }; const handleInputKeyDown = (event: any) => { if (event.key === 'Enter' && inputValue !== '') { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } else { - event.preventDefault() + event.preventDefault(); } } - } + }; const addChip = () => { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } - } + }; const getListCategories = React.useCallback(async () => { try { - const url = `/arbitrary/categories` + const url = `/arbitrary/categories`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - setOptions(responseData) + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + setOptions(responseData); } catch (error) {} - }, []) + }, []); React.useEffect(() => { - getListCategories() - }, [getListCategories]) + getListCategories(); + }, [getListCategories]); return ( {editVideoIdentifier && ( - - You are editing: {editVideoIdentifier} - + You are editing: {editVideoIdentifier} )} Upload Audio @@ -284,14 +275,12 @@ export const AudioModal: React.FC = ({ border: '1px dashed gray', padding: 2, textAlign: 'center', - marginBottom: 2 + marginBottom: 2, }} > - {file - ? file.name - : 'Drag and drop an audio file here or click to select a file'} + {file ? file.name : 'Drag and drop an audio file here or click to select a file'} = ({ - ) -} + ); +}; diff --git a/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts b/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts index 05ebf6e..06403f1 100644 --- a/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts +++ b/src/components/common/BlockedNamesModal/BlockedNamesModal-styles.ts @@ -1,15 +1,11 @@ import { styled } from '@mui/system'; -import { - Box, - Modal, - Typography -} from '@mui/material'; +import { Box, Modal, Typography } from '@mui/material'; export const StyledModal = styled(Modal)(({ theme }) => ({ display: 'flex', alignItems: 'center', - justifyContent: 'center' -})) + justifyContent: 'center', +})); export const ModalContent = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.primary.main, @@ -17,12 +13,12 @@ export const ModalContent = styled(Box)(({ theme }) => ({ borderRadius: theme.spacing(1), width: '40%', '&:focus': { - outline: 'none' - } -})) + outline: 'none', + }, +})); export const ModalText = styled(Typography)(({ theme }) => ({ - fontFamily: "Raleway", - fontSize: "25px", + fontFamily: 'Raleway', + fontSize: '25px', color: theme.palette.text.primary, -})); \ No newline at end of file +})); diff --git a/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx b/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx index 58c552d..43a3868 100644 --- a/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx +++ b/src/components/common/BlockedNamesModal/BlockedNamesModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState } from 'react'; import { Box, Button, @@ -7,55 +7,48 @@ import { SelectChangeEvent, ListItem, List, - useTheme -} from '@mui/material' -import { - StyledModal, - ModalContent, - ModalText -} from './BlockedNamesModal-styles' + useTheme, +} from '@mui/material'; +import { StyledModal, ModalContent, ModalText } from './BlockedNamesModal-styles'; interface PostModalProps { - open: boolean - onClose: () => void + open: boolean; + onClose: () => void; } -export const BlockedNamesModal: React.FC = ({ - open, - onClose -}) => { - const [blockedNames, setBlockedNames] = useState([]) - const theme = useTheme() +export const BlockedNamesModal: React.FC = ({ open, onClose }) => { + const [blockedNames, setBlockedNames] = useState([]); + const theme = useTheme(); const getBlockedNames = React.useCallback(async () => { try { - const listName = `blockedNames_q-blog` + const listName = `blockedNames_q-blog`; const response = await qortalRequest({ action: 'GET_LIST_ITEMS', - list_name: listName - }) - setBlockedNames(response) + list_name: listName, + }); + setBlockedNames(response); } catch (error) { - onClose() + onClose(); } - }, []) + }, []); React.useEffect(() => { - getBlockedNames() - }, [getBlockedNames]) + getBlockedNames(); + }, [getBlockedNames]); const removeFromBlockList = async (name: string) => { try { const response = await qortalRequest({ action: 'DELETE_LIST_ITEM', list_name: 'blockedNames_q-blog', - item: name - }) + item: name, + }); if (response === true) { - setBlockedNames((prev) => prev.filter((n) => n !== name)) + setBlockedNames((prev) => prev.filter((n) => n !== name)); } } catch (error) {} - } + }; return ( @@ -67,14 +60,14 @@ export const BlockedNamesModal: React.FC = ({ display: 'flex', flexDirection: 'column', flex: '1', - overflow: 'auto' + overflow: 'auto', }} > {blockedNames.map((name, index) => ( {name} @@ -82,7 +75,7 @@ export const BlockedNamesModal: React.FC = ({ sx={{ backgroundColor: theme.palette.primary.light, color: theme.palette.text.primary, - fontFamily: 'Arial' + fontFamily: 'Arial', }} onClick={() => removeFromBlockList(name)} > @@ -96,5 +89,5 @@ export const BlockedNamesModal: React.FC = ({ - ) -} + ); +}; diff --git a/src/components/common/Comments/Comment.tsx b/src/components/common/Comments/Comment.tsx index fa2ffe5..b4cff6f 100644 --- a/src/components/common/Comments/Comment.tsx +++ b/src/components/common/Comments/Comment.tsx @@ -7,43 +7,38 @@ import { DialogContent, DialogTitle, Typography, - useTheme -} from '@mui/material' -import React, { useCallback, useState } from 'react' -import { CommentEditor } from './CommentEditor' -import { CardContentContainerComment } from '../../../pages/BlogList/PostPreview-styles' -import { StyledCardHeaderComment } from '../../../pages/BlogList/PostPreview-styles' -import { StyledCardColComment } from '../../../pages/BlogList/PostPreview-styles' -import { AuthorTextComment } from '../../../pages/BlogList/PostPreview-styles' -import { StyledCardContentComment } from '../../../pages/BlogList/PostPreview-styles' -import { useSelector } from 'react-redux' -import { RootState } from '../../../state/store' -import Portal from '../Portal' -import { Tipping } from '../Tipping/Tipping' -import { formatDate } from '../../../utils/time' + useTheme, +} from '@mui/material'; +import React, { useCallback, useState } from 'react'; +import { CommentEditor } from './CommentEditor'; +import { CardContentContainerComment } from '../../../pages/BlogList/PostPreview-styles'; +import { StyledCardHeaderComment } from '../../../pages/BlogList/PostPreview-styles'; +import { StyledCardColComment } from '../../../pages/BlogList/PostPreview-styles'; +import { AuthorTextComment } from '../../../pages/BlogList/PostPreview-styles'; +import { StyledCardContentComment } from '../../../pages/BlogList/PostPreview-styles'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../state/store'; +import Portal from '../Portal'; +import { Tipping } from '../Tipping/Tipping'; +import { formatDate } from '../../../utils/time'; interface CommentProps { - comment: any - postId: string - postName: string - onSubmit: (obj?: any, isEdit?: boolean) => void + comment: any; + postId: string; + postName: string; + onSubmit: (obj?: any, isEdit?: boolean) => void; } -export const Comment = ({ - comment, - postId, - postName, - onSubmit -}: CommentProps) => { - const [isReplying, setIsReplying] = useState(false) - const [isEditing, setIsEditing] = useState(false) - const { user } = useSelector((state: RootState) => state.auth) - const [currentEdit, setCurrentEdit] = useState(null) - const theme = useTheme() +export const Comment = ({ comment, postId, postName, onSubmit }: CommentProps) => { + const [isReplying, setIsReplying] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const { user } = useSelector((state: RootState) => state.auth); + const [currentEdit, setCurrentEdit] = useState(null); + const theme = useTheme(); const handleSubmit = useCallback((comment: any, isEdit?: boolean) => { - onSubmit(comment, isEdit) - setCurrentEdit(null) - setIsReplying(false) - }, []) + onSubmit(comment, isEdit); + setCurrentEdit(null); + setIsReplying(false); + }, []); return ( {currentEdit && ( @@ -68,7 +63,7 @@ export const Comment = ({ sx={{ width: '300px', display: 'flex', - justifyContent: 'center' + justifyContent: 'center', }} > {comment?.created && ( @@ -116,7 +111,7 @@ export const Comment = ({ variant="h6" sx={{ fontSize: '12px', - marginLeft: '5px' + marginLeft: '5px', }} color={theme.palette.text.primary} > @@ -127,22 +122,14 @@ export const Comment = ({ sx={{ display: 'flex', alignItems: 'center', - gap: '5px' + gap: '5px', }} > - {user?.name === comment?.name && ( - )} @@ -151,8 +138,8 @@ export const Comment = ({ size="small" variant="contained" onClick={() => { - setIsReplying(false) - setIsEditing(false) + setIsReplying(false); + setIsEditing(false); }} > close @@ -169,7 +156,7 @@ export const Comment = ({ display: 'flex', width: '100%', flexDirection: 'column', - alignItems: 'center' + alignItems: 'center', }} > {isReplying && ( @@ -183,21 +170,14 @@ export const Comment = ({ )} - ) -} + ); +}; -const CommentCard = ({ - message, - created, - name, - replies, - children, - setCurrentEdit -}: any) => { - const [avatarUrl, setAvatarUrl] = React.useState('') - const { user } = useSelector((state: RootState) => state.auth) +const CommentCard = ({ message, created, name, replies, children, setCurrentEdit }: any) => { + const [avatarUrl, setAvatarUrl] = React.useState(''); + const { user } = useSelector((state: RootState) => state.auth); - const theme = useTheme() + const theme = useTheme(); const getAvatar = React.useCallback(async (author: string) => { try { @@ -205,23 +185,23 @@ const CommentCard = ({ action: 'GET_QDN_RESOURCE_URL', name: author, service: 'THUMBNAIL', - identifier: 'qortal_avatar' - }) + identifier: 'qortal_avatar', + }); - setAvatarUrl(url) + setAvatarUrl(url); } catch (error) {} - }, []) + }, []); React.useEffect(() => { - getAvatar(name) - }, [name]) + getAvatar(name); + }, [name]); return ( @@ -229,11 +209,7 @@ const CommentCard = ({ {name} @@ -257,7 +233,7 @@ const CommentCard = ({ color={theme.palette.text.primary} sx={{ fontSize: '16px', - wordBreak: 'break-word' + wordBreak: 'break-word', }} > {message} @@ -267,7 +243,7 @@ const CommentCard = ({ sx={{ paddingLeft: '15px', display: 'flex', - flexDirection: 'column' + flexDirection: 'column', }} > {replies?.map((reply: any) => { @@ -279,7 +255,7 @@ const CommentCard = ({ display: 'flex', border: '1px solid grey', borderRadius: '10px', - marginTop: '8px' + marginTop: '8px', }} > {reply?.created && ( @@ -300,7 +276,7 @@ const CommentCard = ({ variant="h6" sx={{ fontSize: '12px', - marginLeft: '5px' + marginLeft: '5px', }} color={theme.palette.text.primary} > @@ -315,7 +291,7 @@ const CommentCard = ({ sx={{ width: '30px', alignSelf: 'flex-end', - background: theme.palette.primary.light + background: theme.palette.primary.light, }} > edit @@ -327,10 +303,10 @@ const CommentCard = ({ {/* {reply?.message} */} - ) + ); })} {children} - ) -} + ); +}; diff --git a/src/components/common/Comments/CommentEditor.tsx b/src/components/common/Comments/CommentEditor.tsx index b3602a2..b2bef0e 100644 --- a/src/components/common/Comments/CommentEditor.tsx +++ b/src/components/common/Comments/CommentEditor.tsx @@ -1,96 +1,90 @@ -import { Box, Button, TextField } from '@mui/material' -import React, { useEffect, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../../state/store' -import ShortUniqueId from 'short-unique-id' -import { setNotification } from '../../../state/features/notificationsSlice' -import { toBase64 } from '../../../utils/toBase64' -import localforage from 'localforage' -const uid = new ShortUniqueId() +import { Box, Button, TextField } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../../state/store'; +import ShortUniqueId from 'short-unique-id'; +import { setNotification } from '../../../state/features/notificationsSlice'; +import { toBase64 } from '../../../utils/toBase64'; +import localforage from 'localforage'; +const uid = new ShortUniqueId(); const notification = localforage.createInstance({ - name: 'notification' -}) + name: 'notification', +}); -const MAX_ITEMS = 10 +const MAX_ITEMS = 10; export interface Item { - id: string - lastSeen: number - postId: string - postName: string + id: string; + lastSeen: number; + postId: string; + postName: string; } export async function addItem(item: Item): Promise { // Get all items - let notificationComments: Item[] = - (await notification.getItem('comments')) || [] + let notificationComments: Item[] = (await notification.getItem('comments')) || []; // Find the item with the same id, if it exists - let existingItemIndex = notificationComments.findIndex( - (i) => i.id === item.id - ) + let existingItemIndex = notificationComments.findIndex((i) => i.id === item.id); if (existingItemIndex !== -1) { // If the item exists, update its date - notificationComments[existingItemIndex].lastSeen = item.lastSeen + notificationComments[existingItemIndex].lastSeen = item.lastSeen; } else { // If the item doesn't exist, add it - notificationComments.push(item) + notificationComments.push(item); // If adding the item has caused us to exceed the max number of items, remove the oldest one if (notificationComments.length > MAX_ITEMS) { - notificationComments.sort((a, b) => b.lastSeen - a.lastSeen) // sort items by date, newest first - notificationComments.pop() // remove the oldest item + notificationComments.sort((a, b) => b.lastSeen - a.lastSeen); // sort items by date, newest first + notificationComments.pop(); // remove the oldest item } } // Store the items back into localForage - await notification.setItem('comments', notificationComments) + await notification.setItem('comments', notificationComments); } export async function updateItemDate(item: any): Promise { // Get all items - let notificationComments: Item[] = - (await notification.getItem('comments')) || [] + let notificationComments: Item[] = (await notification.getItem('comments')) || []; - let notificationCreatorComment: any = - (await notification.getItem('post-comments')) || {} - const findPostId = notificationCreatorComment[item.postId] + let notificationCreatorComment: any = (await notification.getItem('post-comments')) || {}; + const findPostId = notificationCreatorComment[item.postId]; if (findPostId) { - notificationCreatorComment[item.postId].lastSeen = item.lastSeen + notificationCreatorComment[item.postId].lastSeen = item.lastSeen; } // Find the item with the same id, if it exists notificationComments.forEach((nc, index) => { if (nc.postId === item.postId) { - notificationComments[index].lastSeen = item.lastSeen + notificationComments[index].lastSeen = item.lastSeen; } - }) + }); // Store the items back into localForage - await notification.setItem('comments', notificationComments) - await notification.setItem('post-comments', notificationCreatorComment) + await notification.setItem('comments', notificationComments); + await notification.setItem('post-comments', notificationCreatorComment); } interface CommentEditorProps { - postId: string - postName: string - onSubmit: (obj: any) => void - isReply?: boolean - commentId?: string - isEdit?: boolean - commentMessage?: string + postId: string; + postName: string; + onSubmit: (obj: any) => void; + isReply?: boolean; + commentId?: string; + isEdit?: boolean; + commentMessage?: string; } function utf8ToBase64(inputString: string): string { // Encode the string as UTF-8 - const utf8String = encodeURIComponent(inputString).replace( - /%([0-9A-F]{2})/g, - (match, p1) => String.fromCharCode(Number('0x' + p1)) - ) + const utf8String = encodeURIComponent(inputString).replace(/%([0-9A-F]{2})/g, (match, p1) => + String.fromCharCode(Number('0x' + p1)), + ); // Convert the UTF-8 encoded string to base64 - const base64String = btoa(utf8String) - return base64String + const base64String = btoa(utf8String); + return base64String; } export const CommentEditor = ({ @@ -100,129 +94,122 @@ export const CommentEditor = ({ isReply, commentId, isEdit, - commentMessage + commentMessage, }: CommentEditorProps) => { - const [value, setValue] = useState('') - const dispatch = useDispatch() - const { user } = useSelector((state: RootState) => state.auth) - const notifications = useSelector( - (state: RootState) => state.global.notifications - ) + const [value, setValue] = useState(''); + const dispatch = useDispatch(); + const { user } = useSelector((state: RootState) => state.auth); + const notifications = useSelector((state: RootState) => state.global.notifications); useEffect(() => { if (isEdit && commentMessage) { - setValue(commentMessage) + setValue(commentMessage); } - }, [isEdit, commentMessage]) + }, [isEdit, commentMessage]); - const publishComment = async ( - identifier: string, - idForNotification?: string - ) => { - let address - let name - let errorMsg = '' + const publishComment = async (identifier: string, idForNotification?: string) => { + let address; + let name; + let errorMsg = ''; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ''; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = 'Cannot post without a name'; } if (value.length > 200) { - errorMsg = 'Comment needs to be under 200 characters' + errorMsg = 'Comment needs to be under 200 characters'; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) + alertType: 'error', + }), + ); + throw new Error(errorMsg); } try { - const base64 = utf8ToBase64(value) + const base64 = utf8ToBase64(value); const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', name: name, service: 'BLOG_COMMENT', data64: base64, - identifier: identifier - }) + identifier: identifier, + }); dispatch( setNotification({ msg: 'Comment successfully published', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); if (idForNotification) { addItem({ id: idForNotification, lastSeen: Date.now(), postId, - postName: postName - }) + postName: postName, + }); } - return resourceResponse + return resourceResponse; } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to publish comment', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to publish comment', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to publish comment', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) throw new Error('Failed to publish comment') + if (!notificationObj) throw new Error('Failed to publish comment'); - dispatch(setNotification(notificationObj)) - throw new Error('Failed to publish comment') + dispatch(setNotification(notificationObj)); + throw new Error('Failed to publish comment'); } - } + }; const handleSubmit = async () => { try { - const id = uid() + const id = uid(); - let identifier = `qcomment_v1_qblog_${postId.slice(-12)}_${id}` - let idForNotification = identifier + let identifier = `qcomment_v1_qblog_${postId.slice(-12)}_${id}`; + let idForNotification = identifier; if (isReply && commentId) { - identifier = `qcomment_v1_qblog_${postId.slice( - -12 - )}_reply_${commentId.slice(-6)}_${id}` - idForNotification = commentId + identifier = `qcomment_v1_qblog_${postId.slice(-12)}_reply_${commentId.slice(-6)}_${id}`; + idForNotification = commentId; } if (isEdit && commentId) { - identifier = commentId + identifier = commentId; } - await publishComment(identifier, idForNotification) + await publishComment(identifier, idForNotification); onSubmit({ created: Date.now(), identifier, message: value, service: 'BLOG_COMMENT', - name: user?.name - }) - setValue('') + name: user?.name, + }); + setValue(''); } catch (error) {} - } + }; return ( setValue(e.target.value)} @@ -254,5 +241,5 @@ export const CommentEditor = ({ {isReply ? 'Submit reply' : isEdit ? 'Edit' : 'Submit comment'} - ) -} + ); +}; diff --git a/src/components/common/Comments/CommentSection.tsx b/src/components/common/Comments/CommentSection.tsx index 96bb93b..f0aeb0b 100644 --- a/src/components/common/Comments/CommentSection.tsx +++ b/src/components/common/Comments/CommentSection.tsx @@ -1,17 +1,17 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { CommentEditor, addItem, updateItemDate } from './CommentEditor' -import { Comment } from './Comment' -import { Box, Button, Drawer, Typography, useTheme } from '@mui/material' -import { styled } from '@mui/system' -import CloseIcon from '@mui/icons-material/Close' -import { useSelector } from 'react-redux' -import { RootState } from '../../../state/store' -import CommentIcon from '@mui/icons-material/Comment' -import { useNavigate, useLocation } from 'react-router-dom' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { CommentEditor, addItem, updateItemDate } from './CommentEditor'; +import { Comment } from './Comment'; +import { Box, Button, Drawer, Typography, useTheme } from '@mui/material'; +import { styled } from '@mui/system'; +import CloseIcon from '@mui/icons-material/Close'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../../state/store'; +import CommentIcon from '@mui/icons-material/Comment'; +import { useNavigate, useLocation } from 'react-router-dom'; interface CommentSectionProps { - postId: string - postName: string + postId: string; + postName: string; } const Panel = styled('div')` @@ -37,211 +37,204 @@ const Panel = styled('div')` &::-webkit-scrollbar-thumb:hover { background-color: #555; } -` +`; export const CommentSection = ({ postId, postName }: CommentSectionProps) => { - const navigate = useNavigate() - const location = useLocation() - const [listComments, setListComments] = useState([]) - const [isOpen, setIsOpen] = useState(false) - const { user } = useSelector((state: RootState) => state.auth) - const [newMessages, setNewMessages] = useState(0) - const notifications = useSelector( - (state: RootState) => state.global.notifications - ) + const navigate = useNavigate(); + const location = useLocation(); + const [listComments, setListComments] = useState([]); + const [isOpen, setIsOpen] = useState(false); + const { user } = useSelector((state: RootState) => state.auth); + const [newMessages, setNewMessages] = useState(0); + const notifications = useSelector((state: RootState) => state.global.notifications); const notificationCreatorComment = useSelector( - (state: RootState) => state.global.notificationCreatorComment - ) + (state: RootState) => state.global.notificationCreatorComment, + ); const fullNotifications = useMemo(() => { - return [...notificationCreatorComment, ...notifications].sort( - (a, b) => b.created - a.created - ) - }, [notificationCreatorComment, notifications]) - const theme = useTheme() + return [...notificationCreatorComment, ...notifications].sort((a, b) => b.created - a.created); + }, [notificationCreatorComment, notifications]); + const theme = useTheme(); const onSubmit = (obj?: any, isEdit?: boolean) => { if (isEdit) { setListComments((prev: any[]) => { - const findCommentIndex = prev.findIndex( - (item) => item?.identifier === obj?.identifier - ) - if (findCommentIndex === -1) return prev + const findCommentIndex = prev.findIndex((item) => item?.identifier === obj?.identifier); + if (findCommentIndex === -1) return prev; - const newArray = [...prev] - newArray[findCommentIndex] = obj - return newArray - }) + const newArray = [...prev]; + newArray[findCommentIndex] = obj; + return newArray; + }); - return + return; } setListComments((prev) => [ ...prev, { - ...obj - } - ]) - } + ...obj, + }, + ]); + }; useEffect(() => { - const query = new URLSearchParams(location.search) - let commentVar = query?.get('comment') + const query = new URLSearchParams(location.search); + let commentVar = query?.get('comment'); if (commentVar) { if (commentVar && commentVar.endsWith('/')) { - commentVar = commentVar.slice(0, -1) + commentVar = commentVar.slice(0, -1); } - setIsOpen(true) + setIsOpen(true); if (listComments.length > 0) { - const el = document.getElementById(commentVar) + const el = document.getElementById(commentVar); if (el) { - el.scrollIntoView() - el.classList.add('glow') + el.scrollIntoView(); + el.classList.add('glow'); setTimeout(() => { - el.classList.remove('glow') - }, 2000) + el.classList.remove('glow'); + }, 2000); } - navigate(location.pathname, { replace: true }) + navigate(location.pathname, { replace: true }); } } - }, [navigate, location, listComments]) + }, [navigate, location, listComments]); const getComments = useCallback( async (isNewMessages?: boolean, numberOfComments?: number) => { - let offset: number = 0 + let offset: number = 0; if (isNewMessages && numberOfComments) { - offset = numberOfComments + offset = numberOfComments; } const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=qcomment_v1_qblog_${postId.slice( - -12 - )}&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true` + -12, + )}&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - let comments: any[] = [] + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + let comments: any[] = []; for (const comment of responseData) { if (comment.identifier && comment.name) { - const url = `/arbitrary/BLOG_COMMENT/${comment.name}/${comment.identifier}` + const url = `/arbitrary/BLOG_COMMENT/${comment.name}/${comment.identifier}`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const responseData2 = await response.text() + const responseData2 = await response.text(); if (responseData) { comments.push({ message: responseData2, - ...comment - }) + ...comment, + }); } } } if (isNewMessages) { - setListComments((prev) => [...prev, ...comments]) - setNewMessages(0) + setListComments((prev) => [...prev, ...comments]); + setNewMessages(0); } else { - setListComments(comments) + setListComments(comments); } try { } catch (error) {} }, - [postId] - ) + [postId], + ); const checkAndUpdateNotification = async () => { const filteredNotifications = fullNotifications.filter( (notification) => - postId.includes(notification?.partialPostId) || - notification?.postId === postId - ) + postId.includes(notification?.partialPostId) || notification?.postId === postId, + ); filteredNotifications.forEach((notification) => { if (postId) { updateItemDate({ id: notification?.identifier, lastSeen: Date.now(), - postId - }) + postId, + }); } - }) - } + }); + }; useEffect(() => { if (fullNotifications && isOpen) { - checkAndUpdateNotification() + checkAndUpdateNotification(); } - }, [fullNotifications, isOpen]) + }, [fullNotifications, isOpen]); useEffect(() => { - getComments() - }, [getComments, postId]) + getComments(); + }, [getComments, postId]); const structuredCommentList = useMemo(() => { return listComments.reduce((acc, curr, index, array) => { if (curr?.identifier?.includes('_reply_')) { - return acc + return acc; } acc.push({ ...curr, replies: array.filter((comment) => - comment.identifier.includes(`_reply_${curr.identifier.slice(-6)}`) - ) - }) - return acc - }, []) - }, [listComments]) + comment.identifier.includes(`_reply_${curr.identifier.slice(-6)}`), + ), + }); + return acc; + }, []); + }, [listComments]); - const interval = useRef(null) + const interval = useRef(null); const checkNewComments = useCallback(async () => { try { - const offset = listComments.length + const offset = listComments.length; const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=qcomment_v1_qblog_${postId.slice( - -12 - )}&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true` + -12, + )}&limit=20&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - setNewMessages(responseData.length) + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + setNewMessages(responseData.length); } catch (error) {} - }, [listComments, postId]) + }, [listComments, postId]); const checkNewMessagesFunc = useCallback(() => { - let isCalling = false + let isCalling = false; interval.current = setInterval(async () => { - if (isCalling) return - isCalling = true - const res = await checkNewComments() - isCalling = false - }, 15000) - }, [checkNewComments]) + if (isCalling) return; + isCalling = true; + const res = await checkNewComments(); + isCalling = false; + }, 15000); + }, [checkNewComments]); useEffect(() => { - checkNewMessagesFunc() + checkNewMessagesFunc(); return () => { if (interval?.current) { - clearInterval(interval.current) + clearInterval(interval.current); } - } - }, [checkNewMessagesFunc]) + }; + }, [checkNewMessagesFunc]); return ( <> setIsOpen((prev) => !prev)} > @@ -261,7 +254,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { height: '20px', display: 'flex', alignItems: 'center', - justifyContent: 'center' + justifyContent: 'center', }} > {listComments.length < 10 ? listComments.length : '9+'} @@ -276,12 +269,12 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { open={isOpen} onClose={() => {}} ModalProps={{ - keepMounted: true // Better performance on mobile + keepMounted: true, // Better performance on mobile }} sx={{ '& .MuiPaper-root': { - width: '400px' - } + width: '400px', + }, }} > @@ -293,12 +286,12 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { justifyContent: 'space-between', flex: '0 0', padding: '10px', - width: '100%' + width: '100%', }} > {newMessages > 0 && ( @@ -312,21 +305,20 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { updateItemDate({ id: '', lastSeen: Date.now(), - postId - }) - getComments(true, listComments.length) + postId, + }); + getComments(true, listComments.length); }} variant="contained" size="small" > - Load {newMessages} new{' '} - {newMessages > 1 ? 'messages' : 'message'} + Load {newMessages} new {newMessages > 1 ? 'messages' : 'message'} )} setIsOpen(false)} /> @@ -338,7 +330,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { display: 'flex', flexDirection: 'column', flex: '1', - overflow: 'auto' + overflow: 'auto', }} > { maxWidth: '400px', width: '100%', gap: '10px', - padding: '0px 5px' + padding: '0px 5px', }} > {structuredCommentList.map((comment: any) => { @@ -361,7 +353,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { postId={postId} postName={postName} /> - ) + ); })} @@ -370,17 +362,13 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => { width: '100%', display: 'flex', justifyContent: 'center', - flex: '0 0 100px' + flex: '0 0 100px', }} > - + - ) -} + ); +}; diff --git a/src/components/common/ContextMenu/ContextMenuResource.tsx b/src/components/common/ContextMenu/ContextMenuResource.tsx index af935f8..b2c9b25 100644 --- a/src/components/common/ContextMenu/ContextMenuResource.tsx +++ b/src/components/common/ContextMenu/ContextMenuResource.tsx @@ -1,75 +1,64 @@ -import * as React from 'react' -import Menu from '@mui/material/Menu' -import MenuItem from '@mui/material/MenuItem' -import Typography from '@mui/material/Typography' -import { CopyToClipboard } from 'react-copy-to-clipboard' -import { useDispatch } from 'react-redux' -import { setNotification } from '../../../state/features/notificationsSlice' -import { Box } from '@mui/material' +import * as React from 'react'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import Typography from '@mui/material/Typography'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import { useDispatch } from 'react-redux'; +import { setNotification } from '../../../state/features/notificationsSlice'; +import { Box } from '@mui/material'; -export default function ContextMenuResource({ - children, - name, - service, - identifier, - link -}: any) { +export default function ContextMenuResource({ children, name, service, identifier, link }: any) { const [contextMenu, setContextMenu] = React.useState<{ - mouseX: number - mouseY: number - } | null>(null) - const dispatch = useDispatch() + mouseX: number; + mouseY: number; + } | null>(null); + const dispatch = useDispatch(); const handleContextMenu = (event: React.MouseEvent) => { - event.preventDefault() + event.preventDefault(); setContextMenu( contextMenu === null ? { mouseX: event.clientX + 2, - mouseY: event.clientY - 6 + mouseY: event.clientY - 6, } : // repeated contextmenu when it is already open closes it with Chrome 84 on Ubuntu // Other native context menus might behave different. // With this behavior we prevent contextmenu from the backdrop to re-locale existing context menus. - null - ) - } + null, + ); + }; const handleClose = () => { - setContextMenu(null) - } + setContextMenu(null); + }; return ( -
+
{children} { - handleClose() + handleClose(); dispatch( setNotification({ msg: 'Copied to clipboard!', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); }} > Copy Link @@ -78,5 +67,5 @@ export default function ContextMenuResource({
- ) + ); } diff --git a/src/components/common/CustomIcon.tsx b/src/components/common/CustomIcon.tsx index ae9ddd5..06927d3 100644 --- a/src/components/common/CustomIcon.tsx +++ b/src/components/common/CustomIcon.tsx @@ -1,16 +1,16 @@ -import React from 'react' -import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon' -import { styled } from '@mui/system' +import React from 'react'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import { styled } from '@mui/system'; const CustomSvgIcon: React.FC = styled(SvgIcon)(({ theme }) => ({ cursor: 'pointer', color: '#5f6368', transition: 'all 0.2s', '&:hover': { - transform: 'scale(1.1)' - } -})) as unknown as React.FC + transform: 'scale(1.1)', + }, +})) as unknown as React.FC; export const CustomIcon: React.FC = (props) => { - return -} + return ; +}; diff --git a/src/components/common/DownloadTaskManager.tsx b/src/components/common/DownloadTaskManager.tsx index afc00a8..db20877 100644 --- a/src/components/common/DownloadTaskManager.tsx +++ b/src/components/common/DownloadTaskManager.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect } from 'react'; import { Accordion, AccordionDetails, @@ -9,38 +9,35 @@ import { ListItem, ListItemIcon, Typography, - useTheme -} from '@mui/material' -import { Movie, ArrowDropDown } from '@mui/icons-material' -import { SxProps } from '@mui/system' -import { Theme } from '@mui/material/styles' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { removePrefix } from '../../utils/blogIdformats' -import { useLocation, useNavigate } from 'react-router-dom' -import AudiotrackIcon from '@mui/icons-material/Audiotrack' -import { - setCurrAudio, - setShowingAudioPlayer -} from '../../state/features/globalSlice' -import { MAIL_ATTACHMENT_SERVICE_TYPE } from '../../constants/mail' + useTheme, +} from '@mui/material'; +import { Movie, ArrowDropDown } from '@mui/icons-material'; +import { SxProps } from '@mui/system'; +import { Theme } from '@mui/material/styles'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { removePrefix } from '../../utils/blogIdformats'; +import { useLocation, useNavigate } from 'react-router-dom'; +import AudiotrackIcon from '@mui/icons-material/Audiotrack'; +import { setCurrAudio, setShowingAudioPlayer } from '../../state/features/globalSlice'; +import { MAIL_ATTACHMENT_SERVICE_TYPE } from '../../constants/mail'; type DownloadItem = { - id: string - name: string - progress: number -} + id: string; + name: string; + progress: number; +}; export const DownloadTaskManager: React.FC = () => { - const { downloads } = useSelector((state: RootState) => state.global) - const dispatch = useDispatch() - const location = useLocation() - const isMailRoute = location.pathname === '/mail' - const theme = useTheme() - const [visible, setVisible] = useState(false) - const [hidden, setHidden] = useState(true) - const navigate = useNavigate() + const { downloads } = useSelector((state: RootState) => state.global); + const dispatch = useDispatch(); + const location = useLocation(); + const isMailRoute = location.pathname === '/mail'; + const theme = useTheme(); + const [visible, setVisible] = useState(false); + const [hidden, setHidden] = useState(true); + const navigate = useNavigate(); const containerStyles: SxProps = { position: 'fixed', top: '50px', @@ -50,46 +47,46 @@ export const DownloadTaskManager: React.FC = () => { overflowY: 'auto', backgroundColor: 'background.paper', boxShadow: 2, - display: 'block' - } + display: 'block', + }; useEffect(() => { // Simulate downloads for demo purposes if (visible) { setTimeout(() => { - setHidden(true) - setVisible(false) - }, 3000) + setHidden(true); + setVisible(false); + }, 3000); } - }, [visible]) + }, [visible]); const toggleVisibility = () => { - setVisible(true) - setHidden(false) - } + setVisible(true); + setHidden(false); + }; useEffect(() => { - if (Object.keys(downloads).length === 0) return - setVisible(true) - setHidden(false) - }, [downloads]) + if (Object.keys(downloads).length === 0) return; + setVisible(true); + setHidden(false); + }, [downloads]); - if (isMailRoute) return null + if (isMailRoute) return null; if ( !downloads || Object.keys(downloads).filter( - (item) => downloads[item].service !== MAIL_ATTACHMENT_SERVICE_TYPE + (item) => downloads[item].service !== MAIL_ATTACHMENT_SERVICE_TYPE, ).length === 0 ) - return null + return null; return ( { backgroundColor: theme.palette.primary.light, '&.MuiAccordionSummary-content': { padding: 0, - margin: 0 + margin: 0, }, '&.Mui-expanded': { minHeight: 'unset', - height: '36px' - } + height: '36px', + }, }} > Downloads @@ -122,25 +119,22 @@ export const DownloadTaskManager: React.FC = () => { {Object.keys(downloads) - .filter( - (item) => - downloads[item].service !== MAIL_ATTACHMENT_SERVICE_TYPE - ) + .filter((item) => downloads[item].service !== MAIL_ATTACHMENT_SERVICE_TYPE) .map((download: any) => { - const downloadObj = downloads[download] - const progress = downloads[download]?.status?.percentLoaded || 0 - const status = downloads[download]?.status?.status - const service = downloads[download]?.service + const downloadObj = downloads[download]; + const progress = downloads[download]?.status?.percentLoaded || 0; + const status = downloads[download]?.status?.status; + const service = downloads[download]?.service; return ( { background: theme.palette.primary.main, color: theme.palette.text.primary, cursor: 'pointer', - padding: '2px' + padding: '2px', }} onClick={() => { if (service === 'AUDIO' && downloadObj?.identifier) { - dispatch(setCurrAudio(downloadObj?.identifier)) - dispatch(setShowingAudioPlayer(true)) - return + dispatch(setCurrAudio(downloadObj?.identifier)); + dispatch(setShowingAudioPlayer(true)); + return; } - const str = downloadObj?.blogPost?.postId - if (!str) return - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) - navigate( - `/${downloadObj?.blogPost.user}/${blogId}/${str2}` - ) + const str = downloadObj?.blogPost?.postId; + if (!str) return; + const arr = str.split('-post-'); + const str1 = arr[0]; + const str2 = arr[1]; + const blogId = removePrefix(str1); + navigate(`/${downloadObj?.blogPost.user}/${blogId}/${str2}`); }} > {service === 'AUDIO' && ( - + )} {service === 'VIDEO' && ( )} - + @@ -220,13 +208,13 @@ export const DownloadTaskManager: React.FC = () => { width: '100%', textAlign: 'end', fontFamily: 'Arial', - color: theme.palette.text.primary + color: theme.palette.text.primary, }} > {downloadObj?.identifier} - ) + ); })} @@ -285,5 +273,5 @@ export const DownloadTaskManager: React.FC = () => { */} - ) -} + ); +}; diff --git a/src/components/common/DraggableResizableGrid.tsx b/src/components/common/DraggableResizableGrid.tsx index 871fec7..dbff2aa 100644 --- a/src/components/common/DraggableResizableGrid.tsx +++ b/src/components/common/DraggableResizableGrid.tsx @@ -1,36 +1,36 @@ // DraggableResizableGrid.tsx -import React from 'react' -import { DndProvider } from 'react-dnd' -import { HTML5Backend } from 'react-dnd-html5-backend' -import GridLayout, { Layout } from 'react-grid-layout' +import React from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import GridLayout, { Layout } from 'react-grid-layout'; -import './DraggableResizableGrid.css' // Add your custom CSS for the grid layout +import './DraggableResizableGrid.css'; // Add your custom CSS for the grid layout interface GridItem { - id: string - content: React.ReactNode + id: string; + content: React.ReactNode; } interface DraggableResizableGridProps { - items: GridItem[] - cols?: number - rowHeight?: number - onLayoutChange?: (layout: Layout[]) => void + items: GridItem[]; + cols?: number; + rowHeight?: number; + onLayoutChange?: (layout: Layout[]) => void; } const DraggableResizableGrid: React.FC = ({ items, cols = 12, rowHeight = 30, - onLayoutChange + onLayoutChange, }) => { const layout = items.map((item, index) => ({ i: item.id, x: index % cols, y: Math.floor(index / cols), w: 4, - h: 4 - })) + h: 4, + })); return ( @@ -49,7 +49,7 @@ const DraggableResizableGrid: React.FC = ({ ))} - ) -} + ); +}; -export default DraggableResizableGrid +export default DraggableResizableGrid; diff --git a/src/components/common/ErrorBoundary.tsx b/src/components/common/ErrorBoundary.tsx index 58b3185..07720d3 100644 --- a/src/components/common/ErrorBoundary.tsx +++ b/src/components/common/ErrorBoundary.tsx @@ -1,36 +1,33 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode } from 'react'; interface ErrorBoundaryProps { - children: ReactNode - fallback: ReactNode + children: ReactNode; + fallback: ReactNode; } interface ErrorBoundaryState { - hasError: boolean + hasError: boolean; } -class ErrorBoundary extends React.Component< - ErrorBoundaryProps, - ErrorBoundaryState -> { +class ErrorBoundary extends React.Component { state: ErrorBoundaryState = { - hasError: false - } + hasError: false, + }; static getDerivedStateFromError(_: Error): ErrorBoundaryState { - return { hasError: true } + return { hasError: true }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { // You can log the error and errorInfo here, for example, to an error reporting service. - console.error('Error caught in ErrorBoundary:', error, errorInfo) + console.error('Error caught in ErrorBoundary:', error, errorInfo); } render(): React.ReactNode { - if (this.state.hasError) return this.props.fallback + if (this.state.hasError) return this.props.fallback; - return this.props.children + return this.props.children; } } -export default ErrorBoundary +export default ErrorBoundary; diff --git a/src/components/common/FilePanel.tsx b/src/components/common/FilePanel.tsx index f08b313..6aa3394 100644 --- a/src/components/common/FilePanel.tsx +++ b/src/components/common/FilePanel.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from 'react' -import { styled, Box } from '@mui/system' +import React, { useState, useEffect } from 'react'; +import { styled, Box } from '@mui/system'; import { Drawer, List, @@ -8,24 +8,24 @@ import { Typography, ButtonBase, Button, - Tooltip -} from '@mui/material' -import VideoCallIcon from '@mui/icons-material/VideoCall' -import VideoModal from './VideoPublishModal' -import { useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import AttachFileIcon from '@mui/icons-material/AttachFile' -import { AudioModal } from './AudioPublishModal' -import AudioFileIcon from '@mui/icons-material/AudioFile' -import { GenericModal } from './GenericPublishModal' + Tooltip, +} from '@mui/material'; +import VideoCallIcon from '@mui/icons-material/VideoCall'; +import VideoModal from './VideoPublishModal'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import AttachFileIcon from '@mui/icons-material/AttachFile'; +import { AudioModal } from './AudioPublishModal'; +import AudioFileIcon from '@mui/icons-material/AudioFile'; +import { GenericModal } from './GenericPublishModal'; interface VideoPanelProps { - onSelect: (video: Video) => void - height?: string - width?: string + onSelect: (video: Video) => void; + height?: string; + width?: string; } interface VideoApiResponse { - videos: Video[] + videos: Video[]; } const Panel = styled('div')` @@ -51,7 +51,7 @@ const Panel = styled('div')` &::-webkit-scrollbar-thumb:hover { background-color: #555; } -` +`; const PublishButton = styled(Button)` /* position: absolute; @@ -60,24 +60,18 @@ const PublishButton = styled(Button)` right: 0; margin: auto; */ max-width: 80%; -` +`; -export const FilePanel: React.FC = ({ - onSelect, - height, - width -}) => { - const [isOpen, setIsOpen] = useState(false) - const [videos, setVideos] = useState([]) - const [isOpenVideoModal, setIsOpenVideoModal] = useState(false) - const { user } = useSelector((state: RootState) => state.auth) - const [editVideoIdentifier, setEditVideoIdentifier] = useState< - string | null | undefined - >() +export const FilePanel: React.FC = ({ onSelect, height, width }) => { + const [isOpen, setIsOpen] = useState(false); + const [videos, setVideos] = useState([]); + const [isOpenVideoModal, setIsOpenVideoModal] = useState(false); + const { user } = useSelector((state: RootState) => state.auth); + const [editVideoIdentifier, setEditVideoIdentifier] = useState(); const fetchVideos = React.useCallback(async (): Promise => { - if (!user?.name) return [] + if (!user?.name) return []; - let res = [] + let res = []; try { // res = await qortalRequest({ // action: 'LIST_QDN_RESOURCES', @@ -90,34 +84,34 @@ export const FilePanel: React.FC = ({ // }) const res2 = await fetch( - `/arbitrary/resources?&service=FILE&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true` - ) - const resData = await res2.json() + `/arbitrary/resources?&service=FILE&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`, + ); + const resData = await res2.json(); if (Array.isArray(resData)) { - res = resData + res = resData; } } catch (error) {} // Replace this URL with the actual API endpoint - return res - }, [user]) + return res; + }, [user]); useEffect(() => { - fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)) - }, []) + fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)); + }, []); const handleToggle = () => { - setIsOpen(!isOpen) - } + setIsOpen(!isOpen); + }; const handleClick = (video: Video) => { - onSelect(video) - } + onSelect(video); + }; return ( @@ -126,7 +120,7 @@ export const FilePanel: React.FC = ({ sx={{ height: height || '30px', width: width || 'auto', - cursor: 'pointer' + cursor: 'pointer', }} > @@ -135,12 +129,12 @@ export const FilePanel: React.FC = ({ open={isOpen} onClose={handleToggle} ModalProps={{ - keepMounted: true // Better performance on mobile + keepMounted: true, // Better performance on mobile }} sx={{ '& .MuiPaper-root': { - width: '400px' - } + width: '400px', + }, }} > @@ -149,21 +143,13 @@ export const FilePanel: React.FC = ({ display: 'flex', flexDirection: 'column', alignItems: 'center', - flex: '0 0' + flex: '0 0', }} > - + Select File - + List of Files in QDN under your name (FILE service) @@ -174,15 +160,12 @@ export const FilePanel: React.FC = ({ display: 'flex', flexDirection: 'column', flex: '1', - overflow: 'auto' + overflow: 'auto', }} > {videos.map((video) => ( - handleClick(video)} - sx={{ width: '100%' }} - > + handleClick(video)} sx={{ width: '100%' }}> = ({ size="small" variant="contained" onClick={() => { - setEditVideoIdentifier(video.identifier) - setIsOpenVideoModal(true) + setEditVideoIdentifier(video.identifier); + setIsOpenVideoModal(true); }} > Edit @@ -206,14 +189,14 @@ export const FilePanel: React.FC = ({ width: '100%', display: 'flex', justifyContent: 'center', - flex: '0 0 50px' + flex: '0 0 50px', }} > { - setEditVideoIdentifier(null) - setIsOpenVideoModal(true) + setEditVideoIdentifier(null); + setIsOpenVideoModal(true); }} > Publish new file @@ -225,33 +208,33 @@ export const FilePanel: React.FC = ({ service="FILE" identifierPrefix="qfile_qblog" onClose={() => { - setIsOpenVideoModal(false) - setEditVideoIdentifier(null) + setIsOpenVideoModal(false); + setEditVideoIdentifier(null); }} open={isOpenVideoModal} onPublish={(value) => { - fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)) - setIsOpenVideoModal(false) + fetchVideos().then((fetchedVideos) => setVideos(fetchedVideos)); + setIsOpenVideoModal(false); }} editVideoIdentifier={editVideoIdentifier} /> - ) -} + ); +}; // Add this to your 'types.ts' file export interface Video { - name: string - service: string - identifier: string + name: string; + service: string; + identifier: string; metadata: { - title: string - description: string - tags: string[] - category: string - categoryName: string - } - size: number - created: number - updated: number + title: string; + description: string; + tags: string[]; + category: string; + categoryName: string; + }; + size: number; + created: number; + updated: number; } diff --git a/src/components/common/GenericPublishModal.tsx b/src/components/common/GenericPublishModal.tsx index 91df14e..043ad9b 100644 --- a/src/components/common/GenericPublishModal.tsx +++ b/src/components/common/GenericPublishModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState } from 'react'; import { Box, Button, @@ -12,30 +12,30 @@ import { SelectChangeEvent, OutlinedInput, Chip, - IconButton -} from '@mui/material' -import { styled } from '@mui/system' -import { useDropzone } from 'react-dropzone' -import { toBase64 } from '../../utils/toBase64' -import AddIcon from '@mui/icons-material/Add' -import CloseIcon from '@mui/icons-material/Close' -import { usePublishGeneric } from './PublishGeneric' -import { useDispatch } from 'react-redux' -import { setNotification } from '../../state/features/notificationsSlice' + IconButton, +} from '@mui/material'; +import { styled } from '@mui/system'; +import { useDropzone } from 'react-dropzone'; +import { toBase64 } from '../../utils/toBase64'; +import AddIcon from '@mui/icons-material/Add'; +import CloseIcon from '@mui/icons-material/Close'; +import { usePublishGeneric } from './PublishGeneric'; +import { useDispatch } from 'react-redux'; +import { setNotification } from '../../state/features/notificationsSlice'; const StyledModal = styled(Modal)(({ theme }) => ({ display: 'flex', alignItems: 'center', - justifyContent: 'center' -})) + justifyContent: 'center', +})); const ChipContainer = styled(Box)({ display: 'flex', flexWrap: 'wrap', '& > *': { - margin: '4px' - } -}) + margin: '4px', + }, +}); const ModalContent = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -43,26 +43,26 @@ const ModalContent = styled(Box)(({ theme }) => ({ borderRadius: theme.spacing(1), width: '40%', '&:focus': { - outline: 'none' - } -})) + outline: 'none', + }, +})); interface GenericModalProps { - open: boolean - onClose: () => void - onPublish: (value: any) => void - acceptedFileType?: string - acceptedFileTypes?: string[] - service: string - identifierPrefix: string - editVideoIdentifier?: string | null | undefined + open: boolean; + onClose: () => void; + onPublish: (value: any) => void; + acceptedFileType?: string; + acceptedFileTypes?: string[]; + service: string; + identifierPrefix: string; + editVideoIdentifier?: string | null | undefined; } interface SelectOption { - id: string - name: string + id: string; + name: string; } -const maxSize = 500 * 1024 * 1024 +const maxSize = 500 * 1024 * 1024; export const GenericModal: React.FC = ({ open, @@ -72,92 +72,88 @@ export const GenericModal: React.FC = ({ acceptedFileTypes, service, identifierPrefix, - editVideoIdentifier + editVideoIdentifier, }) => { - const [file, setFile] = useState(null) - const [title, setTitle] = useState('') - const [description, setDescription] = useState('') - const [selectedOption, setSelectedOption] = useState( - null - ) - const [inputValue, setInputValue] = useState('') - const [chips, setChips] = useState([]) + const [file, setFile] = useState(null); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [selectedOption, setSelectedOption] = useState(null); + const [inputValue, setInputValue] = useState(''); + const [chips, setChips] = useState([]); - const [options, setOptions] = useState([]) - const [tags, setTags] = useState([]) - const { publishGeneric } = usePublishGeneric() - const dispatch = useDispatch() + const [options, setOptions] = useState([]); + const [tags, setTags] = useState([]); + const { publishGeneric } = usePublishGeneric(); + const dispatch = useDispatch(); - let acceptedFile = {} + let acceptedFile = {}; if (acceptedFileType) { acceptedFile = { - [acceptedFileType]: [] - } + [acceptedFileType]: [], + }; } const { getRootProps, getInputProps } = useDropzone({ ...acceptedFile, maxFiles: 1, maxSize, onDrop: (acceptedFiles) => { - setFile(acceptedFiles[0]) + setFile(acceptedFiles[0]); }, onDropRejected: (rejectedFiles) => { dispatch( setNotification({ msg: 'Your file is over the 500mb limit.', - alertType: 'error' - }) - ) - } - }) + alertType: 'error', + }), + ); + }, + }); const handleTitleChange = (event: React.ChangeEvent) => { - setTitle(event.target.value) - } + setTitle(event.target.value); + }; - const handleDescriptionChange = ( - event: React.ChangeEvent - ) => { - setDescription(event.target.value) - } + const handleDescriptionChange = (event: React.ChangeEvent) => { + setDescription(event.target.value); + }; const handleOptionChange = (event: SelectChangeEvent) => { - const optionId = event.target.value - const selectedOption = options.find((option) => option.id === optionId) - setSelectedOption(selectedOption || null) - } + const optionId = event.target.value; + const selectedOption = options.find((option) => option.id === optionId); + setSelectedOption(selectedOption || null); + }; const handleChipDelete = (index: number) => { - const newChips = [...chips] - newChips.splice(index, 1) - setChips(newChips) - } + const newChips = [...chips]; + newChips.splice(index, 1); + setChips(newChips); + }; const handleSubmit = async () => { - const missingFields = [] + const missingFields = []; - if (!title) missingFields.push('title') - if (!file) missingFields.push('file') + if (!title) missingFields.push('title'); + if (!file) missingFields.push('file'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; - return + return; } - if (!file) return + if (!file) return; - const formattedTags: { [key: string]: string } = {} + const formattedTags: { [key: string]: string } = {}; chips.forEach((tag, i) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); try { - const base64 = await toBase64(file) - if (typeof base64 !== 'string') return - const base64String = base64.split(',')[1] - const fileExtension = file?.name?.split('.')?.pop() - const fileTitle = title?.replace(/ /g, '_')?.slice(0, 20) - const filename = `${fileTitle}.${fileExtension}` + const base64 = await toBase64(file); + if (typeof base64 !== 'string') return; + const base64String = base64.split(',')[1]; + const fileExtension = file?.name?.split('.')?.pop(); + const fileTitle = title?.replace(/ /g, '_')?.slice(0, 20); + const filename = `${fileTitle}.${fileExtension}`; const res = await publishGeneric({ editVideoIdentifier, service, @@ -168,63 +164,61 @@ export const GenericModal: React.FC = ({ file, filename: filename, category: selectedOption?.id || '', - ...formattedTags - }) - onPublish(res) - setFile(null) - setTitle('') - setDescription('') - onClose() + ...formattedTags, + }); + onPublish(res); + setFile(null); + setTitle(''); + setDescription(''); + onClose(); } catch (error) {} - } + }; const handleInputChange = (event: any) => { - setInputValue(event.target.value) - } + setInputValue(event.target.value); + }; const handleInputKeyDown = (event: any) => { if (event.key === 'Enter' && inputValue !== '') { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } else { - event.preventDefault() + event.preventDefault(); } } - } + }; const addChip = () => { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } - } + }; const getListCategories = React.useCallback(async () => { try { - const url = `/arbitrary/categories` + const url = `/arbitrary/categories`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - setOptions(responseData) + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + setOptions(responseData); } catch (error) {} - }, []) + }, []); React.useEffect(() => { - getListCategories() - }, [getListCategories]) + getListCategories(); + }, [getListCategories]); return ( {editVideoIdentifier && ( - - You are editing: {editVideoIdentifier} - + You are editing: {editVideoIdentifier} )} Upload {service} @@ -235,14 +229,12 @@ export const GenericModal: React.FC = ({ border: '1px dashed gray', padding: 2, textAlign: 'center', - marginBottom: 2 + marginBottom: 2, }} > - {file - ? file.name - : 'Drag and drop a file here or click to select a file'} + {file ? file.name : 'Drag and drop a file here or click to select a file'} = ({ - ) -} + ); +}; diff --git a/src/components/common/ImagePanel.tsx b/src/components/common/ImagePanel.tsx index d175436..b09e1ac 100644 --- a/src/components/common/ImagePanel.tsx +++ b/src/components/common/ImagePanel.tsx @@ -1,21 +1,15 @@ -import React, { useEffect, useState, useCallback } from 'react' -import { styled, Box } from '@mui/system' -import { - Drawer, - Typography, - Tooltip, - Button, - ButtonBase -} from '@mui/material' -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' -import { useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import ImageUploader from './ImageUploader' +import React, { useEffect, useState, useCallback } from 'react'; +import { styled, Box } from '@mui/system'; +import { Drawer, Typography, Tooltip, Button, ButtonBase } from '@mui/material'; +import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import ImageUploader from './ImageUploader'; interface ImagePanelProps { - onSelect: (src: string) => void - height?: string - width?: string + onSelect: (src: string) => void; + height?: string; + width?: string; } const Panel = styled('div')` @@ -38,60 +32,56 @@ const Panel = styled('div')` &::-webkit-scrollbar-thumb:hover { background-color: #555; } -` +`; -export const ImagePanel: React.FC = ({ - onSelect, - height, - width -}) => { - const [isOpen, setIsOpen] = useState(false) - const [items, setItems] = useState([]) - const [loading, setLoading] = useState(false) - const { user } = useSelector((state: RootState) => state.auth) +export const ImagePanel: React.FC = ({ onSelect, height, width }) => { + const [isOpen, setIsOpen] = useState(false); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(false); + const { user } = useSelector((state: RootState) => state.auth); const qdnResourceUrl = useCallback( (service: string, name: string, identifier: string) => `/arbitrary/${service}/${name}/${identifier}`, - [] - ) + [], + ); const fetchImages = useCallback(async () => { - if (!user?.name) return - setLoading(true) + if (!user?.name) return; + setLoading(true); try { - const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE'] + const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE']; const lists = await Promise.all( SERVICES.map(async (svc) => { const res = await fetch( - `/arbitrary/resources?service=${svc}&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true` - ) - const data = await res.json() + `/arbitrary/resources?service=${svc}&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`, + ); + const data = await res.json(); return Array.isArray(data) ? data.map((it: any) => ({ ...it, service: it.service || svc })) - : [] - }) - ) - setItems(lists.flat()) + : []; + }), + ); + setItems(lists.flat()); } catch (e) { - setItems([]) + setItems([]); } finally { - setLoading(false) + setLoading(false); } - }, [user]) + }, [user]); useEffect(() => { - if (isOpen) fetchImages() - }, [isOpen, fetchImages]) + if (isOpen) fetchImages(); + }, [isOpen, fetchImages]); - const handleToggle = () => setIsOpen((v) => !v) + const handleToggle = () => setIsOpen((v) => !v); const handleChooseExisting = (img: any) => { - const service = img.service || 'IMAGE' - const url = qdnResourceUrl(service, img.name, img.identifier) - onSelect(url) - setIsOpen(false) - } + const service = img.service || 'IMAGE'; + const url = qdnResourceUrl(service, img.name, img.identifier); + onSelect(url); + setIsOpen(false); + }; return ( @@ -101,7 +91,7 @@ export const ImagePanel: React.FC = ({ sx={{ height: height || '30px', width: width || 'auto', - cursor: 'pointer' + cursor: 'pointer', }} /> @@ -119,7 +109,7 @@ export const ImagePanel: React.FC = ({ display: 'flex', flexDirection: 'column', alignItems: 'center', - flex: '0 0' + flex: '0 0', }} > @@ -136,19 +126,19 @@ export const ImagePanel: React.FC = ({ px: 2, pb: 1, flex: '1', - overflow: 'auto' + overflow: 'auto', }} > {items.map((img) => { - const service = img.service || 'IMAGE' - const url = qdnResourceUrl(service, img.name, img.identifier) + const service = img.service || 'IMAGE'; + const url = qdnResourceUrl(service, img.name, img.identifier); return ( = ({ width: '100%', borderRadius: 1, overflow: 'hidden', - border: '1px solid rgba(0,0,0,0.2)' + border: '1px solid rgba(0,0,0,0.2)', }} > @@ -167,7 +157,7 @@ export const ImagePanel: React.FC = ({ display: 'block', width: '100%', height: 100, - objectFit: 'cover' + objectFit: 'cover', }} /> = ({ - ) + ); })} {loading && ( @@ -201,13 +191,13 @@ export const ImagePanel: React.FC = ({ display: 'flex', justifyContent: 'center', flex: '0 0 56px', - py: 1 + py: 1, }} > { - onSelect(base64) - setIsOpen(false) + onSelect(base64); + setIsOpen(false); }} > @@ -216,5 +206,5 @@ export const ImagePanel: React.FC = ({ - ) -} + ); +}; diff --git a/src/components/common/ImageUploader.tsx b/src/components/common/ImageUploader.tsx index 0cac393..7e15081 100644 --- a/src/components/common/ImageUploader.tsx +++ b/src/components/common/ImageUploader.tsx @@ -1,37 +1,33 @@ -import React, { useCallback } from 'react' -import { Box, Button, TextField, Typography, Modal } from '@mui/material' -import { - useDropzone, - DropzoneRootProps, - DropzoneInputProps -} from 'react-dropzone' -import Compressor from 'compressorjs' +import React, { useCallback } from 'react'; +import { Box, Button, TextField, Typography, Modal } from '@mui/material'; +import { useDropzone, DropzoneRootProps, DropzoneInputProps } from 'react-dropzone'; +import Compressor from 'compressorjs'; const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = () => resolve(reader.result) + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); reader.onerror = (error) => { - reject(error) - } - }) + reject(error); + }; + }); interface ImageUploaderProps { - children: React.ReactNode - onPick: (base64Img: string) => void + children: React.ReactNode; + onPick: (base64Img: string) => void; } const ImageUploader: React.FC = ({ children, onPick }) => { const onDrop = useCallback( async (acceptedFiles: File[]) => { if (acceptedFiles.length > 1) { - return + return; } - let compressedFile: File | undefined + let compressedFile: File | undefined; try { - const image = acceptedFiles[0] + const image = acceptedFiles[0]; await new Promise((resolve) => { new Compressor(image, { quality: 0.6, @@ -39,51 +35,51 @@ const ImageUploader: React.FC = ({ children, onPick }) => { mimeType: 'image/webp', success(result) { const file = new File([result], 'name', { - type: 'image/webp' - }) - compressedFile = file - resolve() + type: 'image/webp', + }); + compressedFile = file; + resolve(); }, - error(err) {} - }) - }) - if (!compressedFile) return - const base64Img = await toBase64(compressedFile) + error(err) {}, + }); + }); + if (!compressedFile) return; + const base64Img = await toBase64(compressedFile); - onPick(base64Img as string) + onPick(base64Img as string); } catch (error) { - console.error(error) + console.error(error); } }, - [onPick] - ) + [onPick], + ); const { getRootProps, getInputProps, - isDragActive + isDragActive, }: { - getRootProps: () => DropzoneRootProps - getInputProps: () => DropzoneInputProps - isDragActive: boolean + getRootProps: () => DropzoneRootProps; + getInputProps: () => DropzoneInputProps; + isDragActive: boolean; } = useDropzone({ onDrop, accept: { - 'image/*': [] - } - }) + 'image/*': [], + }, + }); return ( {children} - ) -} + ); +}; -export default ImageUploader +export default ImageUploader; diff --git a/src/components/common/LazyLoad.tsx b/src/components/common/LazyLoad.tsx index 6369d0d..0ce398c 100644 --- a/src/components/common/LazyLoad.tsx +++ b/src/components/common/LazyLoad.tsx @@ -1,28 +1,28 @@ -import React, { useState, useEffect, useRef } from 'react' -import { useInView } from 'react-intersection-observer' -import CircularProgress from '@mui/material/CircularProgress' +import React, { useState, useEffect, useRef } from 'react'; +import { useInView } from 'react-intersection-observer'; +import CircularProgress from '@mui/material/CircularProgress'; interface Props { - onLoadMore: () => Promise + onLoadMore: () => Promise; } const LazyLoad: React.FC = ({ onLoadMore }) => { - const [isFetching, setIsFetching] = useState(false) + const [isFetching, setIsFetching] = useState(false); - const firstLoad = useRef(false) + const firstLoad = useRef(false); const [ref, inView] = useInView({ - threshold: 0.7 - }) + threshold: 0.7, + }); useEffect(() => { if (inView) { - setIsFetching(true) + setIsFetching(true); onLoadMore().finally(() => { - setIsFetching(false) - firstLoad.current = true - }) + setIsFetching(false); + firstLoad.current = true; + }); } - }, [inView]) + }, [inView]); return (
= ({ onLoadMore }) => { style={{ display: 'flex', justifyContent: 'center', - minHeight: '25px' + minHeight: '25px', }} >
- ) -} + ); +}; -export default LazyLoad +export default LazyLoad; diff --git a/src/components/common/Notification/Notification.tsx b/src/components/common/Notification/Notification.tsx index 501f081..898ee6d 100644 --- a/src/components/common/Notification/Notification.tsx +++ b/src/components/common/Notification/Notification.tsx @@ -1,13 +1,13 @@ -import { useDispatch, useSelector } from 'react-redux' -import { toast, ToastContainer, Zoom, Slide } from 'react-toastify' -import { removeNotification } from '../../../state/features/notificationsSlice' -import 'react-toastify/dist/ReactToastify.css' -import { RootState } from '../../../state/store' +import { useDispatch, useSelector } from 'react-redux'; +import { toast, ToastContainer, Zoom, Slide } from 'react-toastify'; +import { removeNotification } from '../../../state/features/notificationsSlice'; +import 'react-toastify/dist/ReactToastify.css'; +import { RootState } from '../../../state/store'; const Notification = () => { - const dispatch = useDispatch() + const dispatch = useDispatch(); - const { alertTypes } = useSelector((state: RootState) => state.notifications) + const { alertTypes } = useSelector((state: RootState) => state.notifications); if (alertTypes.alertError) { toast.error(`❌ ${alertTypes?.alertError}`, { @@ -18,9 +18,9 @@ const Notification = () => { pauseOnHover: true, draggable: true, progress: undefined, - icon: false - }) - dispatch(removeNotification()) + icon: false, + }); + dispatch(removeNotification()); } if (alertTypes.alertSuccess) { toast.success(`✔️ ${alertTypes?.alertSuccess}`, { @@ -31,9 +31,9 @@ const Notification = () => { pauseOnHover: true, draggable: true, progress: undefined, - icon: false - }) - dispatch(removeNotification()) + icon: false, + }); + dispatch(removeNotification()); } if (alertTypes.alertInfo) { toast.info(`${alertTypes?.alertInfo}`, { @@ -44,9 +44,9 @@ const Notification = () => { pauseOnHover: true, draggable: true, progress: undefined, - theme: 'light' - }) - dispatch(removeNotification()) + theme: 'light', + }); + dispatch(removeNotification()); } if (alertTypes.alertInfo) { @@ -65,7 +65,7 @@ const Notification = () => { toastStyle={{ fontSize: '16px' }} transition={Slide} /> - ) + ); } return ( @@ -80,7 +80,7 @@ const Notification = () => { draggable pauseOnHover /> - ) -} + ); +}; -export default Notification +export default Notification; diff --git a/src/components/common/PageLoader.tsx b/src/components/common/PageLoader.tsx index e8e5740..75acc3b 100644 --- a/src/components/common/PageLoader.tsx +++ b/src/components/common/PageLoader.tsx @@ -1,18 +1,15 @@ import React from 'react'; import CircularProgress from '@mui/material/CircularProgress'; import Box from '@mui/system/Box'; -import { useTheme } from '@mui/material' +import { useTheme } from '@mui/material'; interface PageLoaderProps { - size?: number - thickness?: number + size?: number; + thickness?: number; } -const PageLoader: React.FC = ({ - size = 40, - thickness = 5 -}) => { - const theme = useTheme() +const PageLoader: React.FC = ({ size = 40, thickness = 5 }) => { + const theme = useTheme(); return ( = ({ top: 0, left: 0, backgroundColor: 'rgba(255, 255, 255, 0.25)', - zIndex: 1000 + zIndex: 1000, }} > - ) -} + ); +}; export default PageLoader; diff --git a/src/components/common/PollPanel.tsx b/src/components/common/PollPanel.tsx index cbd6f0a..c5761cb 100644 --- a/src/components/common/PollPanel.tsx +++ b/src/components/common/PollPanel.tsx @@ -1,19 +1,30 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { styled, Box } from '@mui/system' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { styled, Box } from '@mui/system'; import { - Drawer, Typography, Tooltip, Button, ButtonBase, TextField, Checkbox, FormControlLabel, - List, ListItem, ListItemText, Divider, CircularProgress -} from '@mui/material' -import HowToVoteIcon from '@mui/icons-material/HowToVote' -import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import { setNotification } from '../../state/features/notificationsSlice' + Drawer, + Typography, + Tooltip, + Button, + ButtonBase, + TextField, + Checkbox, + FormControlLabel, + List, + ListItem, + ListItemText, + Divider, + CircularProgress, +} from '@mui/material'; +import HowToVoteIcon from '@mui/icons-material/HowToVote'; +import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import { setNotification } from '../../state/features/notificationsSlice'; interface PollPanelProps { - onSelect: (pollName: string) => void - height?: string - width?: string + onSelect: (pollName: string) => void; + height?: string; + width?: string; } const Panel = styled('div')` @@ -24,186 +35,194 @@ const Panel = styled('div')` height: 100%; overflow: hidden; - &::-webkit-scrollbar { width: 8px; height: 8px; } - &::-webkit-scrollbar-thumb { background-color: #888; border-radius: 4px; } - &::-webkit-scrollbar-thumb:hover { background-color: #555; } -` + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: #888; + border-radius: 4px; + } + &::-webkit-scrollbar-thumb:hover { + background-color: #555; + } +`; type PollRow = { - pollName: string - description?: string - owner?: string - pollOptions?: { optionName: string }[] - published?: number -} + pollName: string; + description?: string; + owner?: string; + pollOptions?: { optionName: string }[]; + published?: number; +}; export const PollPanel: React.FC = ({ onSelect, height, width }) => { - const [open, setOpen] = useState(false) - const [isCreateOpen, setIsCreateOpen] = useState(false) - const [search, setSearch] = useState('') - const [myOnly, setMyOnly] = useState(true) - const { user } = useSelector((s: RootState) => s.auth) + const [open, setOpen] = useState(false); + const [isCreateOpen, setIsCreateOpen] = useState(false); + const [search, setSearch] = useState(''); + const [myOnly, setMyOnly] = useState(true); + const { user } = useSelector((s: RootState) => s.auth); // --- create poll form - const [name, setName] = useState('') - const [description, setDescription] = useState('') - const [options, setOptions] = useState(['', '']) + const [name, setName] = useState(''); + const [description, setDescription] = useState(''); + const [options, setOptions] = useState(['', '']); // Cache of ALL polls from the first scan - const apiPageSize = 50 - const [allPolls, setAllPolls] = useState([]) - const [rawOffset, setRawOffset] = useState(0) - const [scanningAll, setScanningAll] = useState(false) - const [fullyScanned, setFullyScanned] = useState(false) - const scanIdRef = useRef(0) + const apiPageSize = 50; + const [allPolls, setAllPolls] = useState([]); + const [rawOffset, setRawOffset] = useState(0); + const [scanningAll, setScanningAll] = useState(false); + const [fullyScanned, setFullyScanned] = useState(false); + const scanIdRef = useRef(0); // UI slice: how many filtered items to show (increments by 50 via "Load more") - const [displayCount, setDisplayCount] = useState(50) + const [displayCount, setDisplayCount] = useState(50); // Reset everything we derive from filters/search/open const resetDerived = () => { - setDisplayCount(50) - } + setDisplayCount(50); + }; - const dispatch = useDispatch() + const dispatch = useDispatch(); // Helper: fetch ONE raw page from /polls at a given offset const fetchRawPage = async (offsetNum: number) => { - const url = new URL('/polls', window.location.origin) - url.searchParams.set('limit', String(apiPageSize)) - url.searchParams.set('offset', String(offsetNum)) - const res = await fetch(url.toString()) - if (!res.ok) throw new Error('Failed to fetch polls') - const data = await res.json() - return Array.isArray(data) ? data as PollRow[] : [] - } + const url = new URL('/polls', window.location.origin); + url.searchParams.set('limit', String(apiPageSize)); + url.searchParams.set('offset', String(offsetNum)); + const res = await fetch(url.toString()); + if (!res.ok) throw new Error('Failed to fetch polls'); + const data = await res.json(); + return Array.isArray(data) ? (data as PollRow[]) : []; + }; const scanAllPolls = useCallback(async () => { - if (fullyScanned || scanningAll) return - setScanningAll(true) - const myScanId = ++scanIdRef.current + if (fullyScanned || scanningAll) return; + setScanningAll(true); + const myScanId = ++scanIdRef.current; try { - let nextRawOffset = rawOffset - let collected: PollRow[] = allPolls // keep any partial cache if we reopen - for (; ;) { - if (scanIdRef.current !== myScanId) return // canceled due to new open/filter change - const page = await fetchRawPage(nextRawOffset) + let nextRawOffset = rawOffset; + let collected: PollRow[] = allPolls; // keep any partial cache if we reopen + for (;;) { + if (scanIdRef.current !== myScanId) return; // canceled due to new open/filter change + const page = await fetchRawPage(nextRawOffset); if (!page.length) { - setFullyScanned(true) - break + setFullyScanned(true); + break; } - collected = [...collected, ...page] - setAllPolls(collected) // progressively update so UI can show partial results quickly - nextRawOffset += apiPageSize - setRawOffset(nextRawOffset) + collected = [...collected, ...page]; + setAllPolls(collected); // progressively update so UI can show partial results quickly + nextRawOffset += apiPageSize; + setRawOffset(nextRawOffset); if (page.length < apiPageSize) { - setFullyScanned(true) - break + setFullyScanned(true); + break; } // yield to UI; optional tiny delay to keep drawer responsive - await new Promise(r => setTimeout(r, 0)) + await new Promise((r) => setTimeout(r, 0)); } } catch (e) { // If it errors, we stop scanning; user can close/open to retry. - setFullyScanned(true) + setFullyScanned(true); } finally { - setScanningAll(false) + setScanningAll(false); } - }, [fullyScanned, scanningAll, rawOffset, allPolls]) + }, [fullyScanned, scanningAll, rawOffset, allPolls]); useEffect(() => { - if (!open) return + if (!open) return; // cancel any in-flight scan loop and start fresh if needed - scanIdRef.current++ - resetDerived() + scanIdRef.current++; + resetDerived(); // kick off a full scan if we haven't already completed one if (!fullyScanned && !scanningAll) { - scanAllPolls() + scanAllPolls(); } // If we *have* already scanned, we just re-derive the visible slice from cache. - }, [open, myOnly, search, fullyScanned, scanningAll, scanAllPolls]) + }, [open, myOnly, search, fullyScanned, scanningAll, scanAllPolls]); - - - const handleToggle = () => setOpen(v => !v) - const addOption = () => setOptions(o => (o.length >= 100 ? o : [...o, ''])) - const updateOption = (i: number, val: string) => setOptions(o => o.map((x, idx) => (idx === i ? val : x))) - const removeOption = (i: number) => setOptions(o => o.filter((_, idx) => idx !== i)) + const handleToggle = () => setOpen((v) => !v); + const addOption = () => setOptions((o) => (o.length >= 100 ? o : [...o, ''])); + const updateOption = (i: number, val: string) => + setOptions((o) => o.map((x, idx) => (idx === i ? val : x))); + const removeOption = (i: number) => setOptions((o) => o.filter((_, idx) => idx !== i)); // newest first by published, fallback to pollName const sortedAll = useMemo(() => { - const arr = allPolls.slice() + const arr = allPolls.slice(); arr.sort((a, b) => { - const aPub = a.published ?? 0 - const bPub = b.published ?? 0 - if (bPub !== aPub) return bPub - aPub + const aPub = a.published ?? 0; + const bPub = b.published ?? 0; + if (bPub !== aPub) return bPub - aPub; // fallback deterministic tiebreaker - return (b.pollName || '').localeCompare(a.pollName || '') - }) - return arr - }, [allPolls]) + return (b.pollName || '').localeCompare(a.pollName || ''); + }); + return arr; + }, [allPolls]); - const normalizedSearch = search.trim().toLowerCase() + const normalizedSearch = search.trim().toLowerCase(); const filtered = useMemo(() => { - const byOwner = myOnly && user?.address - ? (p: PollRow) => (p.owner || '').toLowerCase() === user.address.toLowerCase() - : () => true + const byOwner = + myOnly && user?.address + ? (p: PollRow) => (p.owner || '').toLowerCase() === user.address.toLowerCase() + : () => true; const byQuery = normalizedSearch ? (p: PollRow) => { - const inName = p.pollName?.toLowerCase().includes(normalizedSearch) - const inDesc = p.description?.toLowerCase().includes(normalizedSearch) - const inOpts = (p.pollOptions || []).some(o => - o.optionName?.toLowerCase().includes(normalizedSearch) - ) - return !!(inName || inDesc || inOpts) - } - : () => true + const inName = p.pollName?.toLowerCase().includes(normalizedSearch); + const inDesc = p.description?.toLowerCase().includes(normalizedSearch); + const inOpts = (p.pollOptions || []).some((o) => + o.optionName?.toLowerCase().includes(normalizedSearch), + ); + return !!(inName || inDesc || inOpts); + } + : () => true; - return sortedAll.filter(p => byOwner(p) && byQuery(p)) - }, [sortedAll, myOnly, user, normalizedSearch]) + return sortedAll.filter((p) => byOwner(p) && byQuery(p)); + }, [sortedAll, myOnly, user, normalizedSearch]); - const visible = useMemo(() => filtered.slice(0, displayCount), [filtered, displayCount]) - const canLoadMore = visible.length < filtered.length // controls "Load more" + const visible = useMemo(() => filtered.slice(0, displayCount), [filtered, displayCount]); + const canLoadMore = visible.length < filtered.length; // controls "Load more" const canCreate = useMemo(() => { - const validName = name.trim().length >= 3 && name.trim().length <= 400 - const validDesc = description.length <= 4000 - const cleaned = options.map(o => o.trim()).filter(Boolean) - return validName && validDesc && cleaned.length >= 1 - }, [name, description, options]) + const validName = name.trim().length >= 3 && name.trim().length <= 400; + const validDesc = description.length <= 4000; + const cleaned = options.map((o) => o.trim()).filter(Boolean); + return validName && validDesc && cleaned.length >= 1; + }, [name, description, options]); const handleCreate = async () => { - if (!user?.address) return - const cleaned = options.map(o => o.trim()).filter(Boolean) - const optionsString = cleaned.join(', ') + if (!user?.address) return; + const cleaned = options.map((o) => o.trim()).filter(Boolean); + const optionsString = cleaned.join(', '); try { await qortalRequest({ action: 'CREATE_POLL', pollName: name.trim(), pollDescription: description.trim(), pollOptions: [optionsString], - pollOwnerAddress: user.address - }) + pollOwnerAddress: user.address, + }); dispatch( setNotification({ msg: 'Creating poll successful', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); // After create, immediately hand back pollName to editor - onSelect(name.trim()) - setIsCreateOpen(false) - setOpen(false) + onSelect(name.trim()); + setIsCreateOpen(false); + setOpen(false); } catch (e: any) { dispatch( setNotification({ msg: e?.message || 'Creating poll failed', - alertType: 'error' - }) - ) + alertType: 'error', + }), + ); } - } + }; return ( @@ -223,8 +242,12 @@ export const PollPanel: React.FC = ({ onSelect, height, width }) > - Select Poll - Search polls on Qortal. Default shows only your polls. + + Select Poll + + + Search polls on Qortal. Default shows only your polls. + = ({ onSelect, height, width }) size="small" label="Search polls" value={search} - onChange={(e) => { setSearch(e.target.value); setDisplayCount(50) }} + onChange={(e) => { + setSearch(e.target.value); + setDisplayCount(50); + }} /> { setMyOnly(e.target.checked); setDisplayCount(50) }} + onChange={(e) => { + setMyOnly(e.target.checked); + setDisplayCount(50); + }} /> } label="My Polls" /> - @@ -255,13 +288,28 @@ export const PollPanel: React.FC = ({ onSelect, height, width }) - {visible.map(p => ( - { onSelect(p.pollName); setOpen(false) }}> - Embed - - }> - { onSelect(p.pollName); setOpen(false) }} sx={{ width: '100%', textAlign: 'left' }}> + {visible.map((p) => ( + { + onSelect(p.pollName); + setOpen(false); + }} + > + Embed + + } + > + { + onSelect(p.pollName); + setOpen(false); + }} + sx={{ width: '100%', textAlign: 'left' }} + > @@ -279,9 +327,7 @@ export const PollPanel: React.FC = ({ onSelect, height, width }) {/* Only show Load more if there are more filtered results beyond what's visible */} {!scanningAll && canLoadMore && ( - + )} @@ -289,21 +335,40 @@ export const PollPanel: React.FC = ({ onSelect, height, width }) {/* Create Poll Dialog (inline, simple) */} {isCreateOpen && ( - Create Poll + + Create Poll + setName(e.target.value)} + fullWidth + label="Poll name" + value={name} + onChange={(e) => setName(e.target.value)} helperText="3–400 chars; must be unique on-chain" sx={{ mb: 1 }} /> setDescription(e.target.value)} sx={{ mb: 1 }} + fullWidth + multiline + minRows={2} + label="Description" + value={description} + onChange={(e) => setDescription(e.target.value)} + sx={{ mb: 1 }} /> - Options + + Options + {options.map((opt, i) => ( - updateOption(i, e.target.value)} /> - + updateOption(i, e.target.value)} + /> + ))} @@ -311,12 +376,14 @@ export const PollPanel: React.FC = ({ onSelect, height, width }) - + )} - ) -} + ); +}; diff --git a/src/components/common/PollWidget.tsx b/src/components/common/PollWidget.tsx index 29fdcab..6349277 100644 --- a/src/components/common/PollWidget.tsx +++ b/src/components/common/PollWidget.tsx @@ -1,147 +1,164 @@ -import React, { useEffect, useMemo, useState } from 'react' -import { Box, Card, CardContent, Typography, RadioGroup, FormControlLabel, Radio, Button, LinearProgress } from '@mui/material' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import { setNotification } from '../../state/features/notificationsSlice' +import React, { useEffect, useMemo, useState } from 'react'; +import { + Box, + Card, + CardContent, + Typography, + RadioGroup, + FormControlLabel, + Radio, + Button, + LinearProgress, +} from '@mui/material'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import { setNotification } from '../../state/features/notificationsSlice'; -type PollOption = { optionName: string } -type PollInfo = { pollName: string; description?: string; pollOptions: PollOption[] } +type PollOption = { optionName: string }; +type PollInfo = { pollName: string; description?: string; pollOptions: PollOption[] }; type VotesResp = { - votes: { voterPublicKey: string | string[], optionIndex: number }[] - totalVotes: number - voteCounts: { optionName: string, voteCount: number }[] -} + votes: { voterPublicKey: string | string[]; optionIndex: number }[]; + totalVotes: number; + voteCounts: { optionName: string; voteCount: number }[]; +}; export const PollWidget: React.FC<{ pollName: string }> = ({ pollName }) => { - const { user } = useSelector((s: RootState) => s.auth) - const [info, setInfo] = useState(null) - const [votes, setVotes] = useState(null) - const [submitting, setSubmitting] = useState(false) - const [selected, setSelected] = useState(null) - const [myExistingChoice, setMyExistingChoice] = useState(null) - const [myPendingChoice, setMyPendingChoice] = useState(null) - const myPk = user?.publicKey - const dispatch = useDispatch() + const { user } = useSelector((s: RootState) => s.auth); + const [info, setInfo] = useState(null); + const [votes, setVotes] = useState(null); + const [submitting, setSubmitting] = useState(false); + const [selected, setSelected] = useState(null); + const [myExistingChoice, setMyExistingChoice] = useState(null); + const [myPendingChoice, setMyPendingChoice] = useState(null); + const myPk = user?.publicKey; + const dispatch = useDispatch(); const fetchInfo = async () => { try { - const r = await fetch(`/polls/${encodeURIComponent(pollName)}`) - const j = await r.json() - setInfo(j) + const r = await fetch(`/polls/${encodeURIComponent(pollName)}`); + const j = await r.json(); + setInfo(j); } catch {} - } + }; const fetchVotes = async () => { try { - const r = await fetch(`/polls/votes/${encodeURIComponent(pollName)}`) - const j = await r.json() - setVotes(j) + const r = await fetch(`/polls/votes/${encodeURIComponent(pollName)}`); + const j = await r.json(); + setVotes(j); if (myPk && j?.votes?.length) { const found = j.votes.find((v: any) => { - const arr = Array.isArray(v.voterPublicKey) ? v.voterPublicKey : [v.voterPublicKey] - return arr?.[0]?.toLowerCase?.() === String(myPk).toLowerCase() - }) + const arr = Array.isArray(v.voterPublicKey) ? v.voterPublicKey : [v.voterPublicKey]; + return arr?.[0]?.toLowerCase?.() === String(myPk).toLowerCase(); + }); // <-- NEW: capture your recorded vote separately - setMyExistingChoice(found ? found.optionIndex : null) + setMyExistingChoice(found ? found.optionIndex : null); // Initialize the radio selection once, but don't let it drive the label if (selected === null) { - setSelected(found ? found.optionIndex : null) + setSelected(found ? found.optionIndex : null); } } else { - setMyExistingChoice(null) - if (selected === null) setSelected(null) + setMyExistingChoice(null); + if (selected === null) setSelected(null); } - } catch { } - } + } catch {} + }; const fetchPendingVotes = async () => { if (!user?.address) { - setMyPendingChoice(null) - return + setMyPendingChoice(null); + return; } try { const r = await fetch( - `/transactions/search?txType=VOTE_ON_POLL&reverse=true&confirmationStatus=UNCONFIRMED&limit=100` - ) - const txs = await r.json() + `/transactions/search?txType=VOTE_ON_POLL&reverse=true&confirmationStatus=UNCONFIRMED&limit=100`, + ); + const txs = await r.json(); // Filter to only this user's unconfirmed votes for this poll const matching = txs - .filter((tx: any) => - tx.pollName === pollName && - tx.creatorAddress?.toLowerCase() === user.address.toLowerCase() + .filter( + (tx: any) => + tx.pollName === pollName && + tx.creatorAddress?.toLowerCase() === user.address.toLowerCase(), ) - .sort((a: any, b: any) => b.timestamp - a.timestamp) + .sort((a: any, b: any) => b.timestamp - a.timestamp); if (matching.length > 0) { - setMyPendingChoice(matching[0].optionIndex) + setMyPendingChoice(matching[0].optionIndex); } else { - setMyPendingChoice(null) + setMyPendingChoice(null); } } catch { - setMyPendingChoice(null) + setMyPendingChoice(null); } - } + }; useEffect(() => { - fetchInfo() - fetchVotes() - fetchPendingVotes() + fetchInfo(); + fetchVotes(); + fetchPendingVotes(); // eslint-disable-next-line - }, [pollName, myPk]) + }, [pollName, myPk]); - const total = votes?.totalVotes || 0 + const total = votes?.totalVotes || 0; const countsByOption = useMemo(() => { - const map = new Map() - votes?.voteCounts?.forEach(v => map.set(v.optionName, v.voteCount)) - return map - }, [votes]) + const map = new Map(); + votes?.voteCounts?.forEach((v) => map.set(v.optionName, v.voteCount)); + return map; + }, [votes]); const doVote = async () => { - if (selected === null) return + if (selected === null) return; try { - setSubmitting(true) + setSubmitting(true); await qortalRequest({ action: 'VOTE_ON_POLL', pollName, - optionIndex: selected - }) + optionIndex: selected, + }); dispatch( - setNotification({ - msg: 'Voting successful', - alertType: 'success' - }) - ) - await fetchVotes() - await fetchPendingVotes() + setNotification({ + msg: 'Voting successful', + alertType: 'success', + }), + ); + await fetchVotes(); + await fetchPendingVotes(); } catch (e: any) { dispatch( - setNotification({ - msg: e?.message || 'Voting failed', - alertType: 'error' - }) - ) + setNotification({ + msg: e?.message || 'Voting failed', + alertType: 'error', + }), + ); } finally { - setSubmitting(false) + setSubmitting(false); } - } + }; - if (!info) return null + if (!info) return null; return ( - {info.pollName} - {info.description && {info.description}} + + {info.pollName} + + {info.description && ( + + {info.description} + + )} setSelected(Number(e.target.value))} > {info.pollOptions?.map((opt, i) => { - const count = countsByOption.get(opt.optionName) || 0 - const pct = total ? Math.round((count / total) * 100) : 0 - const isMine = myPk ? myExistingChoice === i : false - const isPending = myPk ? myPendingChoice === i : false + const count = countsByOption.get(opt.optionName) || 0; + const pct = total ? Math.round((count / total) * 100) : 0; + const isMine = myPk ? myExistingChoice === i : false; + const isPending = myPk ? myPendingChoice === i : false; return ( } label={opt.optionName} /> @@ -151,19 +168,23 @@ export const PollWidget: React.FC<{ pollName: string }> = ({ pollName }) => { {isMine ? ` • Your choice${isPending ? ' (pending)' : ''}` : ''} - ) + ); })} - - ) -} + ); +}; -export default PostPublishModal +export default PostPublishModal; diff --git a/src/components/common/PublishAudio.tsx b/src/components/common/PublishAudio.tsx index d9e6952..e9f1a34 100644 --- a/src/components/common/PublishAudio.tsx +++ b/src/components/common/PublishAudio.tsx @@ -1,23 +1,22 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { setNotification } from '../../state/features/notificationsSlice' -import { RootState } from '../../state/store' -import ShortUniqueId from 'short-unique-id' +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setNotification } from '../../state/features/notificationsSlice'; +import { RootState } from '../../state/store'; +import ShortUniqueId from 'short-unique-id'; -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); interface IPublishVideo { - title: string - description: string - base64: string - category: string - editVideoIdentifier?: string | null | undefined - + title: string; + description: string; + base64: string; + category: string; + editVideoIdentifier?: string | null | undefined; } export const usePublishAudio = () => { - const { user } = useSelector((state: RootState) => state.auth) - const dispatch = useDispatch() + const { user } = useSelector((state: RootState) => state.auth); + const dispatch = useDispatch(); const publishAudio = async ({ editVideoIdentifier, title, @@ -26,43 +25,43 @@ export const usePublishAudio = () => { category, ...rest }: IPublishVideo) => { - let address - let name - let errorMsg = '' + let address; + let name; + let errorMsg = ''; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ''; - const missingFields = [] + const missingFields = []; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = 'Cannot post without a name'; } - if (!title) missingFields.push('title') + if (!title) missingFields.push('title'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) + alertType: 'error', + }), + ); + throw new Error(errorMsg); } try { - const id = uid() + const id = uid(); - let identifier = `qaudio_qblog_${id}` - if(editVideoIdentifier){ - identifier = editVideoIdentifier + let identifier = `qaudio_qblog_${id}`; + if (editVideoIdentifier) { + identifier = editVideoIdentifier; } const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', @@ -73,39 +72,38 @@ export const usePublishAudio = () => { description: description, category: category, ...rest, - identifier: identifier - }) + identifier: identifier, + }); dispatch( setNotification({ msg: 'Audio successfully published', - alertType: 'success' - }) - ) - return resourceResponse + alertType: 'success', + }), + ); + return resourceResponse; } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to publish audio', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to publish audio', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || error?.message || 'Failed to publish audio', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) - + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); } - } + }; return { - publishAudio - } -} + publishAudio, + }; +}; diff --git a/src/components/common/PublishGeneric.tsx b/src/components/common/PublishGeneric.tsx index c744a8d..35b82b9 100644 --- a/src/components/common/PublishGeneric.tsx +++ b/src/components/common/PublishGeneric.tsx @@ -1,26 +1,26 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { setNotification } from '../../state/features/notificationsSlice' -import { RootState } from '../../state/store' -import ShortUniqueId from 'short-unique-id' +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setNotification } from '../../state/features/notificationsSlice'; +import { RootState } from '../../state/store'; +import ShortUniqueId from 'short-unique-id'; -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); interface IPublishGeneric { - title: string - description: string - base64?: string - file?: File - category: string - service: string - identifierPrefix: string - filename: string - editVideoIdentifier?: string | null | undefined + title: string; + description: string; + base64?: string; + file?: File; + category: string; + service: string; + identifierPrefix: string; + filename: string; + editVideoIdentifier?: string | null | undefined; } export const usePublishGeneric = () => { - const { user } = useSelector((state: RootState) => state.auth) - const dispatch = useDispatch() + const { user } = useSelector((state: RootState) => state.auth); + const dispatch = useDispatch(); const publishGeneric = async ({ editVideoIdentifier, service, @@ -33,43 +33,43 @@ export const usePublishGeneric = () => { category, ...rest }: IPublishGeneric) => { - let address - let name - let errorMsg = '' + let address; + let name; + let errorMsg = ''; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ''; - const missingFields = [] + const missingFields = []; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = 'Cannot post without a name'; } - if (!title) missingFields.push('title') + if (!title) missingFields.push('title'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) + alertType: 'error', + }), + ); + throw new Error(errorMsg); } try { - const id = uid() + const id = uid(); - let identifier = `${identifierPrefix}_${id}` + let identifier = `${identifierPrefix}_${id}`; if (editVideoIdentifier) { - identifier = editVideoIdentifier + identifier = editVideoIdentifier; } const resourceResponse = await qortalRequest({ @@ -82,39 +82,38 @@ export const usePublishGeneric = () => { category: category, filename, ...rest, - identifier: identifier - }) + identifier: identifier, + }); dispatch( setNotification({ msg: `${service} successfully published`, - alertType: 'success' - }) - ) - return resourceResponse + alertType: 'success', + }), + ); + return resourceResponse; } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || `Failed to publish ${service}`, - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || `Failed to publish ${service}`, - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { - msg: - error?.message || error?.message || `Failed to publish ${service}`, - alertType: 'error' - } + msg: error?.message || error?.message || `Failed to publish ${service}`, + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); } - } + }; return { - publishGeneric - } -} + publishGeneric, + }; +}; diff --git a/src/components/common/PublishVideo.tsx b/src/components/common/PublishVideo.tsx index 4042994..aeac89a 100644 --- a/src/components/common/PublishVideo.tsx +++ b/src/components/common/PublishVideo.tsx @@ -1,23 +1,23 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { setNotification } from '../../state/features/notificationsSlice' -import { RootState } from '../../state/store' -import ShortUniqueId from 'short-unique-id' +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { setNotification } from '../../state/features/notificationsSlice'; +import { RootState } from '../../state/store'; +import ShortUniqueId from 'short-unique-id'; -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); interface IPublishVideo { - title: string - description: string - base64?: string - category: string - editVideoIdentifier?: string | null | undefined - file?: File + title: string; + description: string; + base64?: string; + category: string; + editVideoIdentifier?: string | null | undefined; + file?: File; } export const usePublishVideo = () => { - const { user } = useSelector((state: RootState) => state.auth) - const dispatch = useDispatch() + const { user } = useSelector((state: RootState) => state.auth); + const dispatch = useDispatch(); const publishVideo = async ({ file, editVideoIdentifier, @@ -27,43 +27,43 @@ export const usePublishVideo = () => { category, ...rest }: IPublishVideo) => { - let address - let name - let errorMsg = '' + let address; + let name; + let errorMsg = ''; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ''; - const missingFields = [] + const missingFields = []; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = 'Cannot post without a name'; } - if (!title) missingFields.push('title') + if (!title) missingFields.push('title'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) + alertType: 'error', + }), + ); + throw new Error(errorMsg); } try { - const id = uid() + const id = uid(); - let identifier = `qvideo_qblog_${id}` + let identifier = `qvideo_qblog_${id}`; if (editVideoIdentifier) { - identifier = editVideoIdentifier + identifier = editVideoIdentifier; } const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', @@ -75,38 +75,38 @@ export const usePublishVideo = () => { description: description, category: category, ...rest, - identifier: identifier - }) + identifier: identifier, + }); dispatch( setNotification({ msg: 'Video successfully published', - alertType: 'success' - }) - ) - return resourceResponse + alertType: 'success', + }), + ); + return resourceResponse; } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to publish video', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to publish video', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to publish video', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); } - } + }; return { - publishVideo - } -} + publishVideo, + }; +}; diff --git a/src/components/common/ResponsiveImage.tsx b/src/components/common/ResponsiveImage.tsx index d903dce..e22eb93 100644 --- a/src/components/common/ResponsiveImage.tsx +++ b/src/components/common/ResponsiveImage.tsx @@ -1,13 +1,13 @@ -import React, { useState, useEffect, CSSProperties } from 'react' -import Skeleton from '@mui/material/Skeleton' -import { Box } from '@mui/material' +import React, { useState, useEffect, CSSProperties } from 'react'; +import Skeleton from '@mui/material/Skeleton'; +import { Box } from '@mui/material'; interface ResponsiveImageProps { - src: string - dimensions: string - alt?: string - className?: string - style?: CSSProperties + src: string; + dimensions: string; + alt?: string; + className?: string; + style?: CSSProperties; } const ResponsiveImage: React.FC = ({ @@ -15,44 +15,44 @@ const ResponsiveImage: React.FC = ({ dimensions, alt, className, - style + style, }) => { - const [loading, setLoading] = useState(true) - const matchResult = dimensions?.match(/v1\.(\d+(\.\d+)?)x(\d+)/) + const [loading, setLoading] = useState(true); + const matchResult = dimensions?.match(/v1\.(\d+(\.\d+)?)x(\d+)/); - const width = matchResult ? parseFloat(matchResult[1]) : 1 // Default width value - const height = matchResult ? parseInt(matchResult[3], 10) : 1 // Default height value + const width = matchResult ? parseFloat(matchResult[1]) : 1; // Default width value + const height = matchResult ? parseInt(matchResult[3], 10) : 1; // Default height value - const aspectRatio = (height / width) * 100 + const aspectRatio = (height / width) * 100; useEffect(() => { if (dimensions === 'v1.0x0') { - setLoading(false) - return + setLoading(false); + return; } - }, [dimensions]) + }, [dimensions]); if (dimensions === 'v1.0x0' || !dimensions) { - return null + return null; } const imageStyle: CSSProperties = { width: '100%', height: '100%', - objectFit: 'cover' - } + objectFit: 'cover', + }; const wrapperStyle: CSSProperties = { position: 'relative', paddingBottom: `${aspectRatio}%`, overflow: 'hidden', - ...style - } + ...style, + }; return ( {/* = ({ paddingBottom: `${(height / width) * 100}%`, objectFit: 'contain', visibility: loading ? 'visible' : 'hidden', - borderRadius: '8px' + borderRadius: '8px', }} /> )} @@ -86,11 +86,11 @@ const ResponsiveImage: React.FC = ({ height: 'auto', borderRadius: '8px', visibility: loading ? 'hidden' : 'visible', - position: loading ? 'absolute' : 'unset' + position: loading ? 'absolute' : 'unset', }} /> - ) + ); return (
@@ -102,7 +102,7 @@ const ResponsiveImage: React.FC = ({ top: 0, left: 0, right: 0, - bottom: 0 + bottom: 0, }} /> ) : ( @@ -113,12 +113,12 @@ const ResponsiveImage: React.FC = ({ ...imageStyle, position: 'absolute', top: 0, - left: 0 + left: 0, }} /> )}
- ) -} + ); +}; -export default ResponsiveImage +export default ResponsiveImage; diff --git a/src/components/common/Tipping/Tipping.tsx b/src/components/common/Tipping/Tipping.tsx index ca01dab..21daf17 100644 --- a/src/components/common/Tipping/Tipping.tsx +++ b/src/components/common/Tipping/Tipping.tsx @@ -11,34 +11,34 @@ import { InputLabel, Tooltip, Typography, - useTheme -} from '@mui/material' -import React, { useCallback, useState } from 'react' -import { CardContentContainerComment } from '../../../pages/BlogList/PostPreview-styles' -import { StyledCardHeaderComment } from '../../../pages/BlogList/PostPreview-styles' -import { StyledCardColComment } from '../../../pages/BlogList/PostPreview-styles' -import { AuthorTextComment } from '../../../pages/BlogList/PostPreview-styles' -import { StyledCardContentComment } from '../../../pages/BlogList/PostPreview-styles' -import MenuItem from '@mui/material/MenuItem' -import Select, { SelectChangeEvent } from '@mui/material/Select' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../../state/store' -import Portal from '../Portal' -import MonetizationOnIcon from '@mui/icons-material/MonetizationOn' + useTheme, +} from '@mui/material'; +import React, { useCallback, useState } from 'react'; +import { CardContentContainerComment } from '../../../pages/BlogList/PostPreview-styles'; +import { StyledCardHeaderComment } from '../../../pages/BlogList/PostPreview-styles'; +import { StyledCardColComment } from '../../../pages/BlogList/PostPreview-styles'; +import { AuthorTextComment } from '../../../pages/BlogList/PostPreview-styles'; +import { StyledCardContentComment } from '../../../pages/BlogList/PostPreview-styles'; +import MenuItem from '@mui/material/MenuItem'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../../state/store'; +import Portal from '../Portal'; +import MonetizationOnIcon from '@mui/icons-material/MonetizationOn'; interface TippingProps { - name: string - onSubmit: () => void - onClose: () => void - onlyIcon?: boolean + name: string; + onSubmit: () => void; + onClose: () => void; + onlyIcon?: boolean; } -import QORT from '../../../assets/img/qort.png' -import ARRR from '../../../assets/img/arrr.png' -import LTC from '../../../assets/img/ltc.png' -import BTC from '../../../assets/img/btc.png' -import DOGE from '../../../assets/img/doge.png' -import DGB from '../../../assets/img/dgb.png' -import RVN from '../../../assets/img/rvn.png' -import { setNotification } from '../../../state/features/notificationsSlice' +import QORT from '../../../assets/img/qort.png'; +import ARRR from '../../../assets/img/arrr.png'; +import LTC from '../../../assets/img/ltc.png'; +import BTC from '../../../assets/img/btc.png'; +import DOGE from '../../../assets/img/doge.png'; +import DGB from '../../../assets/img/dgb.png'; +import RVN from '../../../assets/img/rvn.png'; +import { setNotification } from '../../../state/features/notificationsSlice'; const coins = [ { value: 'QORT', label: 'QORT' }, { value: 'ARRR', label: 'ARRR' }, @@ -46,104 +46,97 @@ const coins = [ { value: 'BTC', label: 'BTC' }, { value: 'DOGE', label: 'DOGE' }, { value: 'DGB', label: 'DGB' }, - { value: 'RVN', label: 'RVN' } -] -export const Tipping = ({ - onSubmit, - onClose, - name, - onlyIcon -}: TippingProps) => { - const { user } = useSelector((state: RootState) => state.auth) - const [isOpen, setIsOpen] = useState(false) - const [selectedCoin, setSelectedCoint] = useState(coins[0]) - const [amount, setAmount] = useState(0) + { value: 'RVN', label: 'RVN' }, +]; +export const Tipping = ({ onSubmit, onClose, name, onlyIcon }: TippingProps) => { + const { user } = useSelector((state: RootState) => state.auth); + const [isOpen, setIsOpen] = useState(false); + const [selectedCoin, setSelectedCoint] = useState(coins[0]); + const [amount, setAmount] = useState(0); - const dispatch = useDispatch() + const dispatch = useDispatch(); const resetValues = () => { - setSelectedCoint(coins[0]) - setAmount(0) - setIsOpen(false) - } + setSelectedCoint(coins[0]); + setAmount(0); + setIsOpen(false); + }; const sendCoin = async () => { try { - if (!name) return + if (!name) return; let res = await qortalRequest({ action: 'GET_NAME_DATA', - name: name - }) - const address = res.owner - if (!address || !amount || !selectedCoin?.value) return + name: name, + }); + const address = res.owner; + if (!address || !amount || !selectedCoin?.value) return; - if (isNaN(amount)) return + if (isNaN(amount)) return; await qortalRequest({ action: 'SEND_COIN', coin: selectedCoin.value, destinationAddress: address, - amount: amount - }) + amount: amount, + }); dispatch( setNotification({ msg: 'Coin successfully sent', - alertType: 'success' - }) - ) - resetValues() - onSubmit() + alertType: 'success', + }), + ); + resetValues(); + onSubmit(); } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to send coin', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to send coin', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to send coin', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); } - } + }; const handleOptionChange = (event: SelectChangeEvent) => { - const optionId = event.target.value - const selectedOption = coins.find( - (option: any) => option.value === optionId - ) - setSelectedCoint(selectedOption || null) - } + const optionId = event.target.value; + const selectedOption = coins.find((option: any) => option.value === optionId); + setSelectedCoint(selectedOption || null); + }; const getLogo = (coin: string) => { switch (coin) { case 'QORT': - return QORT + return QORT; case 'ARRR': - return ARRR + return ARRR; case 'LTC': - return LTC + return LTC; case 'BTC': - return BTC + return BTC; case 'DOGE': - return DOGE + return DOGE; case 'DGB': - return DGB + return DGB; case 'RVN': - return RVN + return RVN; default: - '' + ''; // code block } - } + }; return ( @@ -161,20 +154,20 @@ export const Tipping = ({ display: 'flex', alignItems: 'center', gap: 1, - cursor: 'pointer' + cursor: 'pointer', }} onClick={() => setIsOpen((prev) => !prev)} > {!onlyIcon && ( Support @@ -196,15 +189,13 @@ export const Tipping = ({ sx={{ width: '300px', display: 'flex', - justifyContent: 'center' + justifyContent: 'center', }} > To - - Coin - + Coin - - Amount - + Amount @@ -270,9 +259,9 @@ export const Tipping = ({ - ) -} + ); +}; -export default VideoModal +export default VideoModal; diff --git a/src/components/editor/BlogEditor.css b/src/components/editor/BlogEditor.css index 0a3e0e8..43fccb5 100644 --- a/src/components/editor/BlogEditor.css +++ b/src/components/editor/BlogEditor.css @@ -1,78 +1,75 @@ /* src/components/BlogEditor.css */ .blog-editor { - max-width: 800px; - margin: 0 auto; - padding: 1rem; - line-height: 1.5; - font-size: 18px; - max-height: 50vh; - overflow-y: auto; - min-height: 200px; - z-index: 500; - } - - .toolbar { - display: flex; - justify-content: center; - margin-bottom: 1rem; - } - + max-width: 800px; + margin: 0 auto; + padding: 1rem; + line-height: 1.5; + font-size: 18px; + max-height: 50vh; + overflow-y: auto; + min-height: 200px; + z-index: 500; +} - - .toolbar-button:focus { - outline: none; - } - - .code-block { - background-color: #2c2b31; - color: rgb(238, 234, 234); - border-radius: 3px; - padding: 10px; - margin: 10px 0; - font-family: 'Courier New', Courier, monospace; - white-space: pre-wrap; - overflow-x: auto; - max-width: 100%; - font-size: 14px; - } +.toolbar { + display: flex; + justify-content: center; + margin-bottom: 1rem; +} - .paragraph { - font-size: 20px; - margin: 0px; - } +.toolbar-button:focus { + outline: none; +} - .paragraph-mail { - font-size: 16px; - margin: 0px; - } +.code-block { + background-color: #2c2b31; + color: rgb(238, 234, 234); + border-radius: 3px; + padding: 10px; + margin: 10px 0; + font-family: 'Courier New', Courier, monospace; + white-space: pre-wrap; + overflow-x: auto; + max-width: 100%; + font-size: 14px; +} - .toolbar-button { - background-color: white; - border: 1px solid gray; - border-radius: 5px; - margin-right: 5px; - cursor: pointer; - outline: none; - height: 32px; - width: 32px; - display: flex; - align-items: center; - justify-content: center; - } - - .toolbar-button.active { - background-color: lightgray; - } +.paragraph { + font-size: 20px; + margin: 0px; +} - .h2 { - font-size: 25px - } +.paragraph-mail { + font-size: 16px; + margin: 0px; +} - .h2 { - font-size: 22px - } +.toolbar-button { + background-color: white; + border: 1px solid gray; + border-radius: 5px; + margin-right: 5px; + cursor: pointer; + outline: none; + height: 32px; + width: 32px; + display: flex; + align-items: center; + justify-content: center; +} - .align-center { - text-align: center; - } +.toolbar-button.active { + background-color: lightgray; +} +.h2 { + font-size: 25px; +} + +.h2 { + font-size: 22px; +} + +.align-center { + text-align: center; +} diff --git a/src/components/editor/BlogEditor.tsx b/src/components/editor/BlogEditor.tsx index 46f715d..dd9cf3c 100644 --- a/src/components/editor/BlogEditor.tsx +++ b/src/components/editor/BlogEditor.tsx @@ -1,48 +1,47 @@ // src/components/BlogEditor.tsx -import React, { useMemo, useState, useCallback } from 'react'; -import { createEditor, Descendant, Editor, Transforms, Range } from 'slate' -import SvgIcon from '@material-ui/core/SvgIcon' +import React, { useMemo, useState } from 'react'; +import { createEditor, Descendant, Editor, Transforms, Range } from 'slate'; import { Slate, Editable, withReact, RenderElementProps, RenderLeafProps, - useSlate -} from 'slate-react' -import { styled } from '@mui/system' -import { CustomElement, CustomText, FormatMark } from './customTypes' -import './BlogEditor.css' -import { Modal, Box, TextField, Button } from '@mui/material' + useSlate, +} from 'slate-react'; +import { styled } from '@mui/material/styles'; +import { CustomElement, CustomText, FormatMark } from './customTypes'; +import './BlogEditor.css'; +import { Modal, Box, TextField, Button } from '@mui/material'; -import { AlignCenterSVG } from '../../assets/svgs/AlignCenterSVG' -import { BoldSVG } from '../../assets/svgs/BoldSVG' -import { ItalicSVG } from '../../assets/svgs/ItalicSVG' -import { UnderlineSVG } from '../../assets/svgs/UnderlineSVG' -import { H2SVG } from '../../assets/svgs/H2SVG' -import { H3SVG } from '../../assets/svgs/H3SVG' -import { AlignLeftSVG } from '../../assets/svgs/AlignLeftSVG' -import { AlignRightSVG } from '../../assets/svgs/AlignRightSVG' -import { CodeBlockSVG } from '../../assets/svgs/CodeBlockSVG' -import { LinkSVG } from '../../assets/svgs/LinkSVG' +import { AlignCenterSVG } from '../../assets/svgs/AlignCenterSVG'; +import { BoldSVG } from '../../assets/svgs/BoldSVG'; +import { ItalicSVG } from '../../assets/svgs/ItalicSVG'; +import { UnderlineSVG } from '../../assets/svgs/UnderlineSVG'; +import { H2SVG } from '../../assets/svgs/H2SVG'; +import { H3SVG } from '../../assets/svgs/H3SVG'; +import { AlignLeftSVG } from '../../assets/svgs/AlignLeftSVG'; +import { AlignRightSVG } from '../../assets/svgs/AlignRightSVG'; +import { CodeBlockSVG } from '../../assets/svgs/CodeBlockSVG'; +import { LinkSVG } from '../../assets/svgs/LinkSVG'; const initialValue: Descendant[] = [ { type: 'paragraph', - children: [{ text: 'Start writing your blog post...' }] - } -] + children: [{ text: 'Start writing your blog post...' }], + }, +]; interface MyComponentProps { - addPostSection?: (value: any) => void - editPostSection?: (value: any, section: any) => void - defaultValue?: any - section?: any - value: any - setValue: (value: any) => void - editorKey?: number - mode?: string + addPostSection?: (value: any) => void; + editPostSection?: (value: any, section: any) => void; + defaultValue?: any; + section?: any; + value: any; + setValue: (value: any) => void; + editorKey?: number; + mode?: string; } const ModalBox = styled(Box)(({ theme }) => ({ @@ -57,8 +56,8 @@ const ModalBox = styled(Box)(({ theme }) => ({ borderRadius: '5px', alignItems: 'center', display: 'flex', - flex: 0 -})) + flex: 0, +})); const BlogEditor: React.FC = ({ addPostSection, @@ -68,112 +67,102 @@ const BlogEditor: React.FC = ({ value, setValue, editorKey, - mode + mode, }) => { - const editor = useMemo(() => withReact(createEditor()), []) + const editor = useMemo(() => withReact(createEditor()), []); // const [value, setValue] = useState(defaultValue || initialValue); const isTextAlignmentActive = (editor: Editor, alignment: string) => { const [match] = Editor.nodes(editor, { - match: (n) => { - return n?.textAlign === alignment?.replace(/^align-/, '') - } - }) - return !!match - } + match: (n) => (n as any)?.textAlign === alignment.replace(/^align-/, ''), + }); + return !!match; + }; const toggleTextAlignment = (editor: Editor, alignment: string) => { - const isActive = isTextAlignmentActive(editor, alignment) + const isActive = isTextAlignmentActive(editor, alignment); + const map = { + 'align-left': 'left', + 'align-center': 'center', + 'align-right': 'right', + } as const; + const value = isActive ? undefined : map[alignment as keyof typeof map]; Transforms.setNodes( editor, - { style: { textAlign: isActive ? 'inherit' : alignment } }, - { match: (n) => Editor.isBlock(editor, n) } - ) - } + { textAlign: value }, + { match: (n) => Editor.isBlock(editor, n as any) }, + ); + }; - const toggleMark = (editor: Editor, format: FormatMark) => { - if ( - format === 'align-left' || - format === 'align-center' || - format === 'align-right' - ) { - toggleTextAlignment(editor, format) + const toggleMark = ( + editor: Editor, + format: FormatMark | 'align-left' | 'align-center' | 'align-right', + ) => { + if (format === 'align-left' || format === 'align-center' || format === 'align-right') { + toggleTextAlignment(editor, format); } else { - const isActive = Editor?.marks(editor)?.[format] === true + const isActive = Editor?.marks(editor)?.[format] === true; if (isActive) { - Editor?.removeMark(editor, format) + Editor?.removeMark(editor, format); } else { - Editor?.addMark(editor, format, true) + Editor?.addMark(editor, format, true); } } - } + }; - const newValue = useMemo(() => [...(value || initialValue)], [value]) + const newValue = useMemo(() => [...(value || initialValue)], [value]); - const types = ['paragraph', 'heading-2', 'heading-3'] + const types = ['paragraph', 'heading-2', 'heading-3']; - const setTextAlignment = (editor, alignment) => { - const isActive = isTextAlignmentActive(editor, alignment) - const alignmentType = '' + const setTextAlignment = (editor: Editor, alignment: string) => { + const isActive = isTextAlignmentActive(editor, alignment); + const map = { + 'align-left': 'left', + 'align-center': 'center', + 'align-right': 'right', + } as const; + const value = isActive ? undefined : map[alignment as keyof typeof map]; Transforms?.setNodes( editor, { - textAlign: isActive ? null : alignment + textAlign: value, }, { match: (n) => - n.type === 'heading-2' || - n.type === 'heading-3' || - n.type === 'paragraph' - } - ) - } + (n as any)?.type === 'heading-2' || + (n as any)?.type === 'heading-3' || + (n as any)?.type === 'paragraph', + }, + ); + }; const ToolbarButton: React.FC<{ - format: FormatMark | string - label: string - editor: Editor - children: React.ReactNode + format: FormatMark | string; + label: string; + editor: Editor; + children: React.ReactNode; }> = ({ format, label, editor, children }) => { - useSlate() + useSlate(); const onClick = () => { if (format === 'heading-2' || format === 'heading-3') { - toggleBlock(editor, format) - } else if ( - format === 'bold' || - format === 'italic' || - format === 'underline' || - format === '' - ) { - toggleMark(editor, format) - } else if ( - format === 'align-left' || - format === 'align-center' || - format === 'align-right' - ) { - setTextAlignment(editor, format?.replace(/^align-/, '')) + toggleBlock(editor, format); + } else if (format === 'bold' || format === 'italic' || format === 'underline') { + toggleMark(editor, format); + } else if (format === 'align-left' || format === 'align-center' || format === 'align-right') { + setTextAlignment(editor, format?.replace(/^align-/, '')); } - } + }; - let isActive = false + let isActive = false; try { - if ( - format === 'align-left' || - format === 'align-center' || - format === 'align-right' - ) { - isActive = isTextAlignmentActive(editor, format) + if (format === 'align-left' || format === 'align-center' || format === 'align-right') { + isActive = isTextAlignmentActive(editor, format); } else if (format === 'heading-2' || format === 'heading-3') { - isActive = isBlockActive(editor, format) - } else if ( - format === 'bold' || - format === 'italic' || - format === 'underline' || - format === '' - ) { - isActive = Editor?.marks(editor)?.[format] === true + isActive = isBlockActive(editor, format); + } else if (format === 'bold' || format === 'italic' || format === 'underline') { + isActive = Editor?.marks(editor)?.[format] === true; } } catch (error) {} @@ -181,32 +170,32 @@ const BlogEditor: React.FC = ({ - ) - } + ); + }; const ToolbarButtonCodeBlock: React.FC<{ - format: FormatMark | string - label: string - editor: Editor - children: React.ReactNode + format: FormatMark | string; + label: string; + editor: Editor; + children: React.ReactNode; }> = ({ format, label, editor, children }) => { - const editor2 = useSlate() + const editor2 = useSlate(); const onClick = () => { if (format === 'code-block') { - toggleBlock(editor, 'code-block') + toggleBlock(editor, 'code-block'); } - } - let isActive = false + }; + let isActive = false; try { if (format === 'code-block') { - isActive = isBlockActive(editor, format) + isActive = isBlockActive(editor, format); } } catch (error) {} @@ -214,54 +203,55 @@ const BlogEditor: React.FC = ({ - ) - } + ); + }; const ToolbarButtonAlign: React.FC<{ - format: string - label: string - editor: Editor + format: 'left' | 'center' | 'right'; + label: string; + editor: Editor; }> = ({ format, label, editor }) => { - const isActive = - Editor?.nodes(editor, { - match: (n) => n?.align === format - })?.length > 0 + const iterator = Editor?.nodes(editor, { + match: (n) => (n as any)?.textAlign === format, + }); + const first = iterator?.next(); + const isActive = !!first && !first.done; return ( - ) - } + ); + }; const ToolbarButtonCodeLink: React.FC<{ - format: FormatMark | string - label: string - editor: Editor - children: React.ReactNode + format: FormatMark | string; + label: string; + editor: Editor; + children: React.ReactNode; }> = ({ format, label, editor, children }) => { - useSlate() + useSlate(); - let isActive = false + let isActive = false; try { if (format === 'link') { - isActive = !!Editor?.marks(editor)?.link + isActive = !!Editor?.marks(editor)?.link; } } catch (error) {} @@ -269,137 +259,140 @@ const BlogEditor: React.FC = ({ - ) - } + ); + }; // Create a toggleBlock function and an isBlockActive function to handle block elements - const toggleBlock = (editor: Editor, format: string) => { - const isActive = isBlockActive(editor, format) + const toggleBlock = ( + editor: Editor, + format: 'paragraph' | 'heading-2' | 'heading-3' | 'code-block', + ) => { + const isActive = isBlockActive(editor, format); Transforms?.unwrapNodes(editor, { - match: (n) => Editor?.isBlock(editor, n), - split: true - }) + match: (n) => Editor?.isBlock(editor, n as any), + split: true, + }); if (isActive) { - Transforms?.setNodes(editor, { type: 'paragraph' }) + Transforms?.setNodes(editor, { type: 'paragraph' }); } else { - Transforms?.setNodes(editor, { type: format }) + Transforms?.setNodes(editor, { type: format }); } - } + }; const isBlockActive = (editor: Editor, format: string) => { const [match] = Editor?.nodes(editor, { - match: (n) => n?.type === format - }) - return !!match - } + match: (n) => (n as any)?.type === format, + }); + return !!match; + }; const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter' && isBlockActive(editor, 'code-block')) { - event.preventDefault() - editor?.insertText('\n') + event.preventDefault(); + editor?.insertText('\n'); } if (event.key === 'ArrowDown' && isBlockActive(editor, 'code-block')) { - event.preventDefault() + event.preventDefault(); Transforms?.insertNodes(editor, { type: 'paragraph', - children: [{ text: '' }] - }) + children: [{ text: '' }], + }); } - } + }; const handleChange = (newValue: Descendant[]) => { - setValue(newValue) - } + setValue(newValue); + }; const toggleLink = (editor: Editor, url: string) => { - const { selection } = editor + const { selection } = editor; if (selection && !Range.isCollapsed(selection)) { - const isLink = Editor?.marks(editor)?.link === true - const isInsideLink = isLinkActive(editor) + const isLink = !!Editor?.marks(editor)?.link; + const isInsideLink = isLinkActive(editor); if (isLink) { - Editor?.removeMark(editor, 'link') + Editor?.removeMark(editor, 'link'); } else if (url) { - Editor?.addMark(editor, 'link', url) + Editor?.addMark(editor, 'link', url); } } - } + }; - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(false); - const initialValue = 'qortal://' - const [inputValue, setInputValue] = useState(initialValue) + const initialValue = 'qortal://'; + const [inputValue, setInputValue] = useState(initialValue); - const handleChangeLink = (event) => { - const newValue = event?.target?.value + const handleChangeLink = (event: React.ChangeEvent) => { + const newValue = event?.target?.value; if (newValue?.startsWith(initialValue)) { - setInputValue(newValue) + setInputValue(newValue); } - } + }; const isLinkActive = (editor: Editor) => { const [link] = Editor?.nodes(editor, { - match: (n) => n?.type === 'link' - }) - return !!link - } + match: (n) => (n as any)?.type === 'link', + }); + return !!link; + }; const handleSaveClick = () => { - const marks = Editor?.marks(editor) - const isLink = marks?.link === true + const marks = Editor?.marks(editor); + const isLink = !!marks?.link; if (isLink) { - Editor?.removeMark(editor, 'link') - return // Return early to skip the rest of the function + Editor?.removeMark(editor, 'link'); + return; // Return early to skip the rest of the function } - toggleLink(editor, inputValue) - setOpen(false) - } + toggleLink(editor, inputValue); + setOpen(false); + }; const onClose = () => { - setOpen(false) - } + setOpen(false); + }; const handlePaste = (event: React.ClipboardEvent) => { - event.preventDefault() - const text = event?.clipboardData?.getData('text/plain') - const isCodeBlock = isBlockActive(editor, 'code-block') + event.preventDefault(); + const text = event?.clipboardData?.getData('text/plain'); + const isCodeBlock = isBlockActive(editor, 'code-block'); if (isCodeBlock) { - const lines = text?.split('\n') + const lines = text?.split('\n'); const fragment: Descendant[] = [ { type: 'code-block', children: lines?.map((line) => ({ type: 'code-line', - children: [{ text: line }] - })) - } - ] + children: [{ text: line }], + })), + }, + ]; - Transforms?.insertFragment(editor, fragment) + Transforms?.insertFragment(editor, fragment as any); } else if (text) { const fragment = text?.split('\n').map((line) => ({ type: 'paragraph', - children: [{ text: line }] - })) + children: [{ text: line }], + })); - Transforms?.insertFragment(editor, fragment) + Transforms?.insertFragment(editor, fragment as any); } - } + }; return ( = ({ border: '1px solid', borderRadius: '5px', marginTop: '20px', - padding: '10px' + padding: '10px', }} > = ({ - + @@ -461,77 +450,62 @@ const BlogEditor: React.FC = ({ renderLeaf={renderLeaf} onKeyDown={handleKeyDown} onPaste={handlePaste} - mode={mode} /> - + {editPostSection && ( - + )} - ) -} + ); +}; -export default BlogEditor +export default BlogEditor; -type ExtendedRenderElementProps = RenderElementProps & { mode?: string } +type ExtendedRenderElementProps = RenderElementProps & { mode?: string }; export const renderElement = ({ attributes, children, element, - mode + mode, }: ExtendedRenderElementProps) => { switch (element.type) { case 'block-quote': - return
{children}
+ return
{children}
; case 'heading-2': return ( -

+

{children}

- ) + ); case 'heading-3': return ( -

+

{children}

- ) + ); case 'code-block': return (
           {children}
         
- ) + ); case 'code-line': - return
{children}
+ return
{children}
; case 'link': return ( {children} - ) + ); default: return (

{children}

- ) + ); } -} - +}; export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => { - let el = children + let el = children; if (leaf.bold) { - el = {el} + el = {el}; } if (leaf.italic) { - el = {el} + el = {el}; } if (leaf.underline) { - el = {el} + el = {el}; } if (leaf.link) { @@ -566,8 +539,8 @@ export const renderLeaf = ({ attributes, children, leaf }: RenderLeafProps) => { {el} - ) + ); } - return {el} -} \ No newline at end of file + return {el}; +}; diff --git a/src/components/editor/ReadOnlySlate.tsx b/src/components/editor/ReadOnlySlate.tsx index cd2616c..d343777 100644 --- a/src/components/editor/ReadOnlySlate.tsx +++ b/src/components/editor/ReadOnlySlate.tsx @@ -1,15 +1,15 @@ import React, { useMemo } from 'react'; import { createEditor, Descendant, Editor } from 'slate'; -import { withReact, Slate, Editable, RenderElementProps, RenderLeafProps } from 'slate-react'; +import { withReact, Slate, Editable, RenderElementProps, RenderLeafProps } from 'slate-react'; import { renderElement, renderLeaf } from './BlogEditor'; interface ReadOnlySlateProps { - content: any - mode?: string + content: any; + mode?: string; } const ReadOnlySlate: React.FC = ({ content, mode }) => { - const editor = useMemo(() => withReact(createEditor()), []) - const value = useMemo(() => content, [content]) + const editor = useMemo(() => withReact(createEditor()), []); + const value = useMemo(() => content, [content]); return ( {}}> @@ -19,7 +19,7 @@ const ReadOnlySlate: React.FC = ({ content, mode }) => { renderLeaf={renderLeaf} /> - ) -} + ); +}; -export default ReadOnlySlate; \ No newline at end of file +export default ReadOnlySlate; diff --git a/src/components/editor/customTypes.ts b/src/components/editor/customTypes.ts index 46142fb..5d373bc 100644 --- a/src/components/editor/customTypes.ts +++ b/src/components/editor/customTypes.ts @@ -3,40 +3,72 @@ import { BaseEditor } from 'slate'; import { ReactEditor } from 'slate-react'; export type CustomText = { - text: string - bold?: boolean - italic?: boolean - underline?: boolean - code?: boolean -} + text: string; + bold?: boolean; + italic?: boolean; + underline?: boolean; + code?: boolean; + // link can be used as a mark carrying the href + link?: string; +}; export type HeadingElement = { - type: 'heading' - children: CustomText[] -} + type: 'heading'; + children: CustomText[]; + textAlign?: 'left' | 'center' | 'right' | 'inherit'; +}; + +export type Heading2Element = { + type: 'heading-2'; + children: CustomText[]; + textAlign?: 'left' | 'center' | 'right' | 'inherit'; +}; + +export type Heading3Element = { + type: 'heading-3'; + children: CustomText[]; + textAlign?: 'left' | 'center' | 'right' | 'inherit'; +}; export type BlockQuoteElement = { - type: 'block-quote' - children: CustomText[] -} + type: 'block-quote'; + children: CustomText[]; + textAlign?: 'left' | 'center' | 'right' | 'inherit'; +}; export type ParagraphElement = { - type: 'paragraph' - children: CustomText[] -} + type: 'paragraph'; + children: CustomText[]; + textAlign?: 'left' | 'center' | 'right' | 'inherit'; +}; + +export type CodeLineElement = { + type: 'code-line'; + children: CustomText[]; +}; export type CodeBlockElement = { - type: 'code-block' - children: CustomText[] -} + type: 'code-block'; + children: CodeLineElement[]; +}; + +export type LinkElement = { + type: 'link'; + url?: string; + children: CustomText[]; +}; export type CustomElement = | HeadingElement + | Heading2Element + | Heading3Element | BlockQuoteElement | ParagraphElement | CodeBlockElement + | CodeLineElement + | LinkElement; -export type FormatMark = 'bold' | 'italic' | 'underline' | 'code' +export type FormatMark = 'bold' | 'italic' | 'underline' | 'code'; declare module 'slate' { interface CustomTypes { diff --git a/src/components/layout/Navbar/Navbar-styles.ts b/src/components/layout/Navbar/Navbar-styles.ts index 7bff108..e7adcff 100644 --- a/src/components/layout/Navbar/Navbar-styles.ts +++ b/src/components/layout/Navbar/Navbar-styles.ts @@ -1,112 +1,112 @@ -import { AppBar, Button, Toolbar, Typography, Box } from '@mui/material' -import { styled } from '@mui/system' +import { AppBar, Button, Toolbar, Typography, Box } from '@mui/material'; +import { styled } from '@mui/system'; export const QblogLogoContainer = styled('img')({ width: 'auto', height: 'auto', userSelect: 'none', objectFit: 'contain', - cursor: 'pointer' -}) + cursor: 'pointer', +}); export const CustomAppBar = styled(AppBar)(({ theme }) => ({ - backgroundColor: theme.palette.mode === "light" ? theme.palette.background.default : "#19191b", + backgroundColor: theme.palette.mode === 'light' ? theme.palette.background.default : '#19191b', [theme.breakpoints.only('xs')]: { gap: '15px', }, -})) +})); export const CustomToolbar = styled(Toolbar)({ display: 'flex', justifyContent: 'space-between', - alignItems: 'center' -}) + alignItems: 'center', +}); export const CustomTitle = styled(Typography)({ fontWeight: 600, - color: '#000000' -}) + color: '#000000', +}); export const StyledButton = styled(Button)(({ theme }) => ({ fontWeight: 600, - color: theme.palette.text.primary -})) + color: theme.palette.text.primary, +})); export const CreateBlogButton = styled(Button)(({ theme }) => ({ display: 'flex', flexDirection: 'row', alignItems: 'center', padding: '8px 15px', - borderRadius: "40px", + borderRadius: '40px', gap: '4px', backgroundColor: theme.palette.secondary.main, color: '#fff', - fontFamily: "Arial", - transition: "all 0.3s ease-in-out", - boxShadow: "none", - "&:hover": { - cursor: "pointer", - boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;", + fontFamily: 'Arial', + transition: 'all 0.3s ease-in-out', + boxShadow: 'none', + '&:hover': { + cursor: 'pointer', + boxShadow: 'rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;', backgroundColor: theme.palette.secondary.main, - filter: "brightness(1.1)", - } -})) + filter: 'brightness(1.1)', + }, +})); export const AuthenticateButton = styled(Button)({ display: 'flex', flexDirection: 'row', alignItems: 'center', padding: '8px 15px', - borderRadius: "40px", + borderRadius: '40px', gap: '4px', - backgroundColor: "#4ACE91", + backgroundColor: '#4ACE91', color: '#fff', - fontFamily: "Arial", - transition: "all 0.3s ease-in-out", - boxShadow: "none", - "&:hover": { - cursor: "pointer", - boxShadow: "rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;", - backgroundColor: "#4ACE91", - filter: "brightness(1.1)", - } -}) + fontFamily: 'Arial', + transition: 'all 0.3s ease-in-out', + boxShadow: 'none', + '&:hover': { + cursor: 'pointer', + boxShadow: 'rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;', + backgroundColor: '#4ACE91', + filter: 'brightness(1.1)', + }, +}); export const AvatarContainer = styled(Box)({ display: 'flex', alignItems: 'center', - "&:hover": { - cursor: "pointer", - "& #expand-icon": { - transition: "all 0.3s ease-in-out", - filter: "brightness(0.7)", - } + '&:hover': { + cursor: 'pointer', + '& #expand-icon': { + transition: 'all 0.3s ease-in-out', + filter: 'brightness(0.7)', + }, }, }); export const DropdownContainer = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - gap: "5px", + display: 'flex', + alignItems: 'center', + gap: '5px', backgroundColor: theme.palette.primary.main, - padding: "10px 15px", - transition: "all 0.4s ease-in-out", - "&:hover": { - cursor: "pointer", - filter: "brightness(0.95)" - } + padding: '10px 15px', + transition: 'all 0.4s ease-in-out', + '&:hover': { + cursor: 'pointer', + filter: 'brightness(0.95)', + }, })); export const DropdownText = styled(Typography)(({ theme }) => ({ - fontFamily: "Arial", - fontSize: "16px", + fontFamily: 'Arial', + fontSize: '16px', color: theme.palette.text.primary, - userSelect: "none" + userSelect: 'none', })); export const NavbarName = styled(Typography)(({ theme }) => ({ - fontFamily: "Arial", - fontSize: "18px", + fontFamily: 'Arial', + fontSize: '18px', color: theme.palette.text.primary, - margin: "0 10px", -})); \ No newline at end of file + margin: '0 10px', +})); diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 15e7e1c..4cc28c7 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from 'react' +import React, { useMemo, useRef, useState } from 'react'; import { Typography, Box, @@ -10,32 +10,32 @@ import { ListItem, ListItemText, ListItemIcon, - Divider -} from '@mui/material' -import AccountCircle from '@mui/icons-material/AccountCircle' -import AddBoxIcon from '@mui/icons-material/AddBox' -import Badge from '@mui/material/Badge' -import NotificationsIcon from '@mui/icons-material/Notifications' -import ExitToAppIcon from '@mui/icons-material/ExitToApp' -import { useNavigate } from 'react-router-dom' -import { togglePublishBlogModal } from '../../../state/features/globalSlice' -import { useDispatch, useSelector } from 'react-redux' -import AutoStoriesIcon from '@mui/icons-material/AutoStories' -import { RootState } from '../../../state/store' -import { UserNavbar } from '../../common/UserNavbar/UserNavbar' -import { removePrefix } from '../../../utils/blogIdformats' -import { useLocation } from 'react-router-dom' -import BookmarkIcon from '@mui/icons-material/Bookmark' -import SubscriptionsIcon from '@mui/icons-material/Subscriptions' -import { BlockedNamesModal } from '../../common/BlockedNamesModal/BlockedNamesModal' -import SearchIcon from '@mui/icons-material/Search' -import EmailIcon from '@mui/icons-material/Email' -import localforage from 'localforage' + Divider, +} from '@mui/material'; +import AccountCircle from '@mui/icons-material/AccountCircle'; +import AddBoxIcon from '@mui/icons-material/AddBox'; +import Badge from '@mui/material/Badge'; +import NotificationsIcon from '@mui/icons-material/Notifications'; +import ExitToAppIcon from '@mui/icons-material/ExitToApp'; +import { useNavigate } from 'react-router-dom'; +import { togglePublishBlogModal } from '../../../state/features/globalSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import AutoStoriesIcon from '@mui/icons-material/AutoStories'; +import { RootState } from '../../../state/store'; +import { UserNavbar } from '../../common/UserNavbar/UserNavbar'; +import { removePrefix } from '../../../utils/blogIdformats'; +import { useLocation } from 'react-router-dom'; +import BookmarkIcon from '@mui/icons-material/Bookmark'; +import SubscriptionsIcon from '@mui/icons-material/Subscriptions'; +import { BlockedNamesModal } from '../../common/BlockedNamesModal/BlockedNamesModal'; +import SearchIcon from '@mui/icons-material/Search'; +import EmailIcon from '@mui/icons-material/Email'; +import localforage from 'localforage'; const notification = localforage.createInstance({ - name: 'notification' -}) + name: 'notification', +}); -import BackspaceIcon from '@mui/icons-material/Backspace' +import BackspaceIcon from '@mui/icons-material/Backspace'; import { AvatarContainer, CreateBlogButton, @@ -46,36 +46,36 @@ import { QblogLogoContainer, StyledButton, AuthenticateButton, - NavbarName -} from './Navbar-styles' -import { AccountCircleSVG } from '../../../assets/svgs/AccountCircleSVG' -import QblogLogo from '../../../assets/img/qBlogLogo.png' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import PersonOffIcon from '@mui/icons-material/PersonOff' -import { NewWindowSVG } from '../../../assets/svgs/NewWindowSVG' + NavbarName, +} from './Navbar-styles'; +import { AccountCircleSVG } from '../../../assets/svgs/AccountCircleSVG'; +import QblogLogo from '../../../assets/img/qBlogLogo.png'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import PersonOffIcon from '@mui/icons-material/PersonOff'; +import { NewWindowSVG } from '../../../assets/svgs/NewWindowSVG'; import { addFilteredPosts, setFilterValue, - setIsFiltering -} from '../../../state/features/blogSlice' -import { Item } from '../../common/Comments/CommentEditor' -import { formatDate } from '../../../utils/time' -import CheckIcon from '@mui/icons-material/Check' -import type { NameRecord } from '../../../utils/qortalRequestFunctions' + setIsFiltering, +} from '../../../state/features/blogSlice'; +import { Item } from '../../common/Comments/CommentEditor'; +import { formatDate } from '../../../utils/time'; +import CheckIcon from '@mui/icons-material/Check'; +import type { NameRecord } from '../../../utils/qortalRequestFunctions'; interface Props { - isAuthenticated: boolean - hasBlog: boolean - userName: string | null - userAvatar: string - blog: any - authenticate: () => void - hasAttemptedToFetchBlogInitial: boolean - allNames: NameRecord[] - onSwitchActiveName: (name: string) => void + isAuthenticated: boolean; + hasBlog: boolean; + userName: string | null; + userAvatar: string; + blog: any; + authenticate: () => void; + hasAttemptedToFetchBlogInitial: boolean; + allNames: NameRecord[]; + onSwitchActiveName: (name: string) => void; } function useQuery() { - return new URLSearchParams(useLocation().search) + return new URLSearchParams(useLocation().search); } const NavBar: React.FC = ({ @@ -87,73 +87,68 @@ const NavBar: React.FC = ({ authenticate, hasAttemptedToFetchBlogInitial, allNames, - onSwitchActiveName + onSwitchActiveName, }) => { - const navigate = useNavigate() - const dispatch = useDispatch() - const theme = useTheme() - const query = useQuery() - const { visitingBlog } = useSelector((state: RootState) => state.global) - const notifications = useSelector( - (state: RootState) => state.global.notifications - ) + const navigate = useNavigate(); + const dispatch = useDispatch(); + const theme = useTheme(); + const query = useQuery(); + const { visitingBlog } = useSelector((state: RootState) => state.global); + const notifications = useSelector((state: RootState) => state.global.notifications); const notificationCreatorComment = useSelector( - (state: RootState) => state.global.notificationCreatorComment - ) + (state: RootState) => state.global.notificationCreatorComment, + ); const fullNotifications = useMemo(() => { - return [...notificationCreatorComment, ...notifications].sort( - (a, b) => b.created - a.created - ) - }, [notificationCreatorComment, notifications]) - const location = useLocation() - const [anchorEl, setAnchorEl] = React.useState(null) - const [anchorElNotification, setAnchorElNotification] = - React.useState(null) - const [isOpenModal, setIsOpenModal] = React.useState(false) - const [searchVal, setSearchVal] = useState('') - const searchValRef = useRef('') - const inputRef = useRef(null) - const stripBlogId = removePrefix(visitingBlog?.blogId || '') + return [...notificationCreatorComment, ...notifications].sort((a, b) => b.created - a.created); + }, [notificationCreatorComment, notifications]); + const location = useLocation(); + const [anchorEl, setAnchorEl] = React.useState(null); + const [anchorElNotification, setAnchorElNotification] = React.useState( + null, + ); + const [isOpenModal, setIsOpenModal] = React.useState(false); + const [searchVal, setSearchVal] = useState(''); + const searchValRef = useRef(''); + const inputRef = useRef(null); + const stripBlogId = removePrefix(visitingBlog?.blogId || ''); const showUserNav = - visitingBlog?.navbarConfig && - stripBlogId && - location.pathname.includes(stripBlogId) + visitingBlog?.navbarConfig && stripBlogId && location.pathname.includes(stripBlogId); const handleClick = (event: React.MouseEvent) => { - const target = event.currentTarget as unknown as HTMLButtonElement | null - setAnchorEl(target) - } + const target = event.currentTarget as unknown as HTMLButtonElement | null; + setAnchorEl(target); + }; const openNotificationPopover = (event: any) => { - const target = event.currentTarget as unknown as HTMLButtonElement | null - setAnchorElNotification(target) - } + const target = event.currentTarget as unknown as HTMLButtonElement | null; + setAnchorElNotification(target); + }; const closeNotificationPopover = () => { - setAnchorElNotification(null) - } + setAnchorElNotification(null); + }; const handleClose = () => { - setAnchorEl(null) - } + setAnchorEl(null); + }; const onClose = () => { - setIsOpenModal(false) - } - const open = Boolean(anchorEl) - const id = open ? 'simple-popover' : undefined - const openPopover = Boolean(anchorElNotification) - const idNotification = openPopover ? 'simple-popover-notification' : undefined + setIsOpenModal(false); + }; + const open = Boolean(anchorEl); + const id = open ? 'simple-popover' : undefined; + const openPopover = Boolean(anchorElNotification); + const idNotification = openPopover ? 'simple-popover-notification' : undefined; const [viewMode, setViewMode] = useState<'tile' | 'list'>(() => { - const saved = localStorage.getItem('qblog_view') - return saved === 'list' ? 'list' : 'tile' - }) + const saved = localStorage.getItem('qblog_view'); + return saved === 'list' ? 'list' : 'tile'; + }); const toggleViewMode = () => { - const next = viewMode === 'tile' ? 'list' : 'tile' - setViewMode(next) - localStorage.setItem('qblog_view', next) - window.dispatchEvent(new CustomEvent('qblog:viewmode', { detail: next })) - } + const next = viewMode === 'tile' ? 'list' : 'tile'; + setViewMode(next); + localStorage.setItem('qblog_view', next); + window.dispatchEvent(new CustomEvent('qblog:viewmode', { detail: next })); + }; return showUserNav ? ( = ({ src={QblogLogo} alt="Qblog Logo" onClick={() => { - navigate(`/`) - dispatch(setIsFiltering(false)) - dispatch(setFilterValue('')) - dispatch(addFilteredPosts([])) - searchValRef.current = '' - if (!inputRef.current) return - inputRef.current.value = '' + navigate(`/`); + dispatch(setIsFiltering(false)); + dispatch(setFilterValue('')); + dispatch(addFilteredPosts([])); + searchValRef.current = ''; + if (!inputRef.current) return; + inputRef.current.value = ''; }} /> { - searchValRef.current = e.target.value + searchValRef.current = e.target.value; }} onKeyDown={(event) => { if (event.key === 'Enter' || event.keyCode === 13) { if (!searchValRef.current) { - dispatch(setIsFiltering(false)) - dispatch(setFilterValue('')) - dispatch(addFilteredPosts([])) - searchValRef.current = '' - if (!inputRef.current) return - inputRef.current.value = '' - return + dispatch(setIsFiltering(false)); + dispatch(setFilterValue('')); + dispatch(addFilteredPosts([])); + searchValRef.current = ''; + if (!inputRef.current) return; + inputRef.current.value = ''; + return; } - navigate('/') - dispatch(setIsFiltering(true)) - dispatch(addFilteredPosts([])) - dispatch(setFilterValue(searchValRef.current)) + navigate('/'); + dispatch(setIsFiltering(true)); + dispatch(addFilteredPosts([])); + dispatch(setFilterValue(searchValRef.current)); } }} placeholder="Filter by name" sx={{ '&&:before': { - borderBottom: 'none' + borderBottom: 'none', }, '&&:after': { - borderBottom: 'none' + borderBottom: 'none', }, '&&:hover:before': { - borderBottom: 'none' + borderBottom: 'none', }, '&&.Mui-focused:before': { - borderBottom: 'none' + borderBottom: 'none', }, '&&.Mui-focused': { - outline: 'none' + outline: 'none', }, - fontSize: '18px' + fontSize: '18px', }} /> { if (!searchValRef.current) { - dispatch(setIsFiltering(false)) - dispatch(setFilterValue('')) - dispatch(addFilteredPosts([])) - searchValRef.current = '' - if (!inputRef.current) return - inputRef.current.value = '' - return + dispatch(setIsFiltering(false)); + dispatch(setFilterValue('')); + dispatch(addFilteredPosts([])); + searchValRef.current = ''; + if (!inputRef.current) return; + inputRef.current.value = ''; + return; } - navigate('/') - dispatch(setIsFiltering(true)) - dispatch(addFilteredPosts([])) - dispatch(setFilterValue(searchValRef.current)) + navigate('/'); + dispatch(setIsFiltering(true)); + dispatch(addFilteredPosts([])); + dispatch(setFilterValue(searchValRef.current)); }} /> { - dispatch(setIsFiltering(false)) - dispatch(setFilterValue('')) - dispatch(addFilteredPosts([])) - searchValRef.current = '' - if (!inputRef.current) return - inputRef.current.value = '' + dispatch(setIsFiltering(false)); + dispatch(setFilterValue('')); + dispatch(addFilteredPosts([])); + searchValRef.current = ''; + if (!inputRef.current) return; + inputRef.current.value = ''; }} /> */} - {isAuthenticated && - userName && - hasAttemptedToFetchBlogInitial && - !hasBlog && ( - { - dispatch(togglePublishBlogModal(true)) - }} - > - - Create Blog - - )} + {isAuthenticated && userName && hasAttemptedToFetchBlogInitial && !hasBlog && ( + { + dispatch(togglePublishBlogModal(true)); + }} + > + + Create Blog + + )} {isAuthenticated && userName && hasBlog && ( <> } onClick={() => { - navigate(`/post/new`) + navigate(`/post/new`); }} > Create Post @@ -421,7 +401,7 @@ const NavBar: React.FC = ({ color="primary" startIcon={} onClick={() => { - navigate(`/${userName}/${blog.blogId}`) + navigate(`/${userName}/${blog.blogId}`); }} > My Blog @@ -433,11 +413,7 @@ const NavBar: React.FC = ({ {userName} {!userAvatar ? ( - + ) : ( = ({ width="32" height="32" style={{ - borderRadius: '50%' + borderRadius: '50%', }} /> )} @@ -459,29 +435,26 @@ const NavBar: React.FC = ({ onClose={handleClose} anchorOrigin={{ vertical: 'bottom', - horizontal: 'left' + horizontal: 'left', }} > {!!(allNames && allNames.length) && ( - + Switch name {allNames .filter((n) => !!n.name) .map((n) => { - const isActive = n.name === userName + const isActive = n.name === userName; return ( { - onSwitchActiveName(n.name) - handleClose() + onSwitchActiveName(n.name); + handleClose(); }} > {isActive && ( @@ -492,7 +465,7 @@ const NavBar: React.FC = ({ {!isActive && } - ) + ); })} @@ -501,7 +474,7 @@ const NavBar: React.FC = ({ navigate('/favorites')}> Favorites @@ -509,20 +482,20 @@ const NavBar: React.FC = ({ navigate('/subscriptions')}> Subscriptions { - setIsOpenModal(true) - handleClose() + setIsOpenModal(true); + handleClose(); }} > Blocked Names @@ -535,12 +508,12 @@ const NavBar: React.FC = ({ width: '100%', display: 'flex', gap: '5px', - alignItems: 'center' + alignItems: 'center', }} > @@ -548,13 +521,11 @@ const NavBar: React.FC = ({ - {isOpenModal && ( - - )} + {isOpenModal && } - ) -} + ); +}; -export default NavBar +export default NavBar; diff --git a/src/components/modals/ConsentModal.tsx b/src/components/modals/ConsentModal.tsx index 2a176f2..fedefdb 100644 --- a/src/components/modals/ConsentModal.tsx +++ b/src/components/modals/ConsentModal.tsx @@ -1,38 +1,38 @@ -import * as React from 'react' -import Button from '@mui/material/Button' -import Dialog from '@mui/material/Dialog' -import DialogActions from '@mui/material/DialogActions' -import DialogContent from '@mui/material/DialogContent' -import DialogContentText from '@mui/material/DialogContentText' -import DialogTitle from '@mui/material/DialogTitle' -import localForage from 'localforage' -import { useTheme } from '@mui/material' +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import localForage from 'localforage'; +import { useTheme } from '@mui/material'; const generalLocal = localForage.createInstance({ - name: 'q-blog-general' -}) + name: 'q-blog-general', +}); export default function ConsentModal() { - const theme = useTheme() + const theme = useTheme(); - const [open, setOpen] = React.useState(false) + const [open, setOpen] = React.useState(false); const handleClose = () => { - setOpen(false) - } + setOpen(false); + }; const getIsConsented = React.useCallback(async () => { try { - const hasConsented = await generalLocal.getItem('general-consent') - if (hasConsented) return + const hasConsented = await generalLocal.getItem('general-consent'); + if (hasConsented) return; - setOpen(true) - generalLocal.setItem('general-consent', true) + setOpen(true); + generalLocal.setItem('general-consent', true); } catch (error) {} - }, []) + }, []); React.useEffect(() => { - getIsConsented() - }, []) + getIsConsented(); + }, []); return (
Welcome - The Qortal community, along with its development team and the - creators of this application, cannot be held accountable for any - content published or displayed. Furthermore, they bear no - responsibility for any data loss that may occur as a result of using - this application. + The Qortal community, along with its development team and the creators of this + application, cannot be held accountable for any content published or displayed. + Furthermore, they bear no responsibility for any data loss that may occur as a result of + using this application. @@ -56,7 +55,7 @@ export default function ConsentModal() { sx={{ backgroundColor: theme.palette.primary.light, color: theme.palette.text.primary, - fontFamily: 'Arial' + fontFamily: 'Arial', }} onClick={handleClose} autoFocus @@ -66,5 +65,5 @@ export default function ConsentModal() {
- ) + ); } diff --git a/src/components/modals/EditBlogModal.tsx b/src/components/modals/EditBlogModal.tsx index 631ac25..9223e38 100644 --- a/src/components/modals/EditBlogModal.tsx +++ b/src/components/modals/EditBlogModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useState } from 'react'; import { Box, Button, @@ -12,135 +12,126 @@ import { SelectChangeEvent, OutlinedInput, Chip, - IconButton -} from '@mui/material' -import { useDispatch } from 'react-redux' -import { togglePublishBlogModal } from '../../state/features/globalSlice' -import AddIcon from '@mui/icons-material/Add' -import CloseIcon from '@mui/icons-material/Close' -import { styled } from '@mui/system' + IconButton, +} from '@mui/material'; +import { useDispatch } from 'react-redux'; +import { togglePublishBlogModal } from '../../state/features/globalSlice'; +import AddIcon from '@mui/icons-material/Add'; +import CloseIcon from '@mui/icons-material/Close'; +import { styled } from '@mui/system'; interface SelectOption { - id: string - name: string + id: string; + name: string; } interface MyModalProps { - open: boolean - onClose: () => void + open: boolean; + onClose: () => void; onPublish: ( title: string, description: string, category: string, - tags: string[] - ) => Promise - currentBlog: any + tags: string[], + ) => Promise; + currentBlog: any; } const ChipContainer = styled(Box)({ display: 'flex', flexWrap: 'wrap', '& > *': { - margin: '4px' - } -}) + margin: '4px', + }, +}); -const MyModal: React.FC = ({ - open, - onClose, - onPublish, - currentBlog -}) => { - const dispatch = useDispatch() +const MyModal: React.FC = ({ open, onClose, onPublish, currentBlog }) => { + const dispatch = useDispatch(); - const [title, setTitle] = useState('') - const [description, setDescription] = useState('') - const [errorMessage, setErrorMessage] = useState('') - const [selectedOption, setSelectedOption] = useState( - null - ) - const [inputValue, setInputValue] = useState('') - const [chips, setChips] = useState([]) + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [selectedOption, setSelectedOption] = useState(null); + const [inputValue, setInputValue] = useState(''); + const [chips, setChips] = useState([]); - const [options, setOptions] = useState([]) + const [options, setOptions] = useState([]); React.useEffect(() => { if (currentBlog) { - setTitle(currentBlog?.title || '') - setDescription(currentBlog?.description || '') - const findCategory = options.find( - (option) => option.id === currentBlog?.category - ) - if (!findCategory) return - setSelectedOption(findCategory) - if (!currentBlog?.tags || !Array.isArray(currentBlog.tags)) return - setChips(currentBlog.tags) + setTitle(currentBlog?.title || ''); + setDescription(currentBlog?.description || ''); + const findCategory = options.find((option) => option.id === currentBlog?.category); + if (!findCategory) return; + setSelectedOption(findCategory); + if (!currentBlog?.tags || !Array.isArray(currentBlog.tags)) return; + setChips(currentBlog.tags); } - }, [currentBlog, options]) + }, [currentBlog, options]); const handlePublish = async (): Promise => { try { - await onPublish(title, description, selectedOption?.id || '', chips) - handleClose() + await onPublish(title, description, selectedOption?.id || '', chips); + handleClose(); } catch (error: any) { - setErrorMessage(error.message) + setErrorMessage(error.message); } - } + }; const handleClose = (): void => { - setErrorMessage('') - dispatch(togglePublishBlogModal(false)) - onClose() - } + setErrorMessage(''); + dispatch(togglePublishBlogModal(false)); + onClose(); + }; const handleOptionChange = (event: SelectChangeEvent) => { - const optionId = event.target.value - const selectedOption = options.find((option) => option.id === optionId) - setSelectedOption(selectedOption || null) - } + const optionId = event.target.value; + const selectedOption = options.find((option) => option.id === optionId); + setSelectedOption(selectedOption || null); + }; const handleChipDelete = (index: number) => { - const newChips = [...chips] - newChips.splice(index, 1) - setChips(newChips) - } + const newChips = [...chips]; + newChips.splice(index, 1); + setChips(newChips); + }; const handleInputChange = (event: any) => { - setInputValue(event.target.value) - } + setInputValue(event.target.value); + }; const handleInputKeyDown = (event: any) => { if (event.key === 'Enter' && inputValue !== '') { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } else { - event.preventDefault() + event.preventDefault(); } } - } + }; const addChip = () => { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } - } + }; const getListCategories = React.useCallback(async () => { try { - const url = `/arbitrary/categories` + const url = `/arbitrary/categories`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - setOptions(responseData) + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + setOptions(responseData); } catch (error) {} - }, []) + }, []); React.useEffect(() => { - getListCategories() - }, [getListCategories]) + getListCategories(); + }, [getListCategories]); return ( = ({ p: 4, display: 'flex', flexDirection: 'column', - gap: 2 + gap: 2, }} > @@ -241,7 +232,7 @@ const MyModal: React.FC = ({
- ) -} + ); +}; -export default MyModal +export default MyModal; diff --git a/src/components/modals/PublishBlogModal.tsx b/src/components/modals/PublishBlogModal.tsx index 5ce6045..3306c03 100644 --- a/src/components/modals/PublishBlogModal.tsx +++ b/src/components/modals/PublishBlogModal.tsx @@ -1,4 +1,4 @@ -import React, { ChangeEvent, useState } from 'react' +import React, { ChangeEvent, useState } from 'react'; import { Box, Button, @@ -12,130 +12,117 @@ import { SelectChangeEvent, OutlinedInput, Chip, - IconButton -} from '@mui/material' -import { useDispatch } from 'react-redux' -import { togglePublishBlogModal } from '../../state/features/globalSlice' -import AddIcon from '@mui/icons-material/Add' -import CloseIcon from '@mui/icons-material/Close' -import { styled } from '@mui/system' + IconButton, +} from '@mui/material'; +import { useDispatch } from 'react-redux'; +import { togglePublishBlogModal } from '../../state/features/globalSlice'; +import AddIcon from '@mui/icons-material/Add'; +import CloseIcon from '@mui/icons-material/Close'; +import { styled } from '@mui/system'; interface SelectOption { - id: string - name: string + id: string; + name: string; } interface MyModalProps { - open: boolean - onClose: () => void + open: boolean; + onClose: () => void; onPublish: ( title: string, description: string, category: string, tags: string[], - blogIdentifier: string - ) => Promise - username: string + blogIdentifier: string, + ) => Promise; + username: string; } const ChipContainer = styled(Box)({ display: 'flex', flexWrap: 'wrap', '& > *': { - margin: '4px' - } -}) + margin: '4px', + }, +}); -const MyModal: React.FC = ({ - open, - onClose, - onPublish, - username -}) => { - const dispatch = useDispatch() +const MyModal: React.FC = ({ open, onClose, onPublish, username }) => { + const dispatch = useDispatch(); - const [title, setTitle] = useState('') - const [description, setDescription] = useState('') - const [errorMessage, setErrorMessage] = useState('') - const [selectedOption, setSelectedOption] = useState( - null - ) - const [inputValue, setInputValue] = useState('') - const [chips, setChips] = useState([]) - const [blogIdentifier, setBlogIdentifier] = useState(username || '') - const [options, setOptions] = useState([]) + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + const [selectedOption, setSelectedOption] = useState(null); + const [inputValue, setInputValue] = useState(''); + const [chips, setChips] = useState([]); + const [blogIdentifier, setBlogIdentifier] = useState(username || ''); + const [options, setOptions] = useState([]); const handlePublish = async (): Promise => { try { - await onPublish( - title, - description, - selectedOption?.id || '', - chips, - blogIdentifier - ) - handleClose() + await onPublish(title, description, selectedOption?.id || '', chips, blogIdentifier); + handleClose(); } catch (error: any) { - setErrorMessage(error.message) + setErrorMessage(error.message); } - } + }; const handleClose = (): void => { - setTitle('') - setDescription('') - setErrorMessage('') - dispatch(togglePublishBlogModal(false)) - onClose() - } + setTitle(''); + setDescription(''); + setErrorMessage(''); + dispatch(togglePublishBlogModal(false)); + onClose(); + }; const handleOptionChange = (event: SelectChangeEvent) => { - const optionId = event.target.value - const selectedOption = options.find((option) => option.id === optionId) - setSelectedOption(selectedOption || null) - } + const optionId = event.target.value; + const selectedOption = options.find((option) => option.id === optionId); + setSelectedOption(selectedOption || null); + }; const handleChipDelete = (index: number) => { - const newChips = [...chips] - newChips.splice(index, 1) - setChips(newChips) - } + const newChips = [...chips]; + newChips.splice(index, 1); + setChips(newChips); + }; const handleInputChange = (event: any) => { - setInputValue(event.target.value) - } + setInputValue(event.target.value); + }; const handleInputKeyDown = (event: any) => { if (event.key === 'Enter' && inputValue !== '') { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } else { - event.preventDefault() + event.preventDefault(); } } - } + }; const addChip = () => { if (chips.length < 5) { - setChips([...chips, inputValue]) - setInputValue('') + setChips([...chips, inputValue]); + setInputValue(''); } - } + }; const getListCategories = React.useCallback(async () => { try { - const url = `/arbitrary/categories` + const url = `/arbitrary/categories`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - setOptions(responseData) + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + setOptions(responseData); } catch (error) {} - }, []) + }, []); React.useEffect(() => { - getListCategories() - }, [getListCategories]) + getListCategories(); + }, [getListCategories]); const handleInputChangeId = (event: ChangeEvent) => { // Replace any non-alphanumeric and non-space characters with an empty string @@ -144,18 +131,18 @@ const MyModal: React.FC = ({ .replace(/[^a-zA-Z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') - .trim() + .trim(); if (newValue.toLowerCase().includes('post')) { // Replace the 'post' string with an empty string - newValue = newValue.replace(/post/gi, '') + newValue = newValue.replace(/post/gi, ''); } if (newValue.toLowerCase().includes('q-blog')) { // Replace the 'q-blog' string with an empty string - newValue = newValue.replace(/q-blog/gi, '') + newValue = newValue.replace(/q-blog/gi, ''); } - setBlogIdentifier(newValue) - } + setBlogIdentifier(newValue); + }; return ( = ({ flexDirection: 'column', gap: 2, overflowY: 'auto', - maxHeight: '95vh' + maxHeight: '95vh', }} > @@ -275,7 +262,7 @@ const MyModal: React.FC = ({
- ) -} + ); +}; -export default MyModal +export default MyModal; diff --git a/src/components/modals/ReusableModal.tsx b/src/components/modals/ReusableModal.tsx index 210aa0d..af0266e 100644 --- a/src/components/modals/ReusableModal.tsx +++ b/src/components/modals/ReusableModal.tsx @@ -1,12 +1,12 @@ -import React from 'react' -import { Box, Modal, useTheme } from '@mui/material' +import React from 'react'; +import { Box, Modal, useTheme } from '@mui/material'; interface MyModalProps { - open: boolean - onClose?: () => void - onSubmit?: (obj: any) => Promise - children: any - customStyles?: any + open: boolean; + onClose?: () => void; + onSubmit?: (obj: any) => Promise; + children: any; + customStyles?: any; } export const ReusableModal: React.FC = ({ @@ -14,9 +14,9 @@ export const ReusableModal: React.FC = ({ onClose, onSubmit, children, - customStyles = {} + customStyles = {}, }) => { - const theme = useTheme() + const theme = useTheme(); return ( = ({ display: 'flex', flexDirection: 'column', gap: 2, - ...customStyles + ...customStyles, }} > {children} - ) -} + ); +}; diff --git a/src/constants/mail.ts b/src/constants/mail.ts index bbb6e7b..eea28c7 100644 --- a/src/constants/mail.ts +++ b/src/constants/mail.ts @@ -1,3 +1,2 @@ -export const MAIL_SERVICE_TYPE: 'MAIL_PRIVATE' = 'MAIL_PRIVATE' -export const MAIL_ATTACHMENT_SERVICE_TYPE: 'ATTACHMENT_PRIVATE' = - 'ATTACHMENT_PRIVATE' +export const MAIL_SERVICE_TYPE: 'MAIL_PRIVATE' = 'MAIL_PRIVATE'; +export const MAIL_ATTACHMENT_SERVICE_TYPE: 'ATTACHMENT_PRIVATE' = 'ATTACHMENT_PRIVATE'; diff --git a/src/global.d.ts b/src/global.d.ts index 75357f6..d36cdac 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,66 +1,64 @@ // src/global.d.ts interface QortalRequestOptions { - action: string - name?: string - service?: string - data64?: string - title?: string - description?: string - category?: string - tags?: string[] - identifier?: string - address?: string - metaData?: string - encoding?: string - includeMetadata?: boolean - limit?: numebr - offset?: number - reverse?: boolean - resources?: any[] - filename?: string - list_name?: string - item?: string - items?: strings[] - tag1?: string - tag2?: string - tag3?: string - tag4?: string - tag5?: string - coin?: string - destinationAddress?: string - amount?: number - blob?: Blob - mimeType?: string - file?: File - count?: number - query?: string - exactMatchNames?: boolean - excludeBlocked?: boolean - mode?: string - pollName?: string - pollDescription?: string - pollOptions?: string[] - pollOwnerAddress?: string - optionIndex?: number + action: string; + name?: string; + service?: string; + data64?: string; + title?: string; + description?: string; + category?: string; + tags?: string[]; + identifier?: string; + address?: string; + metaData?: string; + encoding?: string; + includeMetadata?: boolean; + limit?: number; + offset?: number; + reverse?: boolean; + resources?: any[]; + filename?: string; + list_name?: string; + item?: string; + items?: string[]; + tag1?: string; + tag2?: string; + tag3?: string; + tag4?: string; + tag5?: string; + coin?: string; + destinationAddress?: string; + amount?: number; + blob?: Blob; + mimeType?: string; + file?: File; + count?: number; + query?: string; + exactMatchNames?: boolean; + excludeBlocked?: boolean; + mode?: string; + pollName?: string; + pollDescription?: string; + pollOptions?: string[]; + pollOwnerAddress?: string; + optionIndex?: number; } -declare function qortalRequest(options: QortalRequestOptions): Promise +declare function qortalRequest(options: QortalRequestOptions): Promise; declare function qortalRequestWithTimeout( options: QortalRequestOptions, - time: number -): Promise + time: number, +): Promise; declare global { interface Window { - _qdnBase: any // Replace 'any' with the appropriate type if you know it - _qdnTheme: string + _qdnBase: any; // Replace 'any' with the appropriate type if you know it + _qdnTheme: string; } } declare global { interface Window { - showSaveFilePicker: ( - options?: SaveFilePickerOptions - ) => Promise + showSaveFilePicker: (options?: SaveFilePickerOptions) => Promise; } } diff --git a/src/hooks/useFetchPosts.tsx b/src/hooks/useFetchPosts.tsx index 73b5a2e..0a9578e 100644 --- a/src/hooks/useFetchPosts.tsx +++ b/src/hooks/useFetchPosts.tsx @@ -1,5 +1,5 @@ -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { addPosts, addToHashMap, @@ -9,113 +9,96 @@ import { upsertFilteredPosts, upsertPosts, upsertPostsBeginning, - upsertSubscriptionPosts -} from '../state/features/blogSlice' -import { - setCurrentBlog, - setIsLoadingGlobal -} from '../state/features/globalSlice' -import { RootState } from '../state/store' -import { fetchAndEvaluatePosts } from '../utils/fetchPosts' + upsertSubscriptionPosts, +} from '../state/features/blogSlice'; +import { setCurrentBlog, setIsLoadingGlobal } from '../state/features/globalSlice'; +import { RootState } from '../state/store'; +import { fetchAndEvaluatePosts } from '../utils/fetchPosts'; export const useFetchPosts = () => { - const dispatch = useDispatch() - const hashMapPosts = useSelector( - (state: RootState) => state.blog.hashMapPosts - ) - const posts = useSelector((state: RootState) => state.blog.posts) - const filteredPosts = useSelector( - (state: RootState) => state.blog.filteredPosts - ) - const favoritesLocal = useSelector( - (state: RootState) => state.blog.favoritesLocal - ) - const favorites = useSelector((state: RootState) => state.blog.favorites) - const subscriptionPosts = useSelector( - (state: RootState) => state.blog.subscriptionPosts - ) - const subscriptions = useSelector( - (state: RootState) => state.blog.subscriptions - ) + const dispatch = useDispatch(); + const hashMapPosts = useSelector((state: RootState) => state.blog.hashMapPosts); + const posts = useSelector((state: RootState) => state.blog.posts); + const filteredPosts = useSelector((state: RootState) => state.blog.filteredPosts); + const favoritesLocal = useSelector((state: RootState) => state.blog.favoritesLocal); + const favorites = useSelector((state: RootState) => state.blog.favorites); + const subscriptionPosts = useSelector((state: RootState) => state.blog.subscriptionPosts); + const subscriptions = useSelector((state: RootState) => state.blog.subscriptions); const checkAndUpdatePost = React.useCallback( (post: BlogPost) => { // Check if the post exists in hashMapPosts - const existingPost = hashMapPosts[post.id + "-" + post.user] + const existingPost = hashMapPosts[post.id + '-' + post.user]; if (!existingPost) { // If the post doesn't exist, add it to hashMapPosts - return true + return true; } else if ( post?.updated && existingPost?.updated && (!existingPost?.updated || post?.updated) > existingPost?.updated ) { // If the post exists and its updated is more recent than the existing post's updated, update it in hashMapPosts - return true + return true; } else { - return false + return false; } }, - [hashMapPosts] - ) + [hashMapPosts], + ); const getBlogPost = async (user: string, postId: string, content: any) => { const res = await fetchAndEvaluatePosts({ user, postId, - content - }) + content, + }); - dispatch(addToHashMap(res)) - } + dispatch(addToHashMap(res)); + }; const checkNewMessages = React.useCallback(async () => { try { - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&reverse=true&excludeblocked=true` + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - const latestPost = posts[0] - if (!latestPost) return - const findPost = responseData?.findIndex( - (item: any) => item?.identifier === latestPost?.id - ) + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + const latestPost = posts[0]; + if (!latestPost) return; + const findPost = responseData?.findIndex((item: any) => item?.identifier === latestPost?.id); if (findPost === -1) { - dispatch(setCountNewPosts(responseData.length)) - return + dispatch(setCountNewPosts(responseData.length)); + return; } - const newArray = responseData.slice(0, findPost) - dispatch(setCountNewPosts(newArray.length)) - return + const newArray = responseData.slice(0, findPost); + dispatch(setCountNewPosts(newArray.length)); + return; } catch (error) {} - }, [posts]) + }, [posts]); const getNewPosts = React.useCallback(async () => { try { - dispatch(setIsLoadingGlobal(true)) - dispatch(setCountNewPosts(0)) - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&reverse=true&excludeblocked=true` + dispatch(setIsLoadingGlobal(true)); + dispatch(setCountNewPosts(0)); + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() - const latestPost = posts[0] - if (!latestPost) return - const findPost = responseData?.findIndex( - (item: any) => item?.identifier === latestPost?.id - ) - let fetchAll = responseData - let willFetchAll = true + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); + const latestPost = posts[0]; + if (!latestPost) return; + const findPost = responseData?.findIndex((item: any) => item?.identifier === latestPost?.id); + let fetchAll = responseData; + let willFetchAll = true; if (findPost !== -1) { - willFetchAll = false - fetchAll = responseData.slice(0, findPost) + willFetchAll = false; + fetchAll = responseData.slice(0, findPost); } const structureData = fetchAll.map((post: any): BlogPost => { @@ -129,43 +112,43 @@ export const useFetchPosts = () => { updated: post?.updated, user: post.name, postImage: '', - id: post.identifier - } - }) + id: post.identifier, + }; + }); if (!willFetchAll) { - dispatch(upsertPostsBeginning(structureData)) + dispatch(upsertPostsBeginning(structureData)); } if (willFetchAll) { - dispatch(addPosts(structureData)) + dispatch(addPosts(structureData)); } for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdatePost(content) + const res = checkAndUpdatePost(content); if (res) { - getBlogPost(content.user, content.id, content) + getBlogPost(content.user, content.id, content); } } } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } - }, [posts, hashMapPosts]) + }, [posts, hashMapPosts]); const getBlogPosts = React.useCallback(async () => { try { - const offset = posts.length + const offset = posts.length; - dispatch(setIsLoadingGlobal(true)) - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true` + dispatch(setIsLoadingGlobal(true)); + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); const structureData = responseData.map((post: any): BlogPost => { return { title: post?.metadata?.title, @@ -177,38 +160,38 @@ export const useFetchPosts = () => { updated: post?.updated, user: post.name, postImage: '', - id: post.identifier - } - }) - dispatch(upsertPosts(structureData)) + id: post.identifier, + }; + }); + dispatch(upsertPosts(structureData)); for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdatePost(content) + const res = checkAndUpdatePost(content); if (res) { - getBlogPost(content.user, content.id, content) + getBlogPost(content.user, content.id, content); } } } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } - }, [posts, hashMapPosts]) + }, [posts, hashMapPosts]); const getBlogFilteredPosts = React.useCallback( async (filterValue: string) => { try { - const offset = filteredPosts.length + const offset = filteredPosts.length; - dispatch(setIsLoadingGlobal(true)) - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true&name=${filterValue}` + dispatch(setIsLoadingGlobal(true)); + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&excludeblocked=true&name=${filterValue}`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); const structureData = responseData.map((post: any): BlogPost => { return { title: post?.metadata?.title, @@ -220,40 +203,40 @@ export const useFetchPosts = () => { updated: post?.updated, user: post.name, postImage: '', - id: post.identifier - } - }) - dispatch(upsertFilteredPosts(structureData)) + id: post.identifier, + }; + }); + dispatch(upsertFilteredPosts(structureData)); for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdatePost(content) + const res = checkAndUpdatePost(content); if (res) { - getBlogPost(content.user, content.id, content) + getBlogPost(content.user, content.id, content); } } } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } }, - [filteredPosts, hashMapPosts] - ) + [filteredPosts, hashMapPosts], + ); const getBlogPostsSubscriptions = React.useCallback( async (username: string) => { try { - const offset = subscriptionPosts.length - dispatch(setIsLoadingGlobal(true)) - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&namefilter=q-blog-subscriptions-${username}&excludeblocked=true` + const offset = subscriptionPosts.length; + dispatch(setIsLoadingGlobal(true)); + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&query=q-blog-&limit=20&includemetadata=true&offset=${offset}&reverse=true&namefilter=q-blog-subscriptions-${username}&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); const structureData = responseData.map((post: any): BlogPost => { return { title: post?.metadata?.title, @@ -264,32 +247,32 @@ export const useFetchPosts = () => { createdAt: '', user: post.name, postImage: '', - id: post.identifier - } - }) - dispatch(upsertSubscriptionPosts(structureData)) + id: post.identifier, + }; + }); + dispatch(upsertSubscriptionPosts(structureData)); for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdatePost(content) + const res = checkAndUpdatePost(content); if (res) { - getBlogPost(content.user, content.id, content) + getBlogPost(content.user, content.id, content); } } } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } }, - [subscriptionPosts, hashMapPosts, subscriptions] - ) + [subscriptionPosts, hashMapPosts, subscriptions], + ); const getBlogPostsFavorites = React.useCallback(async () => { try { - const offset = favorites.length - const favSlice = (favoritesLocal || []).slice(offset, 20) - let favs = [] + const offset = favorites.length; + const favSlice = (favoritesLocal || []).slice(offset, 20); + let favs = []; for (const item of favSlice) { try { // await qortalRequest({ @@ -307,17 +290,17 @@ export const useFetchPosts = () => { // reverse: true // }); //TODO - NAME SHOULD BE EXACT - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${item.id}&exactmatchnames=true&name=${item.user}&limit=20&includemetadata=true&reverse=true&excludeblocked=true` + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${item.id}&exactmatchnames=true&name=${item.user}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const data = await response.json() + 'Content-Type': 'application/json', + }, + }); + const data = await response.json(); // if (data.length > 0) { - favs.push(data[0]) + favs.push(data[0]); } } catch (error) {} } @@ -331,23 +314,23 @@ export const useFetchPosts = () => { createdAt: '', user: post.name, postImage: '', - id: post.identifier - } - }) - dispatch(populateFavorites(structureData)) + id: post.identifier, + }; + }); + dispatch(populateFavorites(structureData)); for (const content of structureData) { if (content.user && content.id) { - const res = checkAndUpdatePost(content) + const res = checkAndUpdatePost(content); if (res) { - getBlogPost(content.user, content.id, content) + getBlogPost(content.user, content.id, content); } } } } catch (error) { } finally { } - }, [hashMapPosts, favoritesLocal]) + }, [hashMapPosts, favoritesLocal]); return { getBlogPosts, getBlogPostsFavorites, @@ -357,6 +340,6 @@ export const useFetchPosts = () => { hashMapPosts, checkNewMessages, getNewPosts, - getBlogFilteredPosts - } -} + getBlogFilteredPosts, + }; +}; diff --git a/src/hooks/useIframe.tsx b/src/hooks/useIframe.tsx index 09d68c5..e2ea1a7 100644 --- a/src/hooks/useIframe.tsx +++ b/src/hooks/useIframe.tsx @@ -1,26 +1,23 @@ -import { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; export const useIframe = () => { const navigate = useNavigate(); useEffect(() => { function handleNavigation(event: MessageEvent) { - if (event.data?.action === "NAVIGATE_TO_PATH" && event.data.path) { - console.log("Navigating to path within React app:", event.data.path); + if (event.data?.action === 'NAVIGATE_TO_PATH' && event.data.path) { + console.log('Navigating to path within React app:', event.data.path); navigate(event.data.path); // Navigate directly to the specified path // Send a response back to the parent window after navigation is handled - window.parent.postMessage( - { action: "NAVIGATION_SUCCESS", path: event.data.path }, - "*" - ); + window.parent.postMessage({ action: 'NAVIGATION_SUCCESS', path: event.data.path }, '*'); } } - window.addEventListener("message", handleNavigation); + window.addEventListener('message', handleNavigation); return () => { - window.removeEventListener("message", handleNavigation); + window.removeEventListener('message', handleNavigation); }; }, [navigate]); return { navigate }; diff --git a/src/index.css b/src/index.css index fe65fcf..9ea3de6 100644 --- a/src/index.css +++ b/src/index.css @@ -157,8 +157,7 @@ a:focus { } .glow { - box-shadow: 0 0 10px #9ecaed, 0 0 20px #9ecaed, 0 0 30px #9ecaed, - 0 0 40px #9ecaed; + box-shadow: 0 0 10px #9ecaed, 0 0 20px #9ecaed, 0 0 30px #9ecaed, 0 0 40px #9ecaed; } .list-view-container { diff --git a/src/index.d.ts b/src/index.d.ts index 996a468..e31bd28 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -4,6 +4,6 @@ declare module 'webworker:getBlogWorker' { } declare module 'webworker:decodeBase64' { - const value: new () => Worker - export default value -} \ No newline at end of file + const value: new () => Worker; + export default value; +} diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 48b7666..4cb1908 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -1,8 +1,8 @@ export interface BlogContent { - postContent: any[] - title: string - createdAt: number - user?: any - postId?: string - layouts?: any -} \ No newline at end of file + postContent: any[]; + title: string; + createdAt: number; + user?: any; + postId?: string; + layouts?: any; +} diff --git a/src/main.tsx b/src/main.tsx index b456834..8acd6eb 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,19 +1,19 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' -import { HashRouter, BrowserRouter } from 'react-router-dom' +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; +import { HashRouter, BrowserRouter } from 'react-router-dom'; interface CustomWindow extends Window { - _qdnBase: any // Replace 'any' with the appropriate type if you know it + _qdnBase: any; // Replace 'any' with the appropriate type if you know it } -const customWindow = window as unknown as CustomWindow +const customWindow = window as unknown as CustomWindow; // Now you can access the _qdnTheme property without TypeScript errors -const baseUrl = customWindow?._qdnBase || '' +const baseUrl = customWindow?._qdnBase || ''; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - ) + ); } })} @@ -654,14 +605,14 @@ export const BlogIndividualPost = () => { alignItems: 'center', justifyContent: 'center', marginTop: '25px', - gap: 2 + gap: 2, }} > {row?.ids?.map((elementId: string) => { const section: any = blogContent?.postContent?.find( - (el) => el?.id === elementId - ) - if (!section) return null + (el) => el?.id === elementId, + ); + if (!section) return null; if (section?.type === 'editor') { return (
{ maxWidth: '800px', display: 'flex', flexDirection: 'column', - width: '100%' + width: '100%', }} > - Error loading content: Invalid Data - - } + fallback={Error loading content: Invalid Data} > { count={count} padding={0} > - +
- ) + ); } if (section?.type === 'image') { return (
- Error loading content: Invalid Data - - } + fallback={Error loading content: Invalid Data} > { sx={{ position: 'relative', width: '100%', - height: '100%' + height: '100%', }} > { className="post-image" style={{ objectFit: 'contain', - maxHeight: '50vh' + maxHeight: '50vh', }} />
- ) + ); } if (section?.type === 'video') { return (
- Error loading content: Invalid Data - - } + fallback={Error loading content: Invalid Data} > { sx={{ position: 'relative', width: '100%', - height: '100%' + height: '100%', }} > { identifier={section.content.identifier} poster={section.content.poster} customStyle={{ - height: '50vh' + height: '50vh', }} user={user} postId={fullPostId} @@ -786,17 +722,13 @@ export const BlogIndividualPost = () => {
- ) + ); } if (section?.type === 'audio') { return (
- Error loading content: Invalid Data - - } + fallback={Error loading content: Invalid Data} > { postId={fullPostId} user={user ? user : ''} onClick={() => { - if (!blog || !postId) return - const formBlogId = addPrefix(blog) - const formPostId = - buildIdentifierFromCreateTitleIdAndId( - formBlogId, - postId - ) + if (!blog || !postId) return; + const formBlogId = addPrefix(blog); + const formPostId = buildIdentifierFromCreateTitleIdAndId( + formBlogId, + postId, + ); if (formPostId !== audioPostId) { tempSaveAudio.current = { ...(tempSaveAudio.current || {}), currentSelection: section, message: - 'You are current on a playlist. Would you like to switch?' - } - setisOpenSwitchPlaylistModal(true) + 'You are current on a playlist. Would you like to switch?', + }; + setisOpenSwitchPlaylistModal(true); } else { - const findIndex = ( - audios || [] - ).findIndex( - (item) => - item.identifier === - section.content.identifier - ) + const findIndex = (audios || []).findIndex( + (item) => item.identifier === section.content.identifier, + ); if (findIndex >= 0) { - dispatch(setCurrAudio(findIndex)) + dispatch(setCurrAudio(findIndex)); } } }} @@ -854,17 +781,13 @@ export const BlogIndividualPost = () => {
- ) + ); } if (section?.type === 'file') { return (
- Error loading content: Invalid Data - - } + fallback={Error loading content: Invalid Data} > {
- ) + ); } if (section?.type === 'poll') { return ( @@ -912,11 +835,11 @@ export const BlogIndividualPost = () => {
- ) + ); } })} - ) + ); })} )} @@ -925,7 +848,7 @@ export const BlogIndividualPost = () => { sx={{ display: 'flex', alignItems: 'center', - gap: 1 + gap: 1, }} > @@ -935,7 +858,8 @@ export const BlogIndividualPost = () => { )} - ) + ); } - if (!userBlog) return null + if (!userBlog) return null; return ( <> { display: 'flex', gap: 1, alignItems: 'center', - justifyContent: 'center' + justifyContent: 'center', }} > { color="textPrimary" sx={{ textAlign: 'center', - marginTop: '20px' + marginTop: '20px', }} > {currentBlog?.blogId === blog ? currentBlog?.title : userBlog.title} @@ -254,10 +250,10 @@ export const BlogIndividualProfile = () => { {currentBlog?.blogId === blog && ( { - dispatch(toggleEditBlogModal(true)) + dispatch(toggleEditBlogModal(true)); }} > )} @@ -266,7 +262,7 @@ export const BlogIndividualProfile = () => { sx={{ backgroundColor: theme.palette.primary.light, color: theme.palette.text.primary, - fontFamily: 'Arial' + fontFamily: 'Arial', }} onClick={unsubscribe} > @@ -278,7 +274,7 @@ export const BlogIndividualProfile = () => { sx={{ backgroundColor: theme.palette.primary.light, color: theme.palette.text.primary, - fontFamily: 'Arial' + fontFamily: 'Arial', }} onClick={subscribe} > @@ -288,15 +284,18 @@ export const BlogIndividualProfile = () => { {isListView ? ( - + {blogPosts.map((post, index) => { - const existingPost = hashMapPosts[post.id + '-' + post.user] - const blogPost = existingPost ? existingPost : post - const str = blogPost.id - const arr = str.split('-post-') - const str1 = arr[0] - const blogId = removePrefix(str1) - const str2 = arr[1] + const existingPost = hashMapPosts[post.id + '-' + post.user]; + const blogPost = existingPost ? existingPost : post; + const str = blogPost.id; + const arr = str.split('-post-'); + const str1 = arr[0]; + const blogId = removePrefix(str1); + const str2 = arr[1]; return ( { alignItems: 'stretch', width: '100%', gap: 1, - '@media (max-width: 450px)': { width: '100%' } + '@media (max-width: 450px)': { width: '100%' }, }} > { zIndex: 10, bottom: '25px', right: '25px', - cursor: 'pointer' + cursor: 'pointer', }} onClick={() => { - navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`); }} /> )} - ) + ); })} ) : ( @@ -354,13 +353,13 @@ export const BlogIndividualProfile = () => { style={{ backgroundColor: theme.palette.background.default }} > {blogPosts.map((post, index) => { - const existingPost = hashMapPosts[post.id + '-' + post.user] - const blogPost = existingPost ? existingPost : post - const str = blogPost.id - const arr = str.split('-post-') - const str1 = arr[0] - const blogId = removePrefix(str1) - const str2 = arr[1] + const existingPost = hashMapPosts[post.id + '-' + post.user]; + const blogPost = existingPost ? existingPost : post; + const str = blogPost.id; + const arr = str.split('-post-'); + const str1 = arr[0]; + const blogId = removePrefix(str1); + const str2 = arr[1]; return ( { width: 'auto', position: 'relative', ' @media (max-width: 450px)': { - width: '100%' - } + width: '100%', + }, }} key={blogPost.id} > @@ -383,7 +382,7 @@ export const BlogIndividualProfile = () => { > { - navigate(`/${blogPost.user}/${blogId}/${str2}`) + navigate(`/${blogPost.user}/${blogId}/${str2}`); }} description={blogPost?.description} title={blogPost?.title} @@ -402,19 +401,19 @@ export const BlogIndividualProfile = () => { zIndex: 10, bottom: '25px', right: '25px', - cursor: 'pointer' + cursor: 'pointer', }} onClick={() => { - navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`); }} /> )} - ) + ); })} )} - ) -} + ); +}; diff --git a/src/pages/BlogList/BlogList.tsx b/src/pages/BlogList/BlogList.tsx index 4c6827c..0cab355 100644 --- a/src/pages/BlogList/BlogList.tsx +++ b/src/pages/BlogList/BlogList.tsx @@ -1,22 +1,15 @@ -import React, { FC, useCallback, useEffect, useRef, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import EditIcon from '@mui/icons-material/Edit' -import { - Box, - Button, - List, - ListItem, - Typography, - useTheme -} from '@mui/material' -import BlogPostPreview from './PostPreview' -import { useFetchPosts } from '../../hooks/useFetchPosts' -import LazyLoad from '../../components/common/LazyLoad' -import { removePrefix } from '../../utils/blogIdformats' -import Masonry from 'react-masonry-css' -import ContextMenuResource from '../../components/common/ContextMenu/ContextMenuResource' +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import EditIcon from '@mui/icons-material/Edit'; +import { Box, Button, List, ListItem, Typography, useTheme } from '@mui/material'; +import BlogPostPreview from './PostPreview'; +import { useFetchPosts } from '../../hooks/useFetchPosts'; +import LazyLoad from '../../components/common/LazyLoad'; +import { removePrefix } from '../../utils/blogIdformats'; +import Masonry from 'react-masonry-css'; +import ContextMenuResource from '../../components/common/ContextMenu/ContextMenuResource'; const breakpointColumnsObj = { default: 5, @@ -24,114 +17,102 @@ const breakpointColumnsObj = { 1300: 3, 940: 2, 700: 1, - 500: 1 -} + 500: 1, +}; interface BlogListProps { - mode?: string + mode?: string; } export const BlogList = ({ mode }: BlogListProps) => { - const theme = useTheme() - const prevVal = useRef('') - const { user } = useSelector((state: RootState) => state.auth) - const hashMapPosts = useSelector( - (state: RootState) => state.blog.hashMapPosts - ) - const favoritesLocal = useSelector( - (state: RootState) => state.blog.favoritesLocal - ) - const subscriptionPosts = useSelector( - (state: RootState) => state.blog.subscriptionPosts - ) - const countNewPosts = useSelector( - (state: RootState) => state.blog.countNewPosts - ) - const isFiltering = useSelector((state: RootState) => state.blog.isFiltering) - const filterValue = useSelector((state: RootState) => state.blog.filterValue) - const filteredPosts = useSelector( - (state: RootState) => state.blog.filteredPosts - ) + const theme = useTheme(); + const prevVal = useRef(''); + const { user } = useSelector((state: RootState) => state.auth); + const hashMapPosts = useSelector((state: RootState) => state.blog.hashMapPosts); + const favoritesLocal = useSelector((state: RootState) => state.blog.favoritesLocal); + const subscriptionPosts = useSelector((state: RootState) => state.blog.subscriptionPosts); + const countNewPosts = useSelector((state: RootState) => state.blog.countNewPosts); + const isFiltering = useSelector((state: RootState) => state.blog.isFiltering); + const filterValue = useSelector((state: RootState) => state.blog.filterValue); + const filteredPosts = useSelector((state: RootState) => state.blog.filteredPosts); - const { posts: globalPosts, favorites } = useSelector( - (state: RootState) => state.blog - ) - const navigate = useNavigate() + const { posts: globalPosts, favorites } = useSelector((state: RootState) => state.blog); + const navigate = useNavigate(); const { getBlogPosts, getBlogPostsFavorites, getBlogPostsSubscriptions, checkNewMessages, getNewPosts, - getBlogFilteredPosts - } = useFetchPosts() + getBlogFilteredPosts, + } = useFetchPosts(); const getPosts = React.useCallback(async () => { if (isFiltering) { - getBlogFilteredPosts(filterValue) - return + getBlogFilteredPosts(filterValue); + return; } if (mode === 'favorites') { - getBlogPostsFavorites() - return + getBlogPostsFavorites(); + return; } if (mode === 'subscriptions' && user?.name) { - getBlogPostsSubscriptions(user.name) - return + getBlogPostsSubscriptions(user.name); + return; } - await getBlogPosts() - }, [getBlogPosts, mode, favoritesLocal, user?.name, isFiltering, filterValue]) + await getBlogPosts(); + }, [getBlogPosts, mode, favoritesLocal, user?.name, isFiltering, filterValue]); const [isListView, setIsListView] = useState( - () => (localStorage.getItem('qblog_view') || 'tile') === 'list' - ) + () => (localStorage.getItem('qblog_view') || 'tile') === 'list', + ); useEffect(() => { const onViewMode = (e: Event) => { - const ce = e as CustomEvent - setIsListView(ce.detail === 'list') - } - window.addEventListener('qblog:viewmode', onViewMode as EventListener) - return () => window.removeEventListener('qblog:viewmode', onViewMode as EventListener) - }, []) + const ce = e as CustomEvent; + setIsListView(ce.detail === 'list'); + }; + window.addEventListener('qblog:viewmode', onViewMode as EventListener); + return () => window.removeEventListener('qblog:viewmode', onViewMode as EventListener); + }, []); - let posts = globalPosts + let posts = globalPosts; if (mode === 'favorites') { - posts = favorites + posts = favorites; } if (mode === 'subscriptions') { - posts = subscriptionPosts + posts = subscriptionPosts; } if (isFiltering) { - posts = filteredPosts + posts = filteredPosts; } - const interval = useRef(null) + const interval = useRef(null); const checkNewMessagesFunc = useCallback(() => { - let isCalling = false + let isCalling = false; interval.current = setInterval(async () => { - if (isCalling) return - isCalling = true - const res = await checkNewMessages() - isCalling = false - }, 30000) // 1 second interval - }, [checkNewMessages]) + if (isCalling) return; + isCalling = true; + const res = await checkNewMessages(); + isCalling = false; + }, 30000); // 1 second interval + }, [checkNewMessages]); useEffect(() => { if (!mode) { - checkNewMessagesFunc() + checkNewMessagesFunc(); } return () => { if (interval?.current) { - clearInterval(interval.current) + clearInterval(interval.current); } - } - }, [mode, checkNewMessagesFunc]) + }; + }, [mode, checkNewMessagesFunc]); useEffect(() => { if (isFiltering && filterValue !== prevVal?.current) { - prevVal.current = filterValue - getPosts() + prevVal.current = filterValue; + getPosts(); } - }, [filterValue, isFiltering, filteredPosts]) + }, [filterValue, isFiltering, filteredPosts]); // if (!favoritesLocal) return null return ( @@ -141,7 +122,7 @@ export const BlogList = ({ mode }: BlogListProps) => { sx={{ display: 'flex', alignItems: 'center', - justifyContent: 'center' + justifyContent: 'center', }} > @@ -153,7 +134,7 @@ export const BlogList = ({ mode }: BlogListProps) => { sx={{ backgroundColor: theme.palette.primary.light, color: theme.palette.text.primary, - fontFamily: 'Arial' + fontFamily: 'Arial', }} onClick={getNewPosts} > @@ -165,13 +146,13 @@ export const BlogList = ({ mode }: BlogListProps) => { {isListView ? ( {posts.map((post, index) => { - const existingPost = hashMapPosts[post.id + '-' + post.user] - let blogPost = existingPost ? existingPost : post - const str = blogPost.id - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) + const existingPost = hashMapPosts[post.id + '-' + post.user]; + let blogPost = existingPost ? existingPost : post; + const str = blogPost.id; + const arr = str.split('-post-'); + const str1 = arr[0]; + const str2 = arr[1]; + const blogId = removePrefix(str1); return ( { alignItems: 'stretch', width: '100%', gap: 1, - '@media (max-width: 450px)': { width: '100%' } + '@media (max-width: 450px)': { width: '100%' }, }} > { zIndex: 10, bottom: '25px', right: '25px', - cursor: 'pointer' + cursor: 'pointer', }} onClick={() => { - navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`); }} /> )} - ) + ); })} ) : ( @@ -229,13 +210,13 @@ export const BlogList = ({ mode }: BlogListProps) => { columnClassName="my-masonry-grid_column" > {posts.map((post, index) => { - const existingPost = hashMapPosts[post.id + '-' + post.user] - let blogPost = existingPost ? existingPost : post - const str = blogPost.id - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) + const existingPost = hashMapPosts[post.id + '-' + post.user]; + let blogPost = existingPost ? existingPost : post; + const str = blogPost.id; + const arr = str.split('-post-'); + const str1 = arr[0]; + const str2 = arr[1]; + const blogId = removePrefix(str1); return ( { width: 'auto', position: 'relative', ' @media (max-width: 450px)': { - width: '100%' - } + width: '100%', + }, }} key={blogPost.id} > @@ -258,7 +239,7 @@ export const BlogList = ({ mode }: BlogListProps) => { > { - navigate(`/${blogPost.user}/${blogId}/${str2}`) + navigate(`/${blogPost.user}/${blogId}/${str2}`); }} description={blogPost?.description} title={blogPost?.title} @@ -278,20 +259,20 @@ export const BlogList = ({ mode }: BlogListProps) => { zIndex: 10, bottom: '25px', right: '25px', - cursor: 'pointer' + cursor: 'pointer', }} onClick={() => { - navigate(`/${blogPost.user}/${blogId}/${str2}/edit`) + navigate(`/${blogPost.user}/${blogId}/${str2}/edit`); }} /> )} - ) + ); })} )} {/* */} - ) -} + ); +}; diff --git a/src/pages/BlogList/PostPreview-styles.ts b/src/pages/BlogList/PostPreview-styles.ts index a3c1269..01e0d30 100644 --- a/src/pages/BlogList/PostPreview-styles.ts +++ b/src/pages/BlogList/PostPreview-styles.ts @@ -1,81 +1,81 @@ -import { styled } from "@mui/system"; -import { Card, Box, Typography } from "@mui/material"; +import { styled } from '@mui/system'; +import { Card, Box, Typography } from '@mui/material'; -export const StyledCard = styled(Card)(({ theme }) => ({ - backgroundColor: theme.palette.mode === "light" ? theme.palette.primary.main : theme.palette.primary.dark, - maxWidth: "600px", - width: "100%", - margin: "10px 0px", - cursor: "pointer", - "@media (max-width: 450px)": { - width: "100%;" - } +export const StyledCard = styled(Card)(({ theme }) => ({ + backgroundColor: + theme.palette.mode === 'light' ? theme.palette.primary.main : theme.palette.primary.dark, + maxWidth: '600px', + width: '100%', + margin: '10px 0px', + cursor: 'pointer', + '@media (max-width: 450px)': { + width: '100%;', + }, })); -export const CardContentContainer = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.mode === "light" ? theme.palette.primary.dark : theme.palette.primary.light, - margin: "5px 10px", - borderRadius: "15px", +export const CardContentContainer = styled(Box)(({ theme }) => ({ + backgroundColor: + theme.palette.mode === 'light' ? theme.palette.primary.dark : theme.palette.primary.light, + margin: '5px 10px', + borderRadius: '15px', })); export const CardContentContainerComment = styled(Box)(({ theme }) => ({ backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.dark - : theme.palette.primary.light, + theme.palette.mode === 'light' ? theme.palette.primary.dark : theme.palette.primary.light, margin: '0px', borderRadius: '15px', width: '100%', display: 'flex', - flexDirection: 'column' -})) + flexDirection: 'column', +})); export const StyledCardHeader = styled(Box)({ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: '5px', - padding: '7px' -}) + padding: '7px', +}); export const StyledCardHeaderComment = styled(Box)({ display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: '5px', - padding: '7px' -}) + padding: '7px', +}); export const StyledCardCol = styled(Box)({ display: 'flex', overflow: 'hidden', flexDirection: 'column', gap: '2px', alignItems: 'flex-start', - width: '100%' -}) + width: '100%', +}); export const StyledCardColComment = styled(Box)({ display: 'flex', overflow: 'hidden', flexDirection: 'column', gap: '2px', alignItems: 'flex-start', - width: '100%' -}) + width: '100%', +}); export const StyledCardContent = styled(Box)({ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-start', padding: '5px 10px', - gap: '10px' -}) + gap: '10px', +}); export const StyledCardContentComment = styled(Box)({ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start', padding: '5px 10px', - gap: '10px' -}) + gap: '10px', +}); export const TitleText = styled(Typography)({ whiteSpace: 'nowrap', overflow: 'hidden', @@ -83,22 +83,22 @@ export const TitleText = styled(Typography)({ width: '100%', fontFamily: 'Cairo, sans-serif', fontSize: '22px', - lineHeight: '1.2' -}) + lineHeight: '1.2', +}); export const AuthorText = styled(Typography)({ fontFamily: 'Raleway, sans-serif', fontSize: '16px', - lineHeight: '1.2' -}) + lineHeight: '1.2', +}); export const AuthorTextComment = styled(Typography)({ fontFamily: 'Raleway, sans-serif', fontSize: '16px', - lineHeight: '1.2' -}) + lineHeight: '1.2', +}); export const IconsBox = styled(Box)({ display: 'flex', - gap: "3px", + gap: '3px', position: 'absolute', top: '12px', right: '5px', @@ -107,28 +107,28 @@ export const IconsBox = styled(Box)({ export const BookmarkIconContainer = styled(Box)({ display: 'flex', - boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;", + boxShadow: 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;', backgroundColor: '#fbfbfb', - color: "#50e3c2", + color: '#50e3c2', padding: '5px', borderRadius: '3px', transition: 'all 0.3s ease-in-out', - "&:hover": { + '&:hover': { cursor: 'pointer', - transform: "scale(1.1)", - } -}) + transform: 'scale(1.1)', + }, +}); export const BlockIconContainer = styled(Box)({ display: 'flex', - boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;", + boxShadow: 'rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;', backgroundColor: '#fbfbfb', - color: "#c25252", + color: '#c25252', padding: '5px', borderRadius: '3px', transition: 'all 0.3s ease-in-out', - "&:hover": { + '&:hover': { cursor: 'pointer', - transform: "scale(1.1)", - } -}) \ No newline at end of file + transform: 'scale(1.1)', + }, +}); diff --git a/src/pages/BlogList/PostPreview.tsx b/src/pages/BlogList/PostPreview.tsx index 69664c3..2d05545 100644 --- a/src/pages/BlogList/PostPreview.tsx +++ b/src/pages/BlogList/PostPreview.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react' +import React, { useMemo, useState } from 'react'; import { Avatar, Card, @@ -9,14 +9,14 @@ import { Box, Button, Tooltip, - useTheme -} from '@mui/material' -import Dialog from '@mui/material/Dialog' -import DialogActions from '@mui/material/DialogActions' -import DialogContent from '@mui/material/DialogContent' -import DialogContentText from '@mui/material/DialogContentText' -import DialogTitle from '@mui/material/DialogTitle' -import { styled } from '@mui/system' + useTheme, +} from '@mui/material'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import { styled } from '@mui/system'; import { CardContentContainer, @@ -28,35 +28,35 @@ import { StyledCardCol, IconsBox, BlockIconContainer, - BookmarkIconContainer -} from './PostPreview-styles' -import moment from 'moment' + BookmarkIconContainer, +} from './PostPreview-styles'; +import moment from 'moment'; import { blockUser, BlogPost, removeFavorites, removeSubscription, - upsertFavorites -} from '../../state/features/blogSlice' -import { useDispatch, useSelector } from 'react-redux' -import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder' -import BookmarkIcon from '@mui/icons-material/Bookmark' -import { AppDispatch, RootState } from '../../state/store' -import BlockIcon from '@mui/icons-material/Block' -import { CustomIcon } from '../../components/common/CustomIcon' -import ResponsiveImage from '../../components/common/ResponsiveImage' -import { formatDate } from '../../utils/time' + upsertFavorites, +} from '../../state/features/blogSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; +import BookmarkIcon from '@mui/icons-material/Bookmark'; +import { AppDispatch, RootState } from '../../state/store'; +import BlockIcon from '@mui/icons-material/Block'; +import { CustomIcon } from '../../components/common/CustomIcon'; +import ResponsiveImage from '../../components/common/ResponsiveImage'; +import { formatDate } from '../../utils/time'; interface BlogPostPreviewProps { - title: string - createdAt: number | string - author: string - postImage?: string - description: any - blogPost: BlogPost - onClick?: () => void - isValid?: boolean - tags?: string[] - fullWidth?: boolean + title: string; + createdAt: number | string; + author: string; + postImage?: string; + description: any; + blogPost: BlogPost; + onClick?: () => void; + isValid?: boolean; + tags?: string[]; + fullWidth?: boolean; } const BlogPostPreview: React.FC = ({ @@ -69,35 +69,31 @@ const BlogPostPreview: React.FC = ({ blogPost, isValid, tags, - fullWidth = false + fullWidth = false, }) => { - const [avatarUrl, setAvatarUrl] = React.useState('') - const [showIcons, setShowIcons] = React.useState(false) + const [avatarUrl, setAvatarUrl] = React.useState(''); + const [showIcons, setShowIcons] = React.useState(false); - const dispatch = useDispatch() - const theme = useTheme() - const favoritesLocal = useSelector( - (state: RootState) => state.blog.favoritesLocal - ) - const [isOpenAlert, setIsOpenAlert] = useState(false) - const subscriptions = useSelector( - (state: RootState) => state.blog.subscriptions - ) - const username = useSelector((state: RootState) => state.auth?.user?.name) + const dispatch = useDispatch(); + const theme = useTheme(); + const favoritesLocal = useSelector((state: RootState) => state.blog.favoritesLocal); + const [isOpenAlert, setIsOpenAlert] = useState(false); + const subscriptions = useSelector((state: RootState) => state.blog.subscriptions); + const username = useSelector((state: RootState) => state.auth?.user?.name); function extractTextFromSlate(nodes: any) { - if (!Array.isArray(nodes)) return '' - let text = '' + if (!Array.isArray(nodes)) return ''; + let text = ''; for (const node of nodes) { if (node.text) { - text += node.text + text += node.text; } else if (node.children) { - text += extractTextFromSlate(node.children) + text += extractTextFromSlate(node.children); } } - return text + return text; } const getAvatar = React.useCallback(async () => { try { @@ -105,35 +101,35 @@ const BlogPostPreview: React.FC = ({ action: 'GET_QDN_RESOURCE_URL', name: author, service: 'THUMBNAIL', - identifier: 'qortal_avatar' - }) + identifier: 'qortal_avatar', + }); - setAvatarUrl(url) + setAvatarUrl(url); } catch (error) {} - }, [author]) + }, [author]); React.useEffect(() => { - getAvatar() - }, []) + getAvatar(); + }, []); const isFavorite = useMemo(() => { - if (!favoritesLocal) return false - return favoritesLocal.find((fav) => fav?.id === blogPost?.id) - }, [favoritesLocal, blogPost?.id]) + if (!favoritesLocal) return false; + return favoritesLocal.find((fav) => fav?.id === blogPost?.id); + }, [favoritesLocal, blogPost?.id]); const blockUserFunc = async (user: string) => { - if (user === 'Q-Blog') return + if (user === 'Q-Blog') return; if (subscriptions.includes(user) && username) { try { - const listName = `q-blog-subscriptions-${username}` + const listName = `q-blog-subscriptions-${username}`; const response = await qortalRequest({ action: 'DELETE_LIST_ITEM', list_name: listName, - item: user - }) + item: user, + }); if (response === true) { - dispatch(removeSubscription(user)) + dispatch(removeSubscription(user)); } } catch (error) {} } @@ -142,38 +138,38 @@ const BlogPostPreview: React.FC = ({ const response = await qortalRequest({ action: 'ADD_LIST_ITEMS', list_name: 'blockedNames_q-blog', - items: [user] - }) + items: [user], + }); if (response === true) { - dispatch(blockUser(user)) - dispatch(removeFavorites(blogPost.id)) + dispatch(blockUser(user)); + dispatch(removeFavorites(blogPost.id)); } } catch (error) {} - } + }; const continueToPost = () => { if (isValid === false) { - setIsOpenAlert(true) - return + setIsOpenAlert(true); + return; } - if (!onClick) return - onClick() - } + if (!onClick) return; + onClick(); + }; const handleClose = () => { - setIsOpenAlert(false) - } + setIsOpenAlert(false); + }; const dimensions = useMemo(() => { if (Array.isArray(tags)) { - const imgDimensions = tags[tags.length - 2] - if (!imgDimensions?.includes('v1.')) return '' - return imgDimensions + const imgDimensions = tags[tags.length - 2]; + if (!imgDimensions?.includes('v1.')) return ''; + return imgDimensions; } - return '' - }, [tags]) + return ''; + }, [tags]); return ( <> @@ -183,9 +179,7 @@ const BlogPostPreview: React.FC = ({ onMouseLeave={() => setShowIcons(false)} sx={fullWidth ? { width: '100%', maxWidth: 'none' } : undefined} > - {!fullWidth && ( - - )} + {!fullWidth && } {fullWidth ? ( = ({ width: '100%', maxHeight: { xs: 200, sm: 240, md: 280, lg: 320 }, overflow: 'hidden', - minHeight: { xs: 140, sm: 160 } + minHeight: { xs: 140, sm: 160 }, }} > @@ -213,9 +207,7 @@ const BlogPostPreview: React.FC = ({ {author} @@ -231,7 +223,7 @@ const BlogPostPreview: React.FC = ({ display: '-webkit-box', WebkitLineClamp: { xs: 3, sm: 4 }, WebkitBoxOrient: 'vertical', - overflow: 'hidden' + overflow: 'hidden', }} > {description} @@ -253,7 +245,7 @@ const BlogPostPreview: React.FC = ({ display: 'flex', alignItems: 'center', justifyContent: 'center', - overflow: 'hidden' + overflow: 'hidden', }} > = ({ borderRadius: 8, objectFit: 'contain', objectPosition: 'center', - display: 'block' + display: 'block', }} /> @@ -275,9 +267,7 @@ const BlogPostPreview: React.FC = ({ ) : ( - + @@ -286,11 +276,7 @@ const BlogPostPreview: React.FC = ({ {title} {author} @@ -326,10 +312,10 @@ const BlogPostPreview: React.FC = ({ > { - dispatch(removeFavorites(blogPost.id)) + dispatch(removeFavorites(blogPost.id)); }} /> @@ -343,7 +329,7 @@ const BlogPostPreview: React.FC = ({ > { - dispatch(upsertFavorites([blogPost])) + dispatch(upsertFavorites([blogPost])); }} /> @@ -356,7 +342,7 @@ const BlogPostPreview: React.FC = ({ > { - blockUserFunc(blogPost.user) + blockUserFunc(blogPost.user); }} /> @@ -368,13 +354,10 @@ const BlogPostPreview: React.FC = ({ aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" > - - Invalid Content Structure - + Invalid Content Structure - This post seems to contain an invalid content structure. Click - continue to proceed + This post seems to contain an invalid content structure. Click continue to proceed @@ -385,7 +368,7 @@ const BlogPostPreview: React.FC = ({
- ) -} + ); +}; -export default BlogPostPreview +export default BlogPostPreview; diff --git a/src/pages/CreateEditProfile/CreatEditProfile.tsx b/src/pages/CreateEditProfile/CreatEditProfile.tsx index 61d4f2b..12c33b0 100644 --- a/src/pages/CreateEditProfile/CreatEditProfile.tsx +++ b/src/pages/CreateEditProfile/CreatEditProfile.tsx @@ -1,7 +1,5 @@ -import React from 'react' +import React from 'react'; export const CreatEditProfile = () => { - return ( -
CreatEditProfile
- ) -} + return
CreatEditProfile
; +}; diff --git a/src/pages/CreatePost/CreatePost-styles.ts b/src/pages/CreatePost/CreatePost-styles.ts index da84d3a..b83360e 100644 --- a/src/pages/CreatePost/CreatePost-styles.ts +++ b/src/pages/CreatePost/CreatePost-styles.ts @@ -1,14 +1,14 @@ -import { styled } from '@mui/system' +import { styled } from '@mui/system'; -import { Button } from '@mui/material' +import { Button } from '@mui/material'; export const BuilderButton = styled(Button)(({ theme }) => ({ backgroundColor: theme.palette.primary.light, color: theme.palette.text.primary, fontFamily: 'Arial', - transition: "all 0.3s ease-in-out", - "&:hover": { - cursor: "pointer", - filter: "brightness(0.9)" - } -})); \ No newline at end of file + transition: 'all 0.3s ease-in-out', + '&:hover': { + cursor: 'pointer', + filter: 'brightness(0.9)', + }, +})); diff --git a/src/pages/CreatePost/CreatePost.tsx b/src/pages/CreatePost/CreatePost.tsx index f11cd5e..97655bb 100644 --- a/src/pages/CreatePost/CreatePost.tsx +++ b/src/pages/CreatePost/CreatePost.tsx @@ -1,111 +1,106 @@ -import { Box, Button, Typography } from '@mui/material' -import React, { useEffect, useMemo, useState } from 'react' -import { ReusableModal } from '../../components/modals/ReusableModal' -import { CreatePostBuilder } from './CreatePostBuilder' -import { CreatePostMinimal } from './CreatePostMinimal' -import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded' -import HourglassFullRoundedIcon from '@mui/icons-material/HourglassFullRounded' -import { display } from '@mui/system' -import { useDispatch, useSelector } from 'react-redux' -import { setIsLoadingGlobal } from '../../state/features/globalSlice' -import { useNavigate, useParams } from 'react-router-dom' -import { checkStructure } from '../../utils/checkStructure' -import { RootState } from '../../state/store' -import { - addPrefix, - buildIdentifierFromCreateTitleIdAndId -} from '../../utils/blogIdformats' -import { Tipping } from '../../components/common/Tipping/Tipping' -type EditorType = 'minimal' | 'builder' +import { Box, Button, Typography } from '@mui/material'; +import React, { useEffect, useMemo, useState } from 'react'; +import { ReusableModal } from '../../components/modals/ReusableModal'; +import { CreatePostBuilder } from './CreatePostBuilder'; +import { CreatePostMinimal } from './CreatePostMinimal'; +import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded'; +import HourglassFullRoundedIcon from '@mui/icons-material/HourglassFullRounded'; +import { display } from '@mui/system'; +import { useDispatch, useSelector } from 'react-redux'; +import { setIsLoadingGlobal } from '../../state/features/globalSlice'; +import { useNavigate, useParams } from 'react-router-dom'; +import { checkStructure } from '../../utils/checkStructure'; +import { RootState } from '../../state/store'; +import { addPrefix, buildIdentifierFromCreateTitleIdAndId } from '../../utils/blogIdformats'; +import { Tipping } from '../../components/common/Tipping/Tipping'; +type EditorType = 'minimal' | 'builder'; interface CreatePostProps { - mode?: string + mode?: string; } export const CreatePost = ({ mode }: CreatePostProps) => { - const { user: username, postId, blog } = useParams() + const { user: username, postId, blog } = useParams(); const fullPostId = useMemo(() => { - if (!blog || !postId || mode !== 'edit') return '' - const formBlogId = addPrefix(blog) - const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId) - return formPostId - }, [blog, postId, mode]) - const user = useSelector((state: RootState) => state.auth?.user) + if (!blog || !postId || mode !== 'edit') return ''; + const formBlogId = addPrefix(blog); + const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId); + return formPostId; + }, [blog, postId, mode]); + const user = useSelector((state: RootState) => state.auth?.user); - const [toggleEditorType, setToggleEditorType] = useState( - null - ) - const [blogContentForEdit, setBlogContentForEdit] = useState(null) - const [blogMetadataForEdit, setBlogMetadataForEdit] = useState(null) - const [editType, setEditType] = useState(null) - const [isOpen, setIsOpen] = useState(false) - const dispatch = useDispatch() - const navigate = useNavigate() + const [toggleEditorType, setToggleEditorType] = useState(null); + const [blogContentForEdit, setBlogContentForEdit] = useState(null); + const [blogMetadataForEdit, setBlogMetadataForEdit] = useState(null); + const [editType, setEditType] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const dispatch = useDispatch(); + const navigate = useNavigate(); React.useEffect(() => { if (!toggleEditorType && mode !== 'edit') { - setIsOpen(true) + setIsOpen(true); } - }, [setIsOpen, toggleEditorType]) + }, [setIsOpen, toggleEditorType]); const switchType = () => { - setIsOpen(true) - } + setIsOpen(true); + }; - useEffect(()=> { - if(username && user?.name && mode === 'edit'){ - if(username !== user?.name){ - navigate('/') + useEffect(() => { + if (username && user?.name && mode === 'edit') { + if (username !== user?.name) { + navigate('/'); } } - }, [user, username, mode]) + }, [user, username, mode]); const getBlogPost = React.useCallback(async () => { try { - dispatch(setIsLoadingGlobal(true)) - const url = `/arbitrary/BLOG_POST/${username}/${fullPostId}` + dispatch(setIsLoadingGlobal(true)); + const url = `/arbitrary/BLOG_POST/${username}/${fullPostId}`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const responseData = await response.json() + const responseData = await response.json(); if (checkStructure(responseData)) { // setNewPostContent(responseData.postContent) // setTitle(responseData?.title || '') // setBlogInfo(responseData) - const blogType = responseData?.layoutGeneralSettings?.blogPostType + const blogType = responseData?.layoutGeneralSettings?.blogPostType; if (blogType) { - setEditType(blogType) - setBlogContentForEdit(responseData) + setEditType(blogType); + setBlogContentForEdit(responseData); } //TODO - NAME SHOULD BE EXACT // const url2 = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${fullPostId}&exactMatchNames=${username}&limit=1&includemetadata=true` - const url2 = `/arbitrary/resources?service=BLOG_POST&identifier=${fullPostId}&name=${username}&limit=1&includemetadata=true` + const url2 = `/arbitrary/resources?service=BLOG_POST&identifier=${fullPostId}&name=${username}&limit=1&includemetadata=true`; const responseBlogs = await fetch(url2, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const dataMetadata = await responseBlogs.json() + const dataMetadata = await responseBlogs.json(); if (dataMetadata && dataMetadata.length > 0) { - setBlogMetadataForEdit(dataMetadata[0]) + setBlogMetadataForEdit(dataMetadata[0]); } } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } - }, [username, fullPostId]) + }, [username, fullPostId]); React.useEffect(() => { if (mode === 'edit') { - getBlogPost() + getBlogPost(); } - }, [mode]) + }, [mode]); return ( <> @@ -119,13 +114,11 @@ export const CreatePost = ({ mode }: CreatePostProps) => { {toggleEditorType && ( - - Switching editor type will delete your current progress - + Switching editor type will delete your current progress )} { display: 'flex', justifyContent: 'center', alignItems: 'center', - gap: 2 + gap: 2, }} > { - setToggleEditorType('minimal') - setIsOpen(false) + setToggleEditorType('minimal'); + setIsOpen(false); }} sx={{ display: 'flex', @@ -149,7 +142,7 @@ export const CreatePost = ({ mode }: CreatePostProps) => { padding: '20px', borderRadius: '6px', border: '1px solid', - cursor: 'pointer' + cursor: 'pointer', }} > Minimal Editor @@ -157,8 +150,8 @@ export const CreatePost = ({ mode }: CreatePostProps) => { { - setToggleEditorType('builder') - setIsOpen(false) + setToggleEditorType('builder'); + setIsOpen(false); }} sx={{ display: 'flex', @@ -168,7 +161,7 @@ export const CreatePost = ({ mode }: CreatePostProps) => { padding: '20px', borderRadius: '6px', border: '1px solid', - cursor: 'pointer' + cursor: 'pointer', }} > Builder Editor @@ -179,12 +172,8 @@ export const CreatePost = ({ mode }: CreatePostProps) => { )} - {toggleEditorType === 'minimal' && ( - - )} - {toggleEditorType === 'builder' && ( - - )} + {toggleEditorType === 'minimal' && } + {toggleEditorType === 'builder' && } {mode === 'edit' && editType === 'minimal' && ( { /> )} - ) -} + ); +}; diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index 0d42eb6..e3c158a 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -1,67 +1,77 @@ -import React, { useCallback, useEffect } from 'react' +import React, { useCallback, useEffect } from 'react'; -import BlogEditor from '../../components/editor/BlogEditor' -import ShortUniqueId from 'short-unique-id' -import ReadOnlySlate from '../../components/editor/ReadOnlySlate' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import TextField from '@mui/material/TextField' -import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate' -import ImageUploader from '../../components/common/ImageUploader' -import { ImagePanel } from '../../components/common/ImagePanel' -import AudiotrackIcon from '@mui/icons-material/Audiotrack' -import DeleteIcon from '@mui/icons-material/Delete' -import { Button, Box, useTheme, Dialog, DialogTitle, DialogContent, Tabs, Tab, Typography } from '@mui/material' -import { styled } from '@mui/system' -import { Descendant } from 'slate' -import EditIcon from '@mui/icons-material/Edit' -import { extractTextFromSlate } from '../../utils/extractTextFromSlate' -import { setNotification } from '../../state/features/notificationsSlice' -import { VideoPanel } from '../../components/common/VideoPanel' -import PostPublishModal from '../../components/common/PostPublishModal' -import DynamicHeightItem from '../../components/DynamicHeightItem' -import { Responsive, WidthProvider } from 'react-grid-layout' -import '/node_modules/react-grid-layout/css/styles.css' -import '/node_modules/react-resizable/css/styles.css' -import { ReusableModal } from '../../components/modals/ReusableModal' -import { VideoPlayer } from '../../components/common/VideoPlayer' -import { EditorToolbar } from './components/Toolbar/EditorToolbar' -import { Navbar } from './components/Navbar/NavbarBuilder' -import { UserNavbar } from '../../components/common/UserNavbar/UserNavbar' -import { setCurrentBlog } from '../../state/features/globalSlice' -import AudioElement from '../../components/AudioElement' -import { AudioPanel } from '../../components/common/AudioPanel' +import BlogEditor from '../../components/editor/BlogEditor'; +import ShortUniqueId from 'short-unique-id'; +import ReadOnlySlate from '../../components/editor/ReadOnlySlate'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import TextField from '@mui/material/TextField'; +import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate'; +import ImageUploader from '../../components/common/ImageUploader'; +import { ImagePanel } from '../../components/common/ImagePanel'; +import AudiotrackIcon from '@mui/icons-material/Audiotrack'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { + Button, + Box, + useTheme, + Dialog, + DialogTitle, + DialogContent, + Tabs, + Tab, + Typography, +} from '@mui/material'; +import { styled } from '@mui/system'; +import { Descendant } from 'slate'; +import EditIcon from '@mui/icons-material/Edit'; +import { extractTextFromSlate } from '../../utils/extractTextFromSlate'; +import { setNotification } from '../../state/features/notificationsSlice'; +import { VideoPanel } from '../../components/common/VideoPanel'; +import PostPublishModal from '../../components/common/PostPublishModal'; +import DynamicHeightItem from '../../components/DynamicHeightItem'; +import { Responsive, WidthProvider } from 'react-grid-layout'; +import '/node_modules/react-grid-layout/css/styles.css'; +import '/node_modules/react-resizable/css/styles.css'; +import { ReusableModal } from '../../components/modals/ReusableModal'; +import { VideoPlayer } from '../../components/common/VideoPlayer'; +import { EditorToolbar } from './components/Toolbar/EditorToolbar'; +import { Navbar } from './components/Navbar/NavbarBuilder'; +import { UserNavbar } from '../../components/common/UserNavbar/UserNavbar'; +import { setCurrentBlog } from '../../state/features/globalSlice'; +import AudioElement from '../../components/AudioElement'; +import { AudioPanel } from '../../components/common/AudioPanel'; import { addPostToBeginning, addToHashMap, updateInHashMap, - updatePost -} from '../../state/features/blogSlice' -import { removePrefix } from '../../utils/blogIdformats' -import { useNavigate } from 'react-router-dom' -import { BuilderButton } from './CreatePost-styles' -import FileElement from '../../components/FileElement' -const ResponsiveGridLayout = WidthProvider(Responsive) -const initialMinHeight = 2 // Define an initial minimum height for grid items -const uid = new ShortUniqueId() + updatePost, +} from '../../state/features/blogSlice'; +import { removePrefix } from '../../utils/blogIdformats'; +import { useNavigate } from 'react-router-dom'; +import { BuilderButton } from './CreatePost-styles'; +import FileElement from '../../components/FileElement'; +const ResponsiveGridLayout = WidthProvider(Responsive); +const initialMinHeight = 2; // Define an initial minimum height for grid items +const uid = new ShortUniqueId(); const md = [ { i: 'a', x: 0, y: 0, w: 4, h: initialMinHeight }, - { i: 'b', x: 6, y: 0, w: 4, h: initialMinHeight } -] + { i: 'b', x: 6, y: 0, w: 4, h: initialMinHeight }, +]; const sm = [ { i: 'a', x: 0, y: 0, w: 6, h: initialMinHeight }, - { i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight } -] + { i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight }, +]; const xs = [ { i: 'a', x: 0, y: 0, w: 6, h: initialMinHeight }, - { i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight } -] + { i: 'b', x: 6, y: 0, w: 6, h: initialMinHeight }, +]; const initialValue: Descendant[] = [ { type: 'paragraph', - children: [{ text: '' }] - } -] + children: [{ text: '' }], + }, +]; const BlogTitleInput = styled(TextField)(({ theme }) => ({ marginBottom: '15px', @@ -71,125 +81,124 @@ const BlogTitleInput = styled(TextField)(({ theme }) => ({ background: 'transparent', '&::placeholder': { fontSize: '28px', - color: theme.palette.text.primary - } + color: theme.palette.text.primary, + }, }, '& .MuiInputLabel-root': { - fontSize: '28px' + fontSize: '28px', }, '& .MuiInputBase-root': { background: 'transparent', '&:hover': { - background: 'transparent' + background: 'transparent', }, '&.Mui-focused': { - background: 'transparent' - } + background: 'transparent', + }, }, '& .MuiOutlinedInput-root': { '&:hover .MuiOutlinedInput-notchedOutline': { - borderColor: theme.palette.primary.main + borderColor: theme.palette.primary.main, }, '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: theme.palette.primary.main - } - } -})) + borderColor: theme.palette.primary.main, + }, + }, +})); interface CreatePostBuilderProps { - blogContentForEdit?: any - postIdForEdit?: string - blogMetadataForEdit?: any - switchType?: () => void + blogContentForEdit?: any; + postIdForEdit?: string; + blogMetadataForEdit?: any; + switchType?: () => void; } export const CreatePostBuilder = ({ blogContentForEdit, postIdForEdit, blogMetadataForEdit, - switchType + switchType, }: CreatePostBuilderProps) => { - const navigate = useNavigate() + const navigate = useNavigate(); - const theme = useTheme() - const { user } = useSelector((state: RootState) => state.auth) - const { currentBlog } = useSelector((state: RootState) => state.global) - const [editingSection, setEditingSection] = React.useState(null) - const [layouts, setLayouts] = React.useState({ md, sm, xs }) - const [currentBreakpoint, setCurrentBreakpoint] = React.useState() + const theme = useTheme(); + const { user } = useSelector((state: RootState) => state.auth); + const { currentBlog } = useSelector((state: RootState) => state.global); + const [editingSection, setEditingSection] = React.useState(null); + const [layouts, setLayouts] = React.useState({ md, sm, xs }); + const [currentBreakpoint, setCurrentBreakpoint] = React.useState(); const handleLayoutChange = (layout: any, layoutss: any) => { - setLayouts(layoutss) - } - const [newPostContent, setNewPostContent] = React.useState([]) - const [title, setTitle] = React.useState('') - const [isOpenPostModal, setIsOpenPostModal] = React.useState(false) - const [isOpenEditTextModal, setIsOpenEditTextModal] = - React.useState(false) - const [value, setValue] = React.useState(initialValue) - const [editorKey, setEditorKey] = React.useState(1) - const [count, setCount] = React.useState(1) - const [isOpenAddTextModal, setIsOpenAddTextModal] = - React.useState(false) - const [paddingValue, onChangePadding] = React.useState(5) - const [coverPickerOpen, setCoverPickerOpen] = React.useState(false) - const [coverTab, setCoverTab] = React.useState<'upload' | 'existing'>('upload') - const [pendingVideo, setPendingVideo] = React.useState(null) - const [selectedPoster, setSelectedPoster] = React.useState('') - const [existingImages, setExistingImages] = React.useState([]) - const [existingImagesLoading, setExistingImagesLoading] = React.useState(false) - const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add') - const [coverPickerTarget, setCoverPickerTarget] = React.useState(null) + setLayouts(layoutss); + }; + const [newPostContent, setNewPostContent] = React.useState([]); + const [title, setTitle] = React.useState(''); + const [isOpenPostModal, setIsOpenPostModal] = React.useState(false); + const [isOpenEditTextModal, setIsOpenEditTextModal] = React.useState(false); + const [value, setValue] = React.useState(initialValue); + const [editorKey, setEditorKey] = React.useState(1); + const [count, setCount] = React.useState(1); + const [isOpenAddTextModal, setIsOpenAddTextModal] = React.useState(false); + const [paddingValue, onChangePadding] = React.useState(5); + const [coverPickerOpen, setCoverPickerOpen] = React.useState(false); + const [coverTab, setCoverTab] = React.useState<'upload' | 'existing'>('upload'); + const [pendingVideo, setPendingVideo] = React.useState(null); + const [selectedPoster, setSelectedPoster] = React.useState(''); + const [existingImages, setExistingImages] = React.useState([]); + const [existingImagesLoading, setExistingImagesLoading] = React.useState(false); + const [coverPickerMode, setCoverPickerMode] = React.useState<'add' | 'edit'>('add'); + const [coverPickerTarget, setCoverPickerTarget] = React.useState(null); const qdnResourceUrl = React.useCallback( - (service: string, name: string, identifier: string) => `/arbitrary/${service}/${name}/${identifier}`, - [] - ) + (service: string, name: string, identifier: string) => + `/arbitrary/${service}/${name}/${identifier}`, + [], + ); const fetchExistingImages = React.useCallback(async () => { - if (!user?.name) return - setExistingImagesLoading(true) + if (!user?.name) return; + setExistingImagesLoading(true); try { - const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE'] + const SERVICES = ['IMAGE', 'THUMBNAIL', 'QCHAT_IMAGE']; const lists = await Promise.all( SERVICES.map(async (svc) => { const res = await fetch( - `/arbitrary/resources?service=${svc}&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true` - ) - const data = await res.json() + `/arbitrary/resources?service=${svc}&name=${user.name}&includemetadata=true&limit=100&offset=0&reverse=true`, + ); + const data = await res.json(); return Array.isArray(data) ? data.map((it: any) => ({ ...it, service: it.service || svc })) - : [] - }) - ) - const merged = lists.flat() - setExistingImages(merged) + : []; + }), + ); + const merged = lists.flat(); + setExistingImages(merged); } catch (e) { - setExistingImages([]) + setExistingImages([]); } finally { - setExistingImagesLoading(false) + setExistingImagesLoading(false); } - }, [user]) - const [isEditNavOpen, setIsEditNavOpen] = React.useState(false) - const dispatch = useDispatch() - const [navbarConfig, setNavbarConfig] = React.useState(null) + }, [user]); + const [isEditNavOpen, setIsEditNavOpen] = React.useState(false); + const dispatch = useDispatch(); + const [navbarConfig, setNavbarConfig] = React.useState(null); const addPostSection = React.useCallback((content: any) => { const section = { type: 'editor', version: 1, content, - id: uid() - } + id: uid(), + }; - setNewPostContent((prev) => [...prev, section]) - setEditorKey((prev) => prev + 1) - }, []) + setNewPostContent((prev) => [...prev, section]); + setEditorKey((prev) => prev + 1); + }, []); async function getBlog(name: string, identifier: string, blog: any) { - const urlBlog = `/arbitrary/BLOG/${name}/${identifier}` + const urlBlog = `/arbitrary/BLOG/${name}/${identifier}`; const response = await fetch(urlBlog, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); dispatch( setCurrentBlog({ createdAt: responseData?.createdAt || '', @@ -199,48 +208,46 @@ export const CreatePostBuilder = ({ blogImage: responseData?.blogImage || '', category: blog.metadata?.category, tags: blog.metadata?.tags || [], - navbarConfig: responseData?.navbarConfig || null - }) - ) + navbarConfig: responseData?.navbarConfig || null, + }), + ); } useEffect(() => { if (blogContentForEdit && postIdForEdit && blogMetadataForEdit) { - setTitle(blogContentForEdit?.title || '') + setTitle(blogContentForEdit?.title || ''); setLayouts( blogContentForEdit?.layouts || { - rows: [] - } - ) - setNewPostContent(blogContentForEdit?.postContent || []) - onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding || 5) + rows: [], + }, + ); + setNewPostContent(blogContentForEdit?.postContent || []); + onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding || 5); } - }, [blogContentForEdit, postIdForEdit, blogMetadataForEdit]) + }, [blogContentForEdit, postIdForEdit, blogMetadataForEdit]); const editBlog = React.useCallback( async (navbarConfig: any) => { - if (!user || !user.name) - throw new Error('Cannot update: your Qortal name is not accessible') + if (!user || !user.name) throw new Error('Cannot update: your Qortal name is not accessible'); - if (!currentBlog) - throw new Error('Your blog is not available. Refresh and try again.') + if (!currentBlog) throw new Error('Your blog is not available. Refresh and try again.'); - const name = user.name - const formattedTags: { [key: string]: string } = {} - const tags = currentBlog?.tags || [] - const category = currentBlog?.category || '' - const title = currentBlog?.title || '' - const description = currentBlog?.description || '' + const name = user.name; + const formattedTags: { [key: string]: string } = {}; + const tags = currentBlog?.tags || []; + const category = currentBlog?.category || ''; + const title = currentBlog?.title || ''; + const description = currentBlog?.description || ''; tags.forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); const blogProps: any = { ...currentBlog, - navbarConfig - } + navbarConfig, + }; - const blogPostToBase64 = await objectToBase64(blogProps) + const blogPostToBase64 = await objectToBase64(blogProps); try { const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', @@ -251,221 +258,213 @@ export const CreatePostBuilder = ({ description, category, ...formattedTags, - identifier: currentBlog.blogId - }) + identifier: currentBlog.blogId, + }); await new Promise((res, rej) => { setTimeout(() => { - res() - }, 1000) - }) + res(); + }, 1000); + }); // getBlog(name, currentBlog.blogId, currentBlog) - dispatch(setCurrentBlog(blogProps)) + dispatch(setCurrentBlog(blogProps)); dispatch( setNotification({ msg: 'Blog successfully updated', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); } catch (error: any) { - let notificationObj: any = null + let notificationObj: any = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to save blog', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to save blog', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to save blog', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); if (error instanceof Error) { - throw new Error(error.message) + throw new Error(error.message); } else { - throw new Error('An unknown error occurred') + throw new Error('An unknown error occurred'); } } }, - [user, currentBlog] - ) + [user, currentBlog], + ); - const handleSaveNavBar = useCallback( - async (navMenu: any, navbarConfig: any) => { - try { - const config = { - type: 'topNav', - title: '', - logo: '', - ...navbarConfig, - navItems: navMenu - } - await editBlog(config) - setIsEditNavOpen(false) - setNavbarConfig(config) - } catch (error: any) { - dispatch( - setNotification({ - msg: error?.message || 'Could not save the navbar', - alertType: 'error' - }) - ) - } - }, - [] - ) - - const handleRemoveNavBar = useCallback(async () => { + const handleSaveNavBar = useCallback(async (navMenu: any, navbarConfig: any) => { try { - await editBlog(null) - setNavbarConfig(null) - setIsEditNavOpen(false) + const config = { + type: 'topNav', + title: '', + logo: '', + ...navbarConfig, + navItems: navMenu, + }; + await editBlog(config); + setIsEditNavOpen(false); + setNavbarConfig(config); } catch (error: any) { dispatch( setNotification({ msg: error?.message || 'Could not save the navbar', - alertType: 'error' - }) - ) + alertType: 'error', + }), + ); } - }, []) + }, []); + + const handleRemoveNavBar = useCallback(async () => { + try { + await editBlog(null); + setNavbarConfig(null); + setIsEditNavOpen(false); + } catch (error: any) { + dispatch( + setNotification({ + msg: error?.message || 'Could not save the navbar', + alertType: 'error', + }), + ); + } + }, []); function objectToBase64(obj: any) { // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) + const jsonString = JSON.stringify(obj); // Step 2: Create a Blob from the JSON string - const blob = new Blob([jsonString], { type: 'application/json' }) + const blob = new Blob([jsonString], { type: 'application/json' }); // Step 3: Create a FileReader to read the Blob as a base64-encoded string return new Promise((resolve, reject) => { - const reader = new FileReader() + const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === 'string') { // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace( - 'data:application/json;base64,', - '' - ) - resolve(base64) + const base64 = reader.result.replace('data:application/json;base64,', ''); + resolve(base64); } else { - reject( - new Error('Failed to read the Blob as a base64-encoded string') - ) + reject(new Error('Failed to read the Blob as a base64-encoded string')); } - } + }; reader.onerror = () => { - reject(reader.error) - } - reader.readAsDataURL(blob) - }) + reject(reader.error); + }; + reader.readAsDataURL(blob); + }); } const description = React.useMemo(() => { - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') + let description = ''; + const findText = newPostContent.find((data) => data?.type === 'editor'); if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) + description = extractTextFromSlate(findText?.content); + description = description.slice(0, 180); } - return description - }, [newPostContent]) + return description; + }, [newPostContent]); const post = React.useMemo(() => { return { description, - title - } - }, [title, description]) + title, + }; + }, [title, description]); async function publishQDNResource(params: any) { - let address - let name - let errorMsg = '' + let address; + let name; + let errorMsg = ''; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ''; - const missingFields = [] + const missingFields = []; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = 'Cannot post without a name'; } - if (!title) missingFields.push('title') + if (!title) missingFields.push('title'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (newPostContent.length === 0) { - errorMsg = 'Your post has no content' + errorMsg = 'Your post has no content'; } if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.' + errorMsg = 'Cannot publish without first creating a blog.'; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) + alertType: 'error', + }), + ); + throw new Error(errorMsg); } const layoutGeneralSettings = { padding: paddingValue ?? 0, - blogPostType: 'builder' - } + blogPostType: 'builder', + }; const postObject = { title, createdAt: Date.now(), postContent: newPostContent, layouts, - layoutGeneralSettings - } + layoutGeneralSettings, + }; try { - if (!currentBlog) return - const id = uid() + if (!currentBlog) return; + const id = uid(); let createTitleId = title .replace(/[^a-zA-Z0-9\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') - .trim() + .trim(); if (createTitleId.toLowerCase().includes('post')) { - createTitleId = createTitleId.replace(/post/gi, '') + createTitleId = createTitleId.replace(/post/gi, ''); } if (createTitleId.toLowerCase().includes('q-blog')) { - createTitleId = createTitleId.replace(/q-blog/gi, '') + createTitleId = createTitleId.replace(/q-blog/gi, ''); } if (createTitleId.endsWith('-')) { - createTitleId = createTitleId.slice(0, -1) + createTitleId = createTitleId.slice(0, -1); } if (createTitleId.startsWith('-')) { - createTitleId = createTitleId.slice(1) + createTitleId = createTitleId.slice(1); } - createTitleId = createTitleId.slice(0, 24) - const identifier = `${currentBlog.blogId}-post-${createTitleId}-${id}` - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') + createTitleId = createTitleId.slice(0, 24); + const identifier = `${currentBlog.blogId}-post-${createTitleId}-${id}`; + const blogPostToBase64 = await objectToBase64(postObject); + let description = ''; + const findText = newPostContent.find((data) => data?.type === 'editor'); if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) + description = extractTextFromSlate(findText?.content); + description = description.slice(0, 180); } let requestBody: any = { @@ -476,54 +475,48 @@ export const CreatePostBuilder = ({ title: title, description: params?.description || description, category: params?.category || '', - identifier: identifier - } + identifier: identifier, + }; - const formattedTags: { [key: string]: string } = {} - let tag4 = '' - let tag5 = '' + const formattedTags: { [key: string]: string } = {}; + let tag4 = ''; + let tag5 = ''; if (params?.tags) { params.tags.slice(0, 3).forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); } - const findVideo: any = postObject?.postContent?.find( - (data: any) => data?.type === 'video' - ) - const findAudio: any = postObject?.postContent?.find( - (data: any) => data?.type === 'audio' - ) - const findImage: any = postObject?.postContent?.find( - (data: any) => data?.type === 'image' - ) + const findVideo: any = postObject?.postContent?.find((data: any) => data?.type === 'video'); + const findAudio: any = postObject?.postContent?.find((data: any) => data?.type === 'audio'); + const findImage: any = postObject?.postContent?.find((data: any) => data?.type === 'image'); - const tag5Array = ['t'] - if (findVideo) tag5Array.push('v') - if (findAudio) tag5Array.push('a') + const tag5Array = ['t']; + if (findVideo) tag5Array.push('v'); + if (findAudio) tag5Array.push('a'); if (findImage) { - tag5Array.push('i') + tag5Array.push('i'); const imageElement = document.querySelector( - `[id="${findImage.id}"] img` - ) as HTMLImageElement | null + `[id="${findImage.id}"] img`, + ) as HTMLImageElement | null; if (imageElement) { - tag4 = `v1.${imageElement?.width}x${imageElement?.height}` + tag4 = `v1.${imageElement?.width}x${imageElement?.height}`; } else { - tag4 = 'v1.0x0' + tag4 = 'v1.0x0'; } } if (!findImage) { - tag4 = 'v1.0x0' + tag4 = 'v1.0x0'; } - tag5 = tag5Array.join(', ') + tag5 = tag5Array.join(', '); requestBody = { ...requestBody, ...formattedTags, tag4: tag4, - tag5: tag5 - } + tag5: tag5, + }; - const resourceResponse = await qortalRequest(requestBody) + const resourceResponse = await qortalRequest(requestBody); const postobj: any = { ...postObject, @@ -533,112 +526,112 @@ export const CreatePostBuilder = ({ tags: [...(params?.tags || []), tag4, tag5], id: identifier, user: name, - postImage: findImage ? findImage?.content?.image : '' - } + postImage: findImage ? findImage?.content?.image : '', + }; - const withoutImage = { ...postobj } - delete withoutImage.postImage - dispatch(addPostToBeginning(withoutImage)) - dispatch(addToHashMap(postobj)) + const withoutImage = { ...postobj }; + delete withoutImage.postImage; + dispatch(addPostToBeginning(withoutImage)); + dispatch(addToHashMap(postobj)); dispatch( setNotification({ msg: 'Blog post successfully published', - alertType: 'success' - }) - ) - const str = identifier - const arr = str.split('-post-') - const str1 = arr[0] - const str2 = arr[1] - const blogId = removePrefix(str1) - navigate(`/${name}/${blogId}/${str2}`) + alertType: 'success', + }), + ); + const str = identifier; + const arr = str.split('-post-'); + const str1 = arr[0]; + const str2 = arr[1]; + const blogId = removePrefix(str1); + navigate(`/${name}/${blogId}/${str2}`); } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to publish post', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to publish post', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to publish post', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); - throw new Error('Failed to publish post') + throw new Error('Failed to publish post'); } } async function updateQDNResource(params: any) { - if (!blogContentForEdit || !postIdForEdit || !blogMetadataForEdit) return - let address - let name - let errorMsg = '' + if (!blogContentForEdit || !postIdForEdit || !blogMetadataForEdit) return; + let address; + let name; + let errorMsg = ''; - address = user?.address - name = user?.name || '' + address = user?.address; + name = user?.name || ''; - const missingFields = [] + const missingFields = []; if (!address) { - errorMsg = "Cannot post: your address isn't available" + errorMsg = "Cannot post: your address isn't available"; } if (!name) { - errorMsg = 'Cannot post without a name' + errorMsg = 'Cannot post without a name'; } - if (!title) missingFields.push('title') + if (!title) missingFields.push('title'); if (missingFields.length > 0) { - const missingFieldsString = missingFields.join(', ') - const errMsg = `Missing: ${missingFieldsString}` - errorMsg = errMsg + const missingFieldsString = missingFields.join(', '); + const errMsg = `Missing: ${missingFieldsString}`; + errorMsg = errMsg; } if (newPostContent.length === 0) { - errorMsg = 'Your post has no content' + errorMsg = 'Your post has no content'; } if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.' + errorMsg = 'Cannot publish without first creating a blog.'; } if (errorMsg) { dispatch( setNotification({ msg: errorMsg, - alertType: 'error' - }) - ) - throw new Error(errorMsg) + alertType: 'error', + }), + ); + throw new Error(errorMsg); } const layoutGeneralSettings = { padding: paddingValue ?? 0, - blogPostType: 'builder' - } + blogPostType: 'builder', + }; const postObject = { ...blogContentForEdit, title, postContent: newPostContent, layouts, - layoutGeneralSettings - } + layoutGeneralSettings, + }; try { - if (!currentBlog) return + if (!currentBlog) return; - const identifier = postIdForEdit - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') + const identifier = postIdForEdit; + const blogPostToBase64 = await objectToBase64(postObject); + let description = ''; + const findText = newPostContent.find((data) => data?.type === 'editor'); if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) + description = extractTextFromSlate(findText?.content); + description = description.slice(0, 180); } let requestBody: any = { @@ -649,54 +642,48 @@ export const CreatePostBuilder = ({ title: title, description: params?.description || description, category: params?.category || '', - identifier: identifier - } + identifier: identifier, + }; - const formattedTags: { [key: string]: string } = {} - let tag4 = '' - let tag5 = '' + const formattedTags: { [key: string]: string } = {}; + let tag4 = ''; + let tag5 = ''; if (params?.tags) { params.tags.slice(0, 3).forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); } - const findVideo: any = postObject?.postContent?.find( - (data: any) => data?.type === 'video' - ) - const findAudio: any = postObject?.postContent?.find( - (data: any) => data?.type === 'audio' - ) - const findImage: any = postObject?.postContent?.find( - (data: any) => data?.type === 'image' - ) + const findVideo: any = postObject?.postContent?.find((data: any) => data?.type === 'video'); + const findAudio: any = postObject?.postContent?.find((data: any) => data?.type === 'audio'); + const findImage: any = postObject?.postContent?.find((data: any) => data?.type === 'image'); - const tag5Array = ['t'] - if (findVideo) tag5Array.push('v') - if (findAudio) tag5Array.push('a') + const tag5Array = ['t']; + if (findVideo) tag5Array.push('v'); + if (findAudio) tag5Array.push('a'); if (findImage) { - tag5Array.push('i') + tag5Array.push('i'); const imageElement = document.querySelector( - `[id="${findImage.id}"] img` - ) as HTMLImageElement | null + `[id="${findImage.id}"] img`, + ) as HTMLImageElement | null; if (imageElement) { - tag4 = `v1.${imageElement?.width}x${imageElement?.height}` + tag4 = `v1.${imageElement?.width}x${imageElement?.height}`; } else { - tag4 = 'v1.0x0' + tag4 = 'v1.0x0'; } } if (!findImage) { - tag4 = 'v1.0x0' + tag4 = 'v1.0x0'; } - tag5 = tag5Array.join(', ') + tag5 = tag5Array.join(', '); requestBody = { ...requestBody, ...formattedTags, tag4: tag4, - tag5: tag5 - } + tag5: tag5, + }; - const resourceResponse = await qortalRequest(requestBody) + const resourceResponse = await qortalRequest(requestBody); const postobj = { ...postObject, title: title, @@ -704,40 +691,40 @@ export const CreatePostBuilder = ({ category: params?.category || '', tags: [...(params?.tags || []), tag4, tag5], id: identifier, - user: name - } - const withoutImage = { ...postobj } - delete withoutImage.postImage - dispatch(updatePost(withoutImage)) - dispatch(updateInHashMap(postobj)) + user: name, + }; + const withoutImage = { ...postobj }; + delete withoutImage.postImage; + dispatch(updatePost(withoutImage)); + dispatch(updateInHashMap(postobj)); dispatch( setNotification({ msg: 'Blog post successfully updated', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); } catch (error: any) { - let notificationObj = null + let notificationObj = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to update post', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to update post', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to update post', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); - throw new Error('Failed to update post') + throw new Error('Failed to update post'); } } const addImage = (base64: string) => { @@ -746,31 +733,24 @@ export const CreatePostBuilder = ({ version: 1, content: { image: base64, - caption: '' + caption: '', }, - id: uid() - } - setNewPostContent((prev) => [...prev, section]) - } + id: uid(), + }; + setNewPostContent((prev) => [...prev, section]); + }; interface IaddVideo { - name: string - identifier: string - service: string - title: string - description: string - mimeType?: string - poster?: string + name: string; + identifier: string; + service: string; + title: string; + description: string; + mimeType?: string; + poster?: string; } - const addVideo = ({ - name, - identifier, - service, - title, - description, - poster - }: IaddVideo) => { + const addVideo = ({ name, identifier, service, title, description, poster }: IaddVideo) => { const section = { type: 'video', version: 1, @@ -780,20 +760,14 @@ export const CreatePostBuilder = ({ service: service, title, description, - ...(poster ? { poster } : {}) + ...(poster ? { poster } : {}), }, - id: uid() - } - setNewPostContent((prev) => [...prev, section]) - } + id: uid(), + }; + setNewPostContent((prev) => [...prev, section]); + }; - const addAudio = ({ - name, - identifier, - service, - title, - description - }: IaddVideo) => { + const addAudio = ({ name, identifier, service, title, description }: IaddVideo) => { const section = { type: 'audio', version: 1, @@ -802,23 +776,16 @@ export const CreatePostBuilder = ({ identifier: identifier, service: service, title, - description + description, }, - id: uid() - } - setNewPostContent((prev) => [...prev, section]) - } + id: uid(), + }; + setNewPostContent((prev) => [...prev, section]); + }; - const addFile = ({ - name, - identifier, - service, - title, - description, - mimeType - }: IaddVideo) => { - const id = uid() - const type = 'file' + const addFile = ({ name, identifier, service, title, description, mimeType }: IaddVideo) => { + const id = uid(); + const type = 'file'; const section = { type, version: 1, @@ -828,75 +795,71 @@ export const CreatePostBuilder = ({ service: service, title, description, - mimeType + mimeType, }, - id - } - setNewPostContent((prev) => [...prev, section]) - } + id, + }; + setNewPostContent((prev) => [...prev, section]); + }; const addSection = () => { - addPostSection(value) - setValue(initialValue) - } + addPostSection(value); + setValue(initialValue); + }; const removeSection = (section: any) => { - const newContent = newPostContent.filter((s) => s.id !== section.id) - setNewPostContent(newContent) - } + const newContent = newPostContent.filter((s) => s.id !== section.id); + setNewPostContent(newContent); + }; const editImage = (base64: string, section: any) => { const newSection = { ...section, content: { image: base64, - caption: section.content.caption - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + caption: section.content.caption, + }, + }; + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection + const copyNewPostContent = [...newPostContent]; + copyNewPostContent[findSectionIndex] = newSection; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - } + }; const editVideo = ( { name, identifier, service, description, title, poster }: IaddVideo, - section: any + section: any, ) => { const nextContent: any = { name, identifier, service, description, - title - } + title, + }; if (poster === undefined) { if (typeof section.content?.poster !== 'undefined') { - nextContent.poster = section.content.poster + nextContent.poster = section.content.poster; } } else if (poster) { - nextContent.poster = poster + nextContent.poster = poster; } const newSection = { ...section, - content: nextContent - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + content: nextContent, + }; + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection + const copyNewPostContent = [...newPostContent]; + copyNewPostContent[findSectionIndex] = newSection; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - } + }; const editAudio = ( { name, identifier, service, description, title }: IaddVideo, - section: any + section: any, ) => { const newSection = { ...section, @@ -905,54 +868,53 @@ export const CreatePostBuilder = ({ identifier: identifier, service: service, description, - title - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + title, + }, + }; + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection + const copyNewPostContent = [...newPostContent]; + copyNewPostContent[findSectionIndex] = newSection; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - } + }; const editSection = (section: any) => { - setIsOpenEditTextModal(true) - setEditingSection(section) - setValue(section.content) - } + setIsOpenEditTextModal(true); + setEditingSection(section); + setValue(section.content); + }; const editPostSection = React.useCallback( (content: any, section: any) => { - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] + const copyNewPostContent = [...newPostContent]; copyNewPostContent[findSectionIndex] = { ...section, - content - } + content, + }; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - setEditingSection(null) - setIsOpenEditTextModal(false) + setEditingSection(null); + setIsOpenEditTextModal(false); }, - [newPostContent] - ) + [newPostContent], + ); - const onSelectVideo = React.useCallback((video: any) => { - setCoverPickerMode('add') - setCoverPickerTarget(null) - setPendingVideo(video) - setSelectedPoster('') - setCoverTab('upload') - setCoverPickerOpen(true) - fetchExistingImages() - }, [fetchExistingImages]) + const onSelectVideo = React.useCallback( + (video: any) => { + setCoverPickerMode('add'); + setCoverPickerTarget(null); + setPendingVideo(video); + setSelectedPoster(''); + setCoverTab('upload'); + setCoverPickerOpen(true); + fetchExistingImages(); + }, + [fetchExistingImages], + ); const onSelectAudio = React.useCallback((video: any) => { addAudio({ @@ -960,13 +922,13 @@ export const CreatePostBuilder = ({ identifier: video.identifier, service: video.service, title: video?.metadata?.title, - description: video?.metadata?.description - }) - }, []) + description: video?.metadata?.description, + }); + }, []); const onBreakpointChange = (newBreakpoint: any) => { - setCurrentBreakpoint(newBreakpoint) - } + setCurrentBreakpoint(newBreakpoint); + }; const onSelectFile = React.useCallback((video: any) => { addFile({ @@ -975,45 +937,44 @@ export const CreatePostBuilder = ({ service: video.service, title: video?.metadata?.title, description: video?.metadata?.description, - mimeType: video?.metadata?.mimeType - }) - }, []) + mimeType: video?.metadata?.mimeType, + }); + }, []); const onSelectPoll = React.useCallback((pollName: string) => { - const section = { type: 'poll', version: 1, content: { pollName }, id: uid() } - setNewPostContent((prev) => [...prev, section]) - }, []) + const section = { type: 'poll', version: 1, content: { pollName }, id: uid() }; + setNewPostContent((prev) => [...prev, section]); + }, []); const closeAddTextModal = React.useCallback(() => { - setIsOpenAddTextModal(false) - }, []) + setIsOpenAddTextModal(false); + }, []); const closeEditTextModal = React.useCallback(() => { - setIsOpenEditTextModal(false) - setEditingSection(null) - }, []) + setIsOpenEditTextModal(false); + setEditingSection(null); + }, []); const onResizeStop = (layout: any, layoutItem: any) => { - setCount((prev) => prev + 1) - } + setCount((prev) => prev + 1); + }; const handleResize = () => { - setCount((prev) => prev + 1) - } + setCount((prev) => prev + 1); + }; React.useEffect(() => { - window.addEventListener('resize', handleResize) + window.addEventListener('resize', handleResize); return () => { - window.removeEventListener('resize', handleResize) - } - }, []) + window.removeEventListener('resize', handleResize); + }; + }, []); - const gridItemCount = - currentBreakpoint === 'md' ? 4 : currentBreakpoint === 'sm' ? 3 : 1 + const gridItemCount = currentBreakpoint === 'md' ? 4 : currentBreakpoint === 'sm' ? 3 : 1; const addNav = () => { - setIsEditNavOpen(true) - } + setIsEditNavOpen(true); + }; return ( <>
{Array.from({ length: gridItemCount }, (_, i) => ( @@ -1101,26 +1062,22 @@ export const CreatePostBuilder = ({ count={count} padding={paddingValue} > - {editingSection && - editingSection.id === section.id ? null : ( + {editingSection && editingSection.id === section.id ? null : ( - + removeSection(section)} sx={{ cursor: 'pointer', height: '18px', - width: 'auto' + width: 'auto', }} /> @@ -1136,7 +1093,7 @@ export const CreatePostBuilder = ({ )}
- ) + ); } if (section.type === 'image') { return ( @@ -1154,20 +1111,17 @@ export const CreatePostBuilder = ({ sx={{ position: 'relative', width: '100%', - height: '100%' + height: '100%', }} > - + removeSection(section)} sx={{ cursor: 'pointer', height: '18px', - width: 'auto' + width: 'auto', }} /> - ) + ); } if (section.type === 'video') { @@ -1197,7 +1151,7 @@ export const CreatePostBuilder = ({ sx={{ position: 'relative', width: '100%', - height: '100%' + height: '100%', }} > { - setCoverPickerMode('edit') - setCoverPickerTarget(section) - setPendingVideo(null) - setSelectedPoster(section.content?.poster || '') - setCoverTab('upload') - setCoverPickerOpen(true) - fetchExistingImages() + setCoverPickerMode('edit'); + setCoverPickerTarget(section); + setPendingVideo(null); + setSelectedPoster(section.content?.poster || ''); + setCoverTab('upload'); + setCoverPickerOpen(true); + fetchExistingImages(); }} sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} /> @@ -1248,7 +1202,7 @@ export const CreatePostBuilder = ({ - ) + ); } if (section.type === 'audio') { return ( @@ -1265,7 +1219,7 @@ export const CreatePostBuilder = ({ sx={{ position: 'relative', width: '100%', - height: '100%' + height: '100%', }} > @@ -1304,7 +1258,7 @@ export const CreatePostBuilder = ({ - ) + ); } if (section.type === 'file') { return ( @@ -1321,7 +1275,7 @@ export const CreatePostBuilder = ({ sx={{ position: 'relative', width: '100%', - height: '100%' + height: '100%', }} > - ) + ); } if (section.type === 'poll') { return ( @@ -1362,15 +1316,22 @@ export const CreatePostBuilder = ({ > {/* lightweight preview */} - Poll: {section.content?.pollName} - The interactive poll will appear in the published post. + + Poll: {section.content?.pollName} + + + The interactive poll will appear in the published post. + - removeSection(section)} sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} /> + removeSection(section)} + sx={{ cursor: 'pointer', height: '18px', width: 'auto' }} + /> - ) + ); } })} @@ -1383,12 +1344,12 @@ export const CreatePostBuilder = ({ zIndex: 15, background: 'deepskyblue', padding: '10px', - borderRadius: '5px' + borderRadius: '5px', }} > + + - ) -} + ); +}; diff --git a/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx b/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx index 2f95659..ab0876d 100644 --- a/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx +++ b/src/pages/CreatePost/components/Toolbar/EditorToolbar.tsx @@ -1,42 +1,39 @@ -import React from 'react' -import TextFieldsIcon from '@mui/icons-material/TextFields' -import Slider from '@mui/material/Slider' -import { AudioPanel } from '../../../../components/common/AudioPanel' -import { Box, Toolbar, AppBar, useTheme } from '@mui/material' -import { styled } from '@mui/system' -import { ImagePanel } from '../../../../components/common/ImagePanel' -import { PollPanel } from '../../../../components/common/PollPanel' -import { VideoPanel } from '../../../../components/common/VideoPanel' -import MenuOpenIcon from '@mui/icons-material/MenuOpen' -import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded' -import Tooltip from '@mui/material/Tooltip' -import { FilePanel } from '../../../../components/common/FilePanel' +import React from 'react'; +import TextFieldsIcon from '@mui/icons-material/TextFields'; +import Slider from '@mui/material/Slider'; +import { AudioPanel } from '../../../../components/common/AudioPanel'; +import { Box, Toolbar, AppBar, useTheme } from '@mui/material'; +import { styled } from '@mui/system'; +import { ImagePanel } from '../../../../components/common/ImagePanel'; +import { PollPanel } from '../../../../components/common/PollPanel'; +import { VideoPanel } from '../../../../components/common/VideoPanel'; +import MenuOpenIcon from '@mui/icons-material/MenuOpen'; +import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded'; +import Tooltip from '@mui/material/Tooltip'; +import { FilePanel } from '../../../../components/common/FilePanel'; const CustomToolbar = styled(Toolbar)({ display: 'flex', justifyContent: 'space-between', - alignItems: 'center' -}) + alignItems: 'center', +}); const CustomAppBar = styled(AppBar)(({ theme }) => ({ - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.background.default - : '#19191b' -})) + backgroundColor: theme.palette.mode === 'light' ? theme.palette.background.default : '#19191b', +})); interface IEditorToolbar { - setIsOpenAddTextModal: (val: boolean) => void - addImage: (base64: string) => void - onSelectVideo: (video: any) => void - onSelectAudio: (audio: any) => void - onSelectFile: (file: any) => void - onSelectPoll: (pollName: string) => void - paddingValue: number - onChangePadding: (padding: number) => void - isMinimal?: boolean - addNav?: () => void - switchType?: () => void + setIsOpenAddTextModal: (val: boolean) => void; + addImage: (base64: string) => void; + onSelectVideo: (video: any) => void; + onSelectAudio: (audio: any) => void; + onSelectFile: (file: any) => void; + onSelectPoll: (pollName: string) => void; + paddingValue: number; + onChangePadding: (padding: number) => void; + isMinimal?: boolean; + addNav?: () => void; + switchType?: () => void; } export const EditorToolbar = ({ @@ -50,9 +47,9 @@ export const EditorToolbar = ({ onChangePadding, isMinimal = false, addNav, - switchType + switchType, }: IEditorToolbar) => { - const theme = useTheme() + const theme = useTheme(); return ( @@ -62,13 +59,13 @@ export const EditorToolbar = ({ justifyContent: 'space-between', width: '100%', flexWrap: 'wrap', - alignItems: 'center' + alignItems: 'center', }} > @@ -77,7 +74,7 @@ export const EditorToolbar = ({ sx={{ cursor: 'pointer', width: 'auto', - height: '30px' + height: '30px', }} /> @@ -91,7 +88,7 @@ export const EditorToolbar = ({ {!isMinimal && ( @@ -100,9 +97,7 @@ export const EditorToolbar = ({ - onChangePadding(event.target.value) - } + onChange={(event: any) => onChangePadding(event.target.value)} defaultValue={5} aria-label="Default" valueLabelDisplay="auto" @@ -110,7 +105,7 @@ export const EditorToolbar = ({ max={40} sx={{ color: theme.palette.text.primary, - width: '100px' + width: '100px', }} /> @@ -123,7 +118,7 @@ export const EditorToolbar = ({ sx={{ cursor: 'pointer', width: 'auto', - height: '30px' + height: '30px', }} /> @@ -135,7 +130,7 @@ export const EditorToolbar = ({ sx={{ cursor: 'pointer', width: 'auto', - height: '30px' + height: '30px', }} /> @@ -144,5 +139,5 @@ export const EditorToolbar = ({ - ) -} + ); +}; diff --git a/src/pages/EditPost/EditPost.tsx b/src/pages/EditPost/EditPost.tsx index ba7a173..0f88787 100644 --- a/src/pages/EditPost/EditPost.tsx +++ b/src/pages/EditPost/EditPost.tsx @@ -1,33 +1,31 @@ -import React from 'react' -import { useParams } from 'react-router-dom' -import BlogEditor from '../../components/editor/BlogEditor' -import ShortUniqueId from 'short-unique-id' -import { Button, TextField } from '@mui/material' -import ReadOnlySlate from '../../components/editor/ReadOnlySlate' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../state/store' -import { Box } from '@mui/material' -import { ImagePanel } from '../../components/common/ImagePanel' -import { checkStructure } from '../../utils/checkStructure' -import { BlogContent } from '../../interfaces/interfaces' -import PostAddIcon from '@mui/icons-material/PostAdd' -import RemoveCircleIcon from '@mui/icons-material/RemoveCircle' -import EditIcon from '@mui/icons-material/Edit' -import { createEditor, Descendant, Editor, Transforms } from 'slate' -import { styled } from '@mui/system' -import { setIsLoadingGlobal } from '../../state/features/globalSlice' -import { extractTextFromSlate } from '../../utils/extractTextFromSlate' -import { VideoContent } from '../../components/common/VideoContent' -import { VideoPanel } from '../../components/common/VideoPanel' +import React from 'react'; +import { useParams } from 'react-router-dom'; +import BlogEditor from '../../components/editor/BlogEditor'; +import ShortUniqueId from 'short-unique-id'; +import { Button, TextField } from '@mui/material'; +import ReadOnlySlate from '../../components/editor/ReadOnlySlate'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '../../state/store'; +import { Box } from '@mui/material'; +import { ImagePanel } from '../../components/common/ImagePanel'; +import { checkStructure } from '../../utils/checkStructure'; +import { BlogContent } from '../../interfaces/interfaces'; +import PostAddIcon from '@mui/icons-material/PostAdd'; +import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; +import EditIcon from '@mui/icons-material/Edit'; +import { createEditor, Descendant, Editor, Transforms } from 'slate'; +import { styled } from '@mui/system'; +import { setIsLoadingGlobal } from '../../state/features/globalSlice'; +import { extractTextFromSlate } from '../../utils/extractTextFromSlate'; +import { VideoContent } from '../../components/common/VideoContent'; +import { VideoPanel } from '../../components/common/VideoPanel'; const initialValue: Descendant[] = [ { type: 'paragraph', - children: [ - { text: "Start writing your blog post... Don't forget to add a title :)" } - ] - } -] + children: [{ text: "Start writing your blog post... Don't forget to add a title :)" }], + }, +]; const BlogTitleInput = styled(TextField)(({ theme }) => ({ '& .MuiInputBase-input': { @@ -35,95 +33,88 @@ const BlogTitleInput = styled(TextField)(({ theme }) => ({ height: '28px', '&::placeholder': { fontSize: '28px', - color: theme.palette.text.secondary - } + color: theme.palette.text.secondary, + }, }, '& .MuiInputLabel-root': { - fontSize: '28px' - } -})) + fontSize: '28px', + }, +})); interface IaddVideo { - name: string - identifier: string - service: string - title: string - description: string + name: string; + identifier: string; + service: string; + title: string; + description: string; } -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); export const EditPost = () => { - const { user: username, postId } = useParams() + const { user: username, postId } = useParams(); - const { user } = useSelector((state: RootState) => state.auth) + const { user } = useSelector((state: RootState) => state.auth); - const [newPostContent, setNewPostContent] = React.useState([]) - const [blogInfo, setBlogInfo] = React.useState(null) - const [editingSection, setEditingSection] = React.useState(null) - const [value, setValue] = React.useState(initialValue) - const [value2, setValue2] = React.useState(initialValue) - const [title, setTitle] = React.useState('') - const dispatch = useDispatch() + const [newPostContent, setNewPostContent] = React.useState([]); + const [blogInfo, setBlogInfo] = React.useState(null); + const [editingSection, setEditingSection] = React.useState(null); + const [value, setValue] = React.useState(initialValue); + const [value2, setValue2] = React.useState(initialValue); + const [title, setTitle] = React.useState(''); + const dispatch = useDispatch(); const addPostSection = React.useCallback((content: any) => { const section = { type: 'editor', version: 1, content, - id: uid() - } + id: uid(), + }; - setNewPostContent((prev) => [...prev, section]) - }, []) + setNewPostContent((prev) => [...prev, section]); + }, []); const editPostSection = React.useCallback( (content: any, section: any) => { - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] + const copyNewPostContent = [...newPostContent]; copyNewPostContent[findSectionIndex] = { ...section, - content - } + content, + }; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - setEditingSection(null) + setEditingSection(null); }, - [newPostContent] - ) + [newPostContent], + ); function objectToBase64(obj: any) { // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) + const jsonString = JSON.stringify(obj); // Step 2: Create a Blob from the JSON string - const blob = new Blob([jsonString], { type: 'application/json' }) + const blob = new Blob([jsonString], { type: 'application/json' }); // Step 3: Create a FileReader to read the Blob as a base64-encoded string return new Promise((resolve, reject) => { - const reader = new FileReader() + const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === 'string') { // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace( - 'data:application/json;base64,', - '' - ) - resolve(base64) + const base64 = reader.result.replace('data:application/json;base64,', ''); + resolve(base64); } else { - reject( - new Error('Failed to read the Blob as a base64-encoded string') - ) + reject(new Error('Failed to read the Blob as a base64-encoded string')); } - } + }; reader.onerror = () => { - reject(reader.error) - } - reader.readAsDataURL(blob) - }) + reject(reader.error); + }; + reader.readAsDataURL(blob); + }); } const addImage = (base64: string) => { @@ -132,51 +123,51 @@ export const EditPost = () => { version: 1, content: { image: base64, - caption: '' + caption: '', }, - id: uid() - } + id: uid(), + }; - setNewPostContent((prev) => [...prev, section]) - } + setNewPostContent((prev) => [...prev, section]); + }; async function getNameInfo(address: string) { - const response = await fetch('/names/address/' + address) - const nameData = await response.json() + const response = await fetch('/names/address/' + address); + const nameData = await response.json(); if (nameData?.length > 0) { - return nameData[0].name + return nameData[0].name; } else { - return '' + return ''; } } async function publishQDNResource() { - let address - let name + let address; + let name; try { - if (!user || !user.address) return - address = user.address + if (!user || !user.address) return; + address = user.address; } catch (error) {} - if (!address) return + if (!address) return; try { - name = await getNameInfo(address) + name = await getNameInfo(address); } catch (error) {} - if (!name) return - if (!blogInfo) return + if (!name) return; + if (!blogInfo) return; try { const postObject = { ...blogInfo, title, - postContent: newPostContent - } - const blogPostToBase64 = await objectToBase64(postObject) - let description = '' - const findText = newPostContent.find((data) => data?.type === 'editor') + postContent: newPostContent, + }; + const blogPostToBase64 = await objectToBase64(postObject); + let description = ''; + const findText = newPostContent.find((data) => data?.type === 'editor'); if (findText && findText.content) { - description = extractTextFromSlate(findText?.content) - description = description.slice(0, 180) + description = extractTextFromSlate(findText?.content); + description = description.slice(0, 180); } const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', @@ -188,74 +179,72 @@ export const EditPost = () => { category: 'TECHNOLOGY', tags: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'], metaData: 'description=destriptontest&category=catTest', - identifier: postId - }) + identifier: postId, + }); } catch (error) { - console.error(error) + console.error(error); } } const addSection = () => { - addPostSection(value2) - } + addPostSection(value2); + }; const getBlogPost = React.useCallback(async () => { try { - dispatch(setIsLoadingGlobal(true)) - const url = `/arbitrary/BLOG_POST/${username}/${postId}` + dispatch(setIsLoadingGlobal(true)); + const url = `/arbitrary/BLOG_POST/${username}/${postId}`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const responseData = await response.json() + const responseData = await response.json(); if (checkStructure(responseData)) { - setNewPostContent(responseData.postContent) - setTitle(responseData?.title || '') - setBlogInfo(responseData) + setNewPostContent(responseData.postContent); + setTitle(responseData?.title || ''); + setBlogInfo(responseData); } } catch (error) { } finally { - dispatch(setIsLoadingGlobal(false)) + dispatch(setIsLoadingGlobal(false)); } - }, [user, postId]) + }, [user, postId]); React.useEffect(() => { - getBlogPost() - }, []) + getBlogPost(); + }, []); const editSection = (section: any) => { - setEditingSection(section) - setValue(section.content) - } + setEditingSection(section); + setValue(section.content); + }; const removeSection = (section: any) => { - const newContent = newPostContent.filter((s) => s.id !== section.id) - setNewPostContent(newContent) - } + const newContent = newPostContent.filter((s) => s.id !== section.id); + setNewPostContent(newContent); + }; const editImage = (base64: string, section: any) => { const newSection = { ...section, content: { image: base64, - caption: section.content.caption - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + caption: section.content.caption, + }, + }; + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection + const copyNewPostContent = [...newPostContent]; + copyNewPostContent[findSectionIndex] = newSection; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - } + }; const editVideo = ( { name, identifier, service, description, title }: IaddVideo, - section: any + section: any, ) => { const newSection = { ...section, @@ -264,32 +253,30 @@ export const EditPost = () => { identifier: identifier, service: service, description, - title - } - } - const findSectionIndex = newPostContent.findIndex( - (s) => s.id === section.id - ) + title, + }, + }; + const findSectionIndex = newPostContent.findIndex((s) => s.id === section.id); if (findSectionIndex !== -1) { - const copyNewPostContent = [...newPostContent] - copyNewPostContent[findSectionIndex] = newSection + const copyNewPostContent = [...newPostContent]; + copyNewPostContent[findSectionIndex] = newSection; - setNewPostContent(copyNewPostContent) + setNewPostContent(copyNewPostContent); } - } + }; return ( { ) : ( @@ -334,19 +321,19 @@ export const EditPost = () => { gap: 2, background: 'white', padding: '5px', - borderRadius: '5px' + borderRadius: '5px', }} > removeSection(section)} sx={{ - cursor: 'pointer' + cursor: 'pointer', }} /> editSection(section)} sx={{ - cursor: 'pointer' + cursor: 'pointer', }} /> @@ -357,18 +344,16 @@ export const EditPost = () => { sx={{ display: 'flex', width: '100%', - justifyContent: 'flex-end' + justifyContent: 'flex-end', }} > - + ) : ( <> )} - ) + ); } if (section.type === 'image') { return ( @@ -382,14 +367,14 @@ export const EditPost = () => { ) : ( { gap: 2, background: 'white', padding: '5px', - borderRadius: '5px' + borderRadius: '5px', }} > removeSection(section)} sx={{ - cursor: 'pointer' + cursor: 'pointer', }} /> { <> )} - ) + ); } if (section.type === 'video') { return ( @@ -443,16 +428,16 @@ export const EditPost = () => { identifier: video.identifier, service: video.service, title: video?.metadata?.title, - description: video?.metadata?.description + description: video?.metadata?.description, }, - section + section, ) } /> ) : ( { gap: 2, background: 'white', padding: '5px', - borderRadius: '5px' + borderRadius: '5px', }} > removeSection(section)} sx={{ - cursor: 'pointer' + cursor: 'pointer', }} /> { identifier: video.identifier, service: video.service, title: video?.metadata?.title, - description: video?.metadata?.description + description: video?.metadata?.description, }, - section + section, ) } /> @@ -505,18 +490,14 @@ export const EditPost = () => { <> )} - ) + ); } })} - + { sx={{ cursor: 'pointer', width: '50px', - height: '50px' + height: '50px', }} /> - + { zIndex: 15, background: 'deepskyblue', padding: '10px', - borderRadius: '5px' + borderRadius: '5px', }} > - ) -} + ); +}; diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index 8ab2ced..9ad3d3f 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,7 +1,5 @@ -import React from 'react' +import React from 'react'; export const Home = () => { - return ( -
Home
- ) -} + return
Home
; +}; diff --git a/src/state/features/authSlice.ts b/src/state/features/authSlice.ts index cd73e3a..3578ccc 100644 --- a/src/state/features/authSlice.ts +++ b/src/state/features/authSlice.ts @@ -1,6 +1,5 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; - interface AuthState { user: { address: string; @@ -9,7 +8,7 @@ interface AuthState { } | null; } const initialState: AuthState = { - user: null + user: null, }; export const authSlice = createSlice({ @@ -24,4 +23,4 @@ export const authSlice = createSlice({ export const { addUser } = authSlice.actions; -export default authSlice.reducer; \ No newline at end of file +export default authSlice.reducer; diff --git a/src/state/features/blogSlice.ts b/src/state/features/blogSlice.ts index d6a697f..f3e3fdf 100644 --- a/src/state/features/blogSlice.ts +++ b/src/state/features/blogSlice.ts @@ -1,23 +1,23 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; -import localForage from 'localforage' -import { RootState } from '../store' +import localForage from 'localforage'; +import { RootState } from '../store'; const favoritesLocal = localForage.createInstance({ - name: 'q-blog-favorites' -}) -const instanceCache = new Map() + name: 'q-blog-favorites', +}); +const instanceCache = new Map(); interface GlobalState { - posts: BlogPost[] - filteredPosts: BlogPost[] - hashMapPosts: Record - blogListPageNumber: number - favorites: any[] - favoritesLocal: any[] | null - subscriptions: any[] - subscriptionPosts: any[] - countNewPosts: number - isFiltering: boolean - filterValue: string + posts: BlogPost[]; + filteredPosts: BlogPost[]; + hashMapPosts: Record; + blogListPageNumber: number; + favorites: any[]; + favoritesLocal: any[] | null; + subscriptions: any[]; + subscriptionPosts: any[]; + countNewPosts: number; + isFiltering: boolean; + filterValue: string; } const initialState: GlobalState = { posts: [], @@ -30,278 +30,260 @@ const initialState: GlobalState = { subscriptionPosts: [], countNewPosts: 0, isFiltering: false, - filterValue: '' -} + filterValue: '', +}; export interface BlogPost { - title: string - description: string - createdAt: number | string - user: string - postImage?: string - id: string - category?: string - categoryName?: string - tags?: string[] - updated?: number | string - isValid?: boolean + title: string; + description: string; + createdAt: number | string; + user: string; + postImage?: string; + id: string; + category?: string; + categoryName?: string; + tags?: string[]; + updated?: number | string; + isValid?: boolean; } -export const removeFavorites = createAsyncThunk< - string, - string, - { state: RootState } ->('favorites/remove', async (id, thunkAPI) => { - const state = thunkAPI.getState() // Get the current state - const username = state?.auth?.user?.name // Access the user.name property - if (!username) return '' - let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`) - if (!favoritesLocal) { - favoritesLocal = localForage.createInstance({ - name: `q-blog-favorites-${username}` - }) - } - await favoritesLocal.removeItem(id) - return id -}) -export const removeFavoritesArray = createAsyncThunk< - string[], - string[], - { state: RootState } ->('favorites/remove', async (ids, thunkAPI) => { - const state = thunkAPI.getState() // Get the current state - const username = state?.auth?.user?.name // Access the user.name property - if (!username || !ids.length) return [] +export const removeFavorites = createAsyncThunk( + 'favorites/remove', + async (id, thunkAPI) => { + const state = thunkAPI.getState(); // Get the current state + const username = state?.auth?.user?.name; // Access the user.name property + if (!username) return ''; + let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`); + if (!favoritesLocal) { + favoritesLocal = localForage.createInstance({ + name: `q-blog-favorites-${username}`, + }); + } + await favoritesLocal.removeItem(id); + return id; + }, +); +export const removeFavoritesArray = createAsyncThunk( + 'favorites/remove', + async (ids, thunkAPI) => { + const state = thunkAPI.getState(); // Get the current state + const username = state?.auth?.user?.name; // Access the user.name property + if (!username || !ids.length) return []; - let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`) - if (!favoritesLocal) { - favoritesLocal = localForage.createInstance({ - name: `q-blog-favorites-${username}` - }) - } - if (!favoritesLocal) return [] + let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`); + if (!favoritesLocal) { + favoritesLocal = localForage.createInstance({ + name: `q-blog-favorites-${username}`, + }); + } + if (!favoritesLocal) return []; - // Remove all items in parallel - await Promise.all(ids.map((id) => favoritesLocal?.removeItem(id))) + // Remove all items in parallel + await Promise.all(ids.map((id) => favoritesLocal?.removeItem(id))); - return ids -}) + return ids; + }, +); -export const upsertFavorites = createAsyncThunk< - any[], - any[], - { state: RootState } ->('favorites/upsert', async (payload: any, thunkAPI) => { - const state = thunkAPI.getState() // Get the current state - const username = state?.auth?.user?.name // Access the user.name property - if (!username) return '' - let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`) - if (!favoritesLocal) { - favoritesLocal = localForage.createInstance({ - name: `q-blog-favorites-${username}` - }) - } - if (!favoritesLocal) { - return [] - } - payload.forEach((favorite: BlogPost) => { - favoritesLocal?.setItem(favorite.id, { - user: favorite.user, - id: favorite.id - }) - }) - return payload -}) +export const upsertFavorites = createAsyncThunk( + 'favorites/upsert', + async (payload: any, thunkAPI) => { + const state = thunkAPI.getState(); // Get the current state + const username = state?.auth?.user?.name; // Access the user.name property + if (!username) return ''; + let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`); + if (!favoritesLocal) { + favoritesLocal = localForage.createInstance({ + name: `q-blog-favorites-${username}`, + }); + } + if (!favoritesLocal) { + return []; + } + payload.forEach((favorite: BlogPost) => { + favoritesLocal?.setItem(favorite.id, { + user: favorite.user, + id: favorite.id, + }); + }); + return payload; + }, +); export const blogSlice = createSlice({ name: 'blog', initialState, reducers: { setBlogListPageNumber: (state, action) => { - state.blogListPageNumber = action.payload + state.blogListPageNumber = action.payload; }, setIsFiltering: (state, action) => { - state.isFiltering = action.payload + state.isFiltering = action.payload; }, setFilterValue: (state, action) => { - state.filterValue = action.payload + state.filterValue = action.payload; }, setCountNewPosts: (state, action) => { - state.countNewPosts = action.payload + state.countNewPosts = action.payload; }, addPosts: (state, action) => { - state.posts = action.payload + state.posts = action.payload; }, addFilteredPosts: (state, action) => { - state.filteredPosts = action.payload + state.filteredPosts = action.payload; }, addSubscriptions: (state, action) => { - state.subscriptions = action.payload + state.subscriptions = action.payload; }, removeSubscription: (state, action) => { - state.subscriptions = state.subscriptions.filter( - (sub) => sub !== action.payload - ) + state.subscriptions = state.subscriptions.filter((sub) => sub !== action.payload); state.subscriptionPosts = state.subscriptionPosts.filter( - (post) => post.user !== action.payload - ) + (post) => post.user !== action.payload, + ); }, addSubscription: (state, action) => { - state.subscriptions.push(action.payload) + state.subscriptions.push(action.payload); }, addFavorites: (state, action) => { - state.favoritesLocal = action.payload + state.favoritesLocal = action.payload; }, addFavorite: (state, action) => { - state.favorites = action.payload + state.favorites = action.payload; }, removePost: (state, action) => { - const idToDelete = action.payload - state.posts = state.posts.filter((item) => item.id !== idToDelete) - state.filteredPosts = state.filteredPosts.filter( - (item) => item.id !== idToDelete - ) + const idToDelete = action.payload; + state.posts = state.posts.filter((item) => item.id !== idToDelete); + state.filteredPosts = state.filteredPosts.filter((item) => item.id !== idToDelete); }, addPostToBeginning: (state, action) => { - state.posts.unshift(action.payload) + state.posts.unshift(action.payload); }, updatePost: (state, action) => { - const { id } = action.payload - const index = state.posts.findIndex((post) => post.id === id) + const { id } = action.payload; + const index = state.posts.findIndex((post) => post.id === id); if (index !== -1) { - state.posts[index] = { ...action.payload } + state.posts[index] = { ...action.payload }; } - const index2 = state.filteredPosts.findIndex((post) => post.id === id) + const index2 = state.filteredPosts.findIndex((post) => post.id === id); if (index2 !== -1) { - state.filteredPosts[index2] = { ...action.payload } + state.filteredPosts[index2] = { ...action.payload }; } }, addToHashMap: (state, action) => { - const post = action.payload - const fullId = - state.hashMapPosts[post.id + "-" + post.user] = post + const post = action.payload; + const fullId = (state.hashMapPosts[post.id + '-' + post.user] = post); }, updateInHashMap: (state, action) => { - const { id, user } = action.payload - const post = action.payload - state.hashMapPosts[id + '-' + user] = { ...post } + const { id, user } = action.payload; + const post = action.payload; + state.hashMapPosts[id + '-' + user] = { ...post }; }, removeFromHashMap: (state, action) => { - const idToDelete = action.payload - delete state.hashMapPosts[idToDelete] + const idToDelete = action.payload; + delete state.hashMapPosts[idToDelete]; }, addArrayToHashMap: (state, action) => { - const posts = action.payload + const posts = action.payload; posts.forEach((post: BlogPost) => { - state.hashMapPosts[post.id + "-" + post.user] = post - }) + state.hashMapPosts[post.id + '-' + post.user] = post; + }); }, upsertPosts: (state, action) => { action.payload.forEach((post: BlogPost) => { - const index = state.posts.findIndex((p) => p.id === post.id) + const index = state.posts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.posts[index] = post + state.posts[index] = post; } else { - state.posts.push(post) + state.posts.push(post); } - }) + }); }, upsertFilteredPosts: (state, action) => { action.payload.forEach((post: BlogPost) => { - const index = state.filteredPosts.findIndex((p) => p.id === post.id) + const index = state.filteredPosts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.filteredPosts[index] = post + state.filteredPosts[index] = post; } else { - state.filteredPosts.push(post) + state.filteredPosts.push(post); } - }) + }); }, upsertPostsBeginning: (state, action) => { action.payload.reverse().forEach((post: BlogPost) => { - const index = state.posts.findIndex((p) => p.id === post.id) + const index = state.posts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.posts[index] = post + state.posts[index] = post; } else { - state.posts.unshift(post) + state.posts.unshift(post); } - }) + }); }, upsertSubscriptionPosts: (state, action) => { action.payload.forEach((post: BlogPost) => { - const index = state.subscriptionPosts.findIndex((p) => p.id === post.id) + const index = state.subscriptionPosts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.subscriptionPosts[index] = post + state.subscriptionPosts[index] = post; } else { - state.subscriptionPosts.push(post) + state.subscriptionPosts.push(post); } - }) + }); }, populateFavorites: (state, action) => { action.payload.forEach((favorite: BlogPost) => { - const index = state.favorites.findIndex((p) => p.id === favorite.id) + const index = state.favorites.findIndex((p) => p.id === favorite.id); if (index !== -1) { - state.favorites[index] = favorite + state.favorites[index] = favorite; } else { - state.favorites.push(favorite) + state.favorites.push(favorite); } - }) + }); }, blockUser: (state, action) => { - const username = action.payload - state.posts = state.posts.filter((item) => item.user !== username) - state.filteredPosts = state.filteredPosts.filter( - (item) => item.user !== username - ) - state.favorites = state.favorites.filter((item) => item.user !== username) - state.subscriptionPosts = state.subscriptionPosts.filter( - (item) => item.user !== username - ) + const username = action.payload; + state.posts = state.posts.filter((item) => item.user !== username); + state.filteredPosts = state.filteredPosts.filter((item) => item.user !== username); + state.favorites = state.favorites.filter((item) => item.user !== username); + state.subscriptionPosts = state.subscriptionPosts.filter((item) => item.user !== username); if (state?.favoritesLocal) { const ids = state.favoritesLocal .filter((item) => item.user === username) - .map((user) => user?.user || '') - state.favoritesLocal = state.favoritesLocal.filter( - (item) => item.user !== username - ) + .map((user) => user?.user || ''); + state.favoritesLocal = state.favoritesLocal.filter((item) => item.user !== username); - removeFavoritesArray(ids) + removeFavoritesArray(ids); } - } + }, }, extraReducers: (builder) => { builder.addCase(removeFavorites.fulfilled, (state, action) => { - const idToDelete = action.payload - if (!idToDelete) return state - state.favorites = state.favorites.filter((item) => item.id !== idToDelete) - state.favoritesLocal = state?.favorites?.filter( - (item) => item.id !== idToDelete - ) + const idToDelete = action.payload; + if (!idToDelete) return state; + state.favorites = state.favorites.filter((item) => item.id !== idToDelete); + state.favoritesLocal = state?.favorites?.filter((item) => item.id !== idToDelete); }), builder.addCase(upsertFavorites.fulfilled, (state, action) => { - ;(action.payload || []).forEach((favorite: BlogPost) => { + (action.payload || []).forEach((favorite: BlogPost) => { favoritesLocal.setItem(favorite.id, { user: favorite.user, - id: favorite.id - }) - const index = state.favorites.findIndex((p) => p.id === favorite.id) + id: favorite.id, + }); + const index = state.favorites.findIndex((p) => p.id === favorite.id); if (index !== -1) { - state.favorites[index] = favorite + state.favorites[index] = favorite; } else { - state.favorites.push(favorite) + state.favorites.push(favorite); } - const index2 = state?.favoritesLocal?.findIndex( - (p) => p.id === favorite.id - ) + const index2 = state?.favoritesLocal?.findIndex((p) => p.id === favorite.id); if (index2 !== -1) { - state.favorites[index] = favorite + state.favorites[index] = favorite; } else { - state?.favoritesLocal?.push(favorite) + state?.favoritesLocal?.push(favorite); } - }) - }) - } -}) + }); + }); + }, +}); export const { addPosts, @@ -325,8 +307,7 @@ export const { upsertFilteredPosts, addFilteredPosts, setIsFiltering, - setFilterValue -} = blogSlice.actions - -export default blogSlice.reducer + setFilterValue, +} = blogSlice.actions; +export default blogSlice.reducer; diff --git a/src/state/features/globalSlice.ts b/src/state/features/globalSlice.ts index 4dcbec3..2038a0d 100644 --- a/src/state/features/globalSlice.ts +++ b/src/state/features/globalSlice.ts @@ -1,40 +1,39 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; - interface GlobalState { - isOpenPublishBlogModal: boolean - isLoadingCurrentBlog: boolean - isLoadingGlobal: boolean - isOpenEditBlogModal: boolean + isOpenPublishBlogModal: boolean; + isLoadingCurrentBlog: boolean; + isLoadingGlobal: boolean; + isOpenEditBlogModal: boolean; currentBlog: { - createdAt: number - blogId: string - title: string - description: string - blogImage: string - category?: string - tags?: string[] - navbarConfig?: any - } | null + createdAt: number; + blogId: string; + title: string; + description: string; + blogImage: string; + category?: string; + tags?: string[]; + navbarConfig?: any; + } | null; visitingBlog: { - createdAt: number - blogId: string - title: string - description: string - blogImage: string - category?: string - tags?: string[] - navbarConfig?: any - name?: string - } | null - audios: any[] | null - currAudio: any - audioPostId: string - downloads: any - showingAudioPlayer: boolean - userAvatarHash: Record - notifications: any[] - notificationCreatorComment: any[] + createdAt: number; + blogId: string; + title: string; + description: string; + blogImage: string; + category?: string; + tags?: string[]; + navbarConfig?: any; + name?: string; + } | null; + audios: any[] | null; + currAudio: any; + audioPostId: string; + downloads: any; + showingAudioPlayer: boolean; + userAvatarHash: Record; + notifications: any[]; + notificationCreatorComment: any[]; } const initialState: GlobalState = { isOpenPublishBlogModal: false, @@ -50,72 +49,72 @@ const initialState: GlobalState = { showingAudioPlayer: false, userAvatarHash: {}, notifications: [], - notificationCreatorComment: [] -} + notificationCreatorComment: [], +}; export const globalSlice = createSlice({ name: 'global', initialState, reducers: { togglePublishBlogModal: (state, action) => { - state.isOpenPublishBlogModal = action.payload + state.isOpenPublishBlogModal = action.payload; }, toggleEditBlogModal: (state, action) => { - state.isOpenEditBlogModal = action.payload + state.isOpenEditBlogModal = action.payload; }, setCurrentBlog: (state, action) => { - state.currentBlog = action.payload - state.isLoadingCurrentBlog = false + state.currentBlog = action.payload; + state.isLoadingCurrentBlog = false; }, setShowingAudioPlayer: (state, action) => { - state.showingAudioPlayer = action.payload + state.showingAudioPlayer = action.payload; }, setVisitingBlog: (state, action) => { - state.visitingBlog = action.payload - state.isLoadingCurrentBlog = false + state.visitingBlog = action.payload; + state.isLoadingCurrentBlog = false; }, setAudio: (state, action) => { - state.audios = action.payload.audios - state.audioPostId = action.payload.postId + state.audios = action.payload.audios; + state.audioPostId = action.payload.postId; }, setCurrAudio: (state, action) => { - state.currAudio = action.payload + state.currAudio = action.payload; }, removeAudio: (state, action) => { - state.audios = null - state.currAudio = null - state.audioPostId = '' + state.audios = null; + state.currAudio = null; + state.audioPostId = ''; }, setIsLoadingGlobal: (state, action) => { - state.isLoadingGlobal = action.payload + state.isLoadingGlobal = action.payload; }, setAddToDownloads: (state, action) => { - const download = action.payload - state.downloads[download.identifier] = download + const download = action.payload; + state.downloads[download.identifier] = download; }, updateDownloads: (state, action) => { - const { identifier } = action.payload - const download = action.payload + const { identifier } = action.payload; + const download = action.payload; state.downloads[identifier] = { ...state.downloads[identifier], - ...download - } + ...download, + }; }, setUserAvatarHash: (state, action) => { - const avatar = action.payload + const avatar = action.payload; if (avatar?.name && avatar?.url) { - state.userAvatarHash[avatar?.name] = avatar?.url + state.userAvatarHash[avatar?.name] = avatar?.url; } }, setNotifications: (state, action) => { - state.notifications = action.payload + state.notifications = action.payload; }, setNotificationCreatorComment: (state, action) => { - state.notificationCreatorComment = action.payload - } - } -}) + state.notificationCreatorComment = action.payload; + }, + }, +}); export const { togglePublishBlogModal, @@ -131,7 +130,7 @@ export const { setShowingAudioPlayer, setUserAvatarHash, setNotifications, - setNotificationCreatorComment -} = globalSlice.actions + setNotificationCreatorComment, +} = globalSlice.actions; -export default globalSlice.reducer; \ No newline at end of file +export default globalSlice.reducer; diff --git a/src/state/features/mailSlice.ts b/src/state/features/mailSlice.ts index 0a37c54..0cdcdd6 100644 --- a/src/state/features/mailSlice.ts +++ b/src/state/features/mailSlice.ts @@ -1,25 +1,25 @@ -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' -import localForage from 'localforage' -import { RootState } from '../store' +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; +import localForage from 'localforage'; +import { RootState } from '../store'; const favoritesLocal = localForage.createInstance({ - name: 'q-blog-favorites' -}) -const instanceCache = new Map() + name: 'q-blog-favorites', +}); +const instanceCache = new Map(); interface GlobalState { - posts: BlogPost[] - filteredPosts: BlogPost[] - hashMapPosts: Record - blogListPageNumber: number - favorites: any[] - favoritesLocal: any[] | null - subscriptions: any[] - subscriptionPosts: any[] - countNewPosts: number - isFiltering: boolean - filterValue: string - mailMessages: any[] - hashMapMailMessages: Record + posts: BlogPost[]; + filteredPosts: BlogPost[]; + hashMapPosts: Record; + blogListPageNumber: number; + favorites: any[]; + favoritesLocal: any[] | null; + subscriptions: any[]; + subscriptionPosts: any[]; + countNewPosts: number; + isFiltering: boolean; + filterValue: string; + mailMessages: any[]; + hashMapMailMessages: Record; } const initialState: GlobalState = { posts: [], @@ -34,287 +34,270 @@ const initialState: GlobalState = { isFiltering: false, filterValue: '', mailMessages: [], - hashMapMailMessages: {} -} + hashMapMailMessages: {}, +}; export interface BlogPost { - title: string - description: string - createdAt: number | string - user: string - postImage?: string - id: string - category?: string - categoryName?: string - tags?: string[] - updated?: number | string - isValid?: boolean + title: string; + description: string; + createdAt: number | string; + user: string; + postImage?: string; + id: string; + category?: string; + categoryName?: string; + tags?: string[]; + updated?: number | string; + isValid?: boolean; } -export const removeFavorites = createAsyncThunk< - string, - string, - { state: RootState } ->('favorites/remove', async (id, thunkAPI) => { - const state = thunkAPI.getState() // Get the current state - const username = state?.auth?.user?.name // Access the user.name property - if (!username) return '' - let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`) - if (!favoritesLocal) { - favoritesLocal = localForage.createInstance({ - name: `q-blog-favorites-${username}` - }) - } - await favoritesLocal.removeItem(id) - return id -}) -export const removeFavoritesArray = createAsyncThunk< - string[], - string[], - { state: RootState } ->('favorites/remove', async (ids, thunkAPI) => { - const state = thunkAPI.getState() // Get the current state - const username = state?.auth?.user?.name // Access the user.name property - if (!username || !ids.length) return [] +export const removeFavorites = createAsyncThunk( + 'favorites/remove', + async (id, thunkAPI) => { + const state = thunkAPI.getState(); // Get the current state + const username = state?.auth?.user?.name; // Access the user.name property + if (!username) return ''; + let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`); + if (!favoritesLocal) { + favoritesLocal = localForage.createInstance({ + name: `q-blog-favorites-${username}`, + }); + } + await favoritesLocal.removeItem(id); + return id; + }, +); +export const removeFavoritesArray = createAsyncThunk( + 'favorites/remove', + async (ids, thunkAPI) => { + const state = thunkAPI.getState(); // Get the current state + const username = state?.auth?.user?.name; // Access the user.name property + if (!username || !ids.length) return []; - let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`) - if (!favoritesLocal) { - favoritesLocal = localForage.createInstance({ - name: `q-blog-favorites-${username}` - }) - } - if (!favoritesLocal) return [] + let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`); + if (!favoritesLocal) { + favoritesLocal = localForage.createInstance({ + name: `q-blog-favorites-${username}`, + }); + } + if (!favoritesLocal) return []; - // Remove all items in parallel - await Promise.all(ids.map((id) => favoritesLocal?.removeItem(id))) + // Remove all items in parallel + await Promise.all(ids.map((id) => favoritesLocal?.removeItem(id))); - return ids -}) + return ids; + }, +); -export const upsertFavorites = createAsyncThunk< - any[], - any[], - { state: RootState } ->('favorites/upsert', async (payload: any, thunkAPI) => { - const state = thunkAPI.getState() // Get the current state - const username = state?.auth?.user?.name // Access the user.name property - if (!username) return '' - let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`) - if (!favoritesLocal) { - favoritesLocal = localForage.createInstance({ - name: `q-blog-favorites-${username}` - }) - } - if (!favoritesLocal) { - return [] - } - payload.forEach((favorite: BlogPost) => { - favoritesLocal?.setItem(favorite.id, { - user: favorite.user, - id: favorite.id - }) - }) - return payload -}) +export const upsertFavorites = createAsyncThunk( + 'favorites/upsert', + async (payload: any, thunkAPI) => { + const state = thunkAPI.getState(); // Get the current state + const username = state?.auth?.user?.name; // Access the user.name property + if (!username) return ''; + let favoritesLocal = instanceCache.get(`q-blog-favorites-${username}`); + if (!favoritesLocal) { + favoritesLocal = localForage.createInstance({ + name: `q-blog-favorites-${username}`, + }); + } + if (!favoritesLocal) { + return []; + } + payload.forEach((favorite: BlogPost) => { + favoritesLocal?.setItem(favorite.id, { + user: favorite.user, + id: favorite.id, + }); + }); + return payload; + }, +); export const mailSlice = createSlice({ name: 'blog', initialState, reducers: { setBlogListPageNumber: (state, action) => { - state.blogListPageNumber = action.payload + state.blogListPageNumber = action.payload; }, setIsFiltering: (state, action) => { - state.isFiltering = action.payload + state.isFiltering = action.payload; }, setFilterValue: (state, action) => { - state.filterValue = action.payload + state.filterValue = action.payload; }, setCountNewPosts: (state, action) => { - state.countNewPosts = action.payload + state.countNewPosts = action.payload; }, addPosts: (state, action) => { - state.posts = action.payload + state.posts = action.payload; }, addFilteredPosts: (state, action) => { - state.filteredPosts = action.payload + state.filteredPosts = action.payload; }, addSubscriptions: (state, action) => { - state.subscriptions = action.payload + state.subscriptions = action.payload; }, removeSubscription: (state, action) => { - state.subscriptions = state.subscriptions.filter( - (sub) => sub !== action.payload - ) + state.subscriptions = state.subscriptions.filter((sub) => sub !== action.payload); state.subscriptionPosts = state.subscriptionPosts.filter( - (post) => post.user !== action.payload - ) + (post) => post.user !== action.payload, + ); }, addSubscription: (state, action) => { - state.subscriptions.push(action.payload) + state.subscriptions.push(action.payload); }, addFavorites: (state, action) => { - state.favoritesLocal = action.payload + state.favoritesLocal = action.payload; }, addFavorite: (state, action) => { - state.favorites = action.payload + state.favorites = action.payload; }, removePost: (state, action) => { - const idToDelete = action.payload - state.posts = state.posts.filter((item) => item.id !== idToDelete) - state.filteredPosts = state.filteredPosts.filter( - (item) => item.id !== idToDelete - ) + const idToDelete = action.payload; + state.posts = state.posts.filter((item) => item.id !== idToDelete); + state.filteredPosts = state.filteredPosts.filter((item) => item.id !== idToDelete); }, addPostToBeginning: (state, action) => { - state.posts.unshift(action.payload) + state.posts.unshift(action.payload); }, updatePost: (state, action) => { - const { id } = action.payload - const index = state.posts.findIndex((post) => post.id === id) + const { id } = action.payload; + const index = state.posts.findIndex((post) => post.id === id); if (index !== -1) { - state.posts[index] = { ...action.payload } + state.posts[index] = { ...action.payload }; } - const index2 = state.filteredPosts.findIndex((post) => post.id === id) + const index2 = state.filteredPosts.findIndex((post) => post.id === id); if (index2 !== -1) { - state.filteredPosts[index2] = { ...action.payload } + state.filteredPosts[index2] = { ...action.payload }; } }, addToHashMapMail: (state, action) => { - const message = action.payload - state.hashMapMailMessages[message.id] = message + const message = action.payload; + state.hashMapMailMessages[message.id] = message; }, updateInHashMap: (state, action) => { - const { id } = action.payload - const post = action.payload - state.hashMapPosts[id] = { ...post } + const { id } = action.payload; + const post = action.payload; + state.hashMapPosts[id] = { ...post }; }, removeFromHashMap: (state, action) => { - const idToDelete = action.payload - delete state.hashMapPosts[idToDelete] + const idToDelete = action.payload; + delete state.hashMapPosts[idToDelete]; }, addArrayToHashMap: (state, action) => { - const posts = action.payload + const posts = action.payload; posts.forEach((post: BlogPost) => { - state.hashMapPosts[post.id] = post - }) + state.hashMapPosts[post.id] = post; + }); }, upsertMessages: (state, action) => { action.payload.forEach((message: any) => { - const index = state.mailMessages.findIndex((p) => p.id === message.id) + const index = state.mailMessages.findIndex((p) => p.id === message.id); if (index !== -1) { - state.mailMessages[index] = message + state.mailMessages[index] = message; } else { - state.mailMessages.push(message) + state.mailMessages.push(message); } - }) + }); }, upsertFilteredPosts: (state, action) => { action.payload.forEach((post: BlogPost) => { - const index = state.filteredPosts.findIndex((p) => p.id === post.id) + const index = state.filteredPosts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.filteredPosts[index] = post + state.filteredPosts[index] = post; } else { - state.filteredPosts.push(post) + state.filteredPosts.push(post); } - }) + }); }, upsertPostsBeginning: (state, action) => { action.payload.reverse().forEach((post: BlogPost) => { - const index = state.posts.findIndex((p) => p.id === post.id) + const index = state.posts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.posts[index] = post + state.posts[index] = post; } else { - state.posts.unshift(post) + state.posts.unshift(post); } - }) + }); }, upsertMessagesBeginning: (state, action) => { action.payload.reverse().forEach((message: BlogPost) => { - const index = state.mailMessages.findIndex((p) => p.id === message.id) + const index = state.mailMessages.findIndex((p) => p.id === message.id); if (index !== -1) { - state.mailMessages[index] = message + state.mailMessages[index] = message; } else { - state.mailMessages.unshift(message) + state.mailMessages.unshift(message); } - }) + }); }, upsertSubscriptionPosts: (state, action) => { action.payload.forEach((post: BlogPost) => { - const index = state.subscriptionPosts.findIndex((p) => p.id === post.id) + const index = state.subscriptionPosts.findIndex((p) => p.id === post.id); if (index !== -1) { - state.subscriptionPosts[index] = post + state.subscriptionPosts[index] = post; } else { - state.subscriptionPosts.push(post) + state.subscriptionPosts.push(post); } - }) + }); }, populateFavorites: (state, action) => { action.payload.forEach((favorite: BlogPost) => { - const index = state.favorites.findIndex((p) => p.id === favorite.id) + const index = state.favorites.findIndex((p) => p.id === favorite.id); if (index !== -1) { - state.favorites[index] = favorite + state.favorites[index] = favorite; } else { - state.favorites.push(favorite) + state.favorites.push(favorite); } - }) + }); }, blockUser: (state, action) => { - const username = action.payload - state.posts = state.posts.filter((item) => item.user !== username) - state.filteredPosts = state.filteredPosts.filter( - (item) => item.user !== username - ) - state.favorites = state.favorites.filter((item) => item.user !== username) - state.subscriptionPosts = state.subscriptionPosts.filter( - (item) => item.user !== username - ) + const username = action.payload; + state.posts = state.posts.filter((item) => item.user !== username); + state.filteredPosts = state.filteredPosts.filter((item) => item.user !== username); + state.favorites = state.favorites.filter((item) => item.user !== username); + state.subscriptionPosts = state.subscriptionPosts.filter((item) => item.user !== username); if (state?.favoritesLocal) { const ids = state.favoritesLocal .filter((item) => item.user === username) - .map((user) => user?.user || '') - state.favoritesLocal = state.favoritesLocal.filter( - (item) => item.user !== username - ) + .map((user) => user?.user || ''); + state.favoritesLocal = state.favoritesLocal.filter((item) => item.user !== username); - removeFavoritesArray(ids) + removeFavoritesArray(ids); } - } + }, }, extraReducers: (builder) => { builder.addCase(removeFavorites.fulfilled, (state, action) => { - const idToDelete = action.payload - if (!idToDelete) return state - state.favorites = state.favorites.filter((item) => item.id !== idToDelete) - state.favoritesLocal = state?.favorites?.filter( - (item) => item.id !== idToDelete - ) + const idToDelete = action.payload; + if (!idToDelete) return state; + state.favorites = state.favorites.filter((item) => item.id !== idToDelete); + state.favoritesLocal = state?.favorites?.filter((item) => item.id !== idToDelete); }), builder.addCase(upsertFavorites.fulfilled, (state, action) => { - ;(action.payload || []).forEach((favorite: BlogPost) => { + (action.payload || []).forEach((favorite: BlogPost) => { favoritesLocal.setItem(favorite.id, { user: favorite.user, - id: favorite.id - }) - const index = state.favorites.findIndex((p) => p.id === favorite.id) + id: favorite.id, + }); + const index = state.favorites.findIndex((p) => p.id === favorite.id); if (index !== -1) { - state.favorites[index] = favorite + state.favorites[index] = favorite; } else { - state.favorites.push(favorite) + state.favorites.push(favorite); } - const index2 = state?.favoritesLocal?.findIndex( - (p) => p.id === favorite.id - ) + const index2 = state?.favoritesLocal?.findIndex((p) => p.id === favorite.id); if (index2 !== -1) { - state.favorites[index] = favorite + state.favorites[index] = favorite; } else { - state?.favoritesLocal?.push(favorite) + state?.favoritesLocal?.push(favorite); } - }) - }) - } -}) + }); + }); + }, +}); export const { addPosts, @@ -339,7 +322,7 @@ export const { setFilterValue, upsertMessages, addToHashMapMail, - upsertMessagesBeginning -} = mailSlice.actions + upsertMessagesBeginning, +} = mailSlice.actions; -export default mailSlice.reducer +export default mailSlice.reducer; diff --git a/src/state/features/notificationsSlice.ts b/src/state/features/notificationsSlice.ts index af074e2..d29c6c8 100644 --- a/src/state/features/notificationsSlice.ts +++ b/src/state/features/notificationsSlice.ts @@ -1,32 +1,32 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; interface AlertTypes { - alertSuccess: string - alertError: string - alertInfo: string + alertSuccess: string; + alertError: string; + alertInfo: string; } interface InitialState { - alertTypes: AlertTypes + alertTypes: AlertTypes; } const initialState: InitialState = { alertTypes: { alertSuccess: '', alertError: '', - alertInfo: '' - } -} + alertInfo: '', + }, +}; export const notificationsSlice = createSlice({ - name: "notifications", + name: 'notifications', initialState, reducers: { setNotification: ( state: InitialState, - action: PayloadAction<{ alertType: string; msg: string }> + action: PayloadAction<{ alertType: string; msg: string }>, ) => { - if (action.payload.alertType === "success") { + if (action.payload.alertType === 'success') { return { ...state, alertTypes: { @@ -34,7 +34,7 @@ export const notificationsSlice = createSlice({ alertSuccess: action.payload.msg, }, }; - } else if (action.payload.alertType === "error") { + } else if (action.payload.alertType === 'error') { return { ...state, alertTypes: { @@ -42,7 +42,7 @@ export const notificationsSlice = createSlice({ alertError: action.payload.msg, }, }; - } else if (action.payload.alertType === "info") { + } else if (action.payload.alertType === 'info') { return { ...state, alertTypes: { @@ -60,14 +60,13 @@ export const notificationsSlice = createSlice({ ...state.alertTypes, alertSuccess: '', alertError: '', - alertInfo: '' - } - } + alertInfo: '', + }, + }; }, }, }); -export const { setNotification, removeNotification } = - notificationsSlice.actions; +export const { setNotification, removeNotification } = notificationsSlice.actions; export default notificationsSlice.reducer; diff --git a/src/state/store.ts b/src/state/store.ts index 4507ba0..b77c914 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,10 +1,10 @@ -import { configureStore } from "@reduxjs/toolkit"; +import { configureStore } from '@reduxjs/toolkit'; // import imagesReducer from "./features/imagesSlice"; -import notificationsReducer from "./features/notificationsSlice"; -import authReducer from "./features/authSlice"; -import globalReducer from "./features/globalSlice"; -import blogReducer from "./features/blogSlice"; -import mailReducer from './features/mailSlice' +import notificationsReducer from './features/notificationsSlice'; +import authReducer from './features/authSlice'; +import globalReducer from './features/globalSlice'; +import blogReducer from './features/blogSlice'; +import mailReducer from './features/mailSlice'; export const store = configureStore({ reducer: { @@ -13,14 +13,14 @@ export const store = configureStore({ auth: authReducer, global: globalReducer, blog: blogReducer, - mail: mailReducer + mail: mailReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ - serializableCheck: false + serializableCheck: false, }), - preloadedState: undefined // optional, can be any valid state object -}) + preloadedState: undefined, // optional, can be any valid state object +}); // Define the RootState type, which is the type of the entire Redux state tree. // This is useful when you need to access the state in a component or elsewhere. diff --git a/src/styles/theme.ts b/src/styles/theme.ts index 7832a2f..1bf4c60 100644 --- a/src/styles/theme.ts +++ b/src/styles/theme.ts @@ -1,4 +1,4 @@ -import { createTheme } from '@mui/material/styles' +import { createTheme } from '@mui/material/styles'; const commonThemeOptions = { typography: { @@ -8,38 +8,38 @@ const commonThemeOptions = { 'Oxygen', 'Catamaran', 'Cairo', - 'Arial' + 'Arial', ].join(','), h1: { fontSize: '2rem', - fontWeight: 600 + fontWeight: 600, }, h2: { fontSize: '1.75rem', - fontWeight: 500 + fontWeight: 500, }, h3: { fontSize: '1.5rem', - fontWeight: 500 + fontWeight: 500, }, h4: { fontSize: '1.25rem', - fontWeight: 500 + fontWeight: 500, }, h5: { fontSize: '1rem', - fontWeight: 500 + fontWeight: 500, }, h6: { fontSize: '0.875rem', - fontWeight: 500 + fontWeight: 500, }, body1: { fontSize: '23px', fontFamily: 'Raleway', fontWeight: 400, lineHeight: 1.5, - letterSpacing: '0.5px' + letterSpacing: '0.5px', }, body2: { @@ -47,12 +47,12 @@ const commonThemeOptions = { fontFamily: 'Raleway, Arial', fontWeight: 400, lineHeight: 1.4, - letterSpacing: '0.2px' - } + letterSpacing: '0.2px', + }, }, spacing: 8, shape: { - borderRadius: 4 + borderRadius: 4, }, breakpoints: { values: { @@ -60,8 +60,8 @@ const commonThemeOptions = { sm: 600, md: 900, lg: 1200, - xl: 1536 - } + xl: 1536, + }, }, components: { MuiButton: { @@ -70,17 +70,17 @@ const commonThemeOptions = { backgroundColor: 'inherit', transition: 'filter 0.3s ease-in-out', '&:hover': { - filter: 'brightness(1.1)' - } - } + filter: 'brightness(1.1)', + }, + }, }, defaultProps: { disableElevation: true, - disableRipple: true - } - } - } -} + disableRipple: true, + }, + }, + }, +}; const lightTheme = createTheme({ ...commonThemeOptions, @@ -89,45 +89,43 @@ const lightTheme = createTheme({ primary: { main: '#f4f4fb', dark: '#eaecf4', - light: '#f9f9fd' + light: '#f9f9fd', }, secondary: { - main: '#1EAAF1' + main: '#1EAAF1', }, background: { default: '#fafafa', - paper: '#f0f0f0' + paper: '#f0f0f0', }, text: { primary: '#000000', - secondary: '#525252' - } + secondary: '#525252', + }, }, components: { MuiCard: { styleOverrides: { root: { - boxShadow: - 'rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;', + boxShadow: 'rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;', borderRadius: '8px', transition: 'all 0.3s ease-in-out', '&:hover': { cursor: 'pointer', - boxShadow: - 'rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;' - } - } - } + boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;', + }, + }, + }, }, - MuiIcon:{ + MuiIcon: { defaultProps: { style: { - color: '#000000' - } - } - } + color: '#000000', + }, + }, + }, }, -}) +}); const darkTheme = createTheme({ ...commonThemeOptions, @@ -135,45 +133,45 @@ const darkTheme = createTheme({ mode: 'dark', primary: { main: '#2e3d60', - dark: "#1a2744", - light: "#3f4b66", + dark: '#1a2744', + light: '#3f4b66', }, secondary: { - main: '#45adff' + main: '#45adff', }, - + background: { default: '#313338', - paper: "#1e1e20" + paper: '#1e1e20', }, text: { primary: '#ffffff', - secondary: '#b3b3b3' - } + secondary: '#b3b3b3', + }, }, components: { MuiCard: { styleOverrides: { root: { - boxShadow: "none", + boxShadow: 'none', borderRadius: '8px', transition: 'all 0.3s ease-in-out', '&:hover': { cursor: 'pointer', boxShadow: - ' 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);' - } - } - } + ' 0px 3px 4px 0px hsla(0,0%,0%,0.14), 0px 3px 3px -2px hsla(0,0%,0%,0.12), 0px 1px 8px 0px hsla(0,0%,0%,0.2);', + }, + }, + }, }, - MuiIcon:{ + MuiIcon: { defaultProps: { style: { - color: '#ffffff' - } - } - } + color: '#ffffff', + }, + }, + }, }, -}) +}); -export { lightTheme, darkTheme } +export { lightTheme, darkTheme }; diff --git a/src/utils/blogIdformats.ts b/src/utils/blogIdformats.ts index 00a3544..ef38f67 100644 --- a/src/utils/blogIdformats.ts +++ b/src/utils/blogIdformats.ts @@ -1,29 +1,30 @@ export function removePrefix(id: string): string { if (id.startsWith('q-blog-')) { - return id.substring(7) + return id.substring(7); } else { - return id + return id; } } export function addPrefix(id: string): string { if (id.startsWith('q-blog-')) { - return id + return id; } else { - return `q-blog-${id}` + return `q-blog-${id}`; } } - // This function extracts the createTitleId and id portion of the identifier string export const extractCreateTitleIdAndId = (identifier: string): string => { - // Split the identifier string by the '-post-' delimiter - const parts = identifier.split('-post-') - return parts[1] || '' - } - - // This function combines the createTitleId and id portion with the 'ououeouou-post-' prefix - export const buildIdentifierFromCreateTitleIdAndId = (blogId:string, createTitleIdAndId: string): string => { - return `${blogId}-post-${createTitleIdAndId}` - } - + // Split the identifier string by the '-post-' delimiter + const parts = identifier.split('-post-'); + return parts[1] || ''; +}; + +// This function combines the createTitleId and id portion with the 'ououeouou-post-' prefix +export const buildIdentifierFromCreateTitleIdAndId = ( + blogId: string, + createTitleIdAndId: string, +): string => { + return `${blogId}-post-${createTitleIdAndId}`; +}; diff --git a/src/utils/checkAndUpdatePost.tsx b/src/utils/checkAndUpdatePost.tsx index bc64238..51336eb 100644 --- a/src/utils/checkAndUpdatePost.tsx +++ b/src/utils/checkAndUpdatePost.tsx @@ -1,5 +1,5 @@ import { useDispatch, useSelector } from 'react-redux'; -import { BlogPost, addToHashMap, updateInHashMap } from '../state/features/blogSlice'; +import { BlogPost, addToHashMap, updateInHashMap } from '../state/features/blogSlice'; import { RootState } from '../state/store'; export const checkAndUpdatePost = (post: BlogPost) => { @@ -7,17 +7,17 @@ export const checkAndUpdatePost = (post: BlogPost) => { const hashMapPosts = useSelector((state: RootState) => state.blog.hashMapPosts); // Check if the post exists in hashMapPosts - const existingPost = hashMapPosts[post.id + "-" + post.user]; + const existingPost = hashMapPosts[post.id + '-' + post.user]; if (!existingPost) { // If the post doesn't exist, add it to hashMapPosts - dispatch(addToHashMap(post)) + dispatch(addToHashMap(post)); } else if ( post?.updated && existingPost?.updated && (!existingPost.updated || post.updated) > existingPost.updated ) { // If the post exists and its updatedDate is more recent than the existing post's updatedDate, update it in hashMapPosts - dispatch(updateInHashMap(post)) + dispatch(updateInHashMap(post)); } -} +}; diff --git a/src/utils/checkStructure.ts b/src/utils/checkStructure.ts index e756026..b98002a 100644 --- a/src/utils/checkStructure.ts +++ b/src/utils/checkStructure.ts @@ -1,50 +1,48 @@ // check the structure of blog posts -export const checkStructure = (content: any)=> { - let isValid = true - if(!content?.title) isValid = false - if(!content?.createdAt) isValid = false - if(!Array.isArray(content?.postContent)) isValid = false - content?.postContent?.forEach((c: any)=> { - if (!c.type) { - isValid = false; - } - if (!c.version) { - isValid = false; - } - if (!c.id) { - isValid = false; - } - if (!c.content) { - isValid = false; - } - if ( - c.version === 1 && - c.type !== 'editor' && - c.type !== 'image' && - c.type !== 'video' && - c.type !== 'audio' && - c.type !== 'file' && - c.type !== 'poll' - ) { - isValid = false - } - }); - - return isValid - } +export const checkStructure = (content: any) => { + let isValid = true; + if (!content?.title) isValid = false; + if (!content?.createdAt) isValid = false; + if (!Array.isArray(content?.postContent)) isValid = false; + content?.postContent?.forEach((c: any) => { + if (!c.type) { + isValid = false; + } + if (!c.version) { + isValid = false; + } + if (!c.id) { + isValid = false; + } + if (!c.content) { + isValid = false; + } + if ( + c.version === 1 && + c.type !== 'editor' && + c.type !== 'image' && + c.type !== 'video' && + c.type !== 'audio' && + c.type !== 'file' && + c.type !== 'poll' + ) { + isValid = false; + } + }); - export const checkStructureMailMessages = (content: any) => { - let isValid = true + return isValid; +}; - // if (!content?.title) isValid = false - // if (!content?.description) isValid = false - if (!content?.createdAt) isValid = false - if (!content?.version) isValid = false - if (!Array.isArray(content?.attachments)) isValid = false - if (!Array.isArray(content?.textContent)) isValid = false - if (typeof content?.generalData !== 'object') isValid = false +export const checkStructureMailMessages = (content: any) => { + let isValid = true; - return isValid - } + // if (!content?.title) isValid = false + // if (!content?.description) isValid = false + if (!content?.createdAt) isValid = false; + if (!content?.version) isValid = false; + if (!Array.isArray(content?.attachments)) isValid = false; + if (!Array.isArray(content?.textContent)) isValid = false; + if (typeof content?.generalData !== 'object') isValid = false; - \ No newline at end of file + return isValid; +}; diff --git a/src/utils/extractTextFromSlate.ts b/src/utils/extractTextFromSlate.ts index 7686787..d1312ff 100644 --- a/src/utils/extractTextFromSlate.ts +++ b/src/utils/extractTextFromSlate.ts @@ -1,14 +1,14 @@ export function extractTextFromSlate(nodes: any) { - if(!Array.isArray(nodes)) return "" - let text = ""; - - for (const node of nodes) { - if (node.text) { - text += node.text; - } else if (node.children) { - text += extractTextFromSlate(node.children); - } + if (!Array.isArray(nodes)) return ''; + let text = ''; + + for (const node of nodes) { + if (node.text) { + text += node.text; + } else if (node.children) { + text += extractTextFromSlate(node.children); } - - return text; - } \ No newline at end of file + } + + return text; +} diff --git a/src/utils/fetchMail.ts b/src/utils/fetchMail.ts index 8acd882..c436476 100644 --- a/src/utils/fetchMail.ts +++ b/src/utils/fetchMail.ts @@ -1,55 +1,51 @@ -import { MAIL_SERVICE_TYPE } from '../constants/mail' -import { checkStructure, checkStructureMailMessages } from './checkStructure' -import { extractTextFromSlate } from './extractTextFromSlate' -import { - base64ToUint8Array, - objectToUint8ArrayFromResponse, - uint8ArrayToObject -} from './toBase64' +import { MAIL_SERVICE_TYPE } from '../constants/mail'; +import { checkStructure, checkStructureMailMessages } from './checkStructure'; +import { extractTextFromSlate } from './extractTextFromSlate'; +import { base64ToUint8Array, objectToUint8ArrayFromResponse, uint8ArrayToObject } from './toBase64'; export const fetchAndEvaluateMail = async (data: any) => { const getBlogPost = async () => { - const { user, messageIdentifier, content, otherUser } = data + const { user, messageIdentifier, content, otherUser } = data; let obj: any = { ...content, - isValid: false - } + isValid: false, + }; try { // throw new Error('hello') - if (!user || !messageIdentifier) return obj - const url = `/arbitrary/${MAIL_SERVICE_TYPE}/${user}/${messageIdentifier}` + if (!user || !messageIdentifier) return obj; + const url = `/arbitrary/${MAIL_SERVICE_TYPE}/${user}/${messageIdentifier}`; let res = await qortalRequest({ action: 'FETCH_QDN_RESOURCE', name: user, service: MAIL_SERVICE_TYPE, identifier: messageIdentifier, - encoding: 'base64' - }) - const base64 = res + encoding: 'base64', + }); + const base64 = res; const resName = await qortalRequest({ action: 'GET_NAME_DATA', - name: otherUser - }) - if (!resName?.owner) return obj + name: otherUser, + }); + if (!resName?.owner) return obj; - const recipientAddress = resName.owner + const recipientAddress = resName.owner; const resAddress = await qortalRequest({ action: 'GET_ACCOUNT_DATA', - address: recipientAddress - }) - if (!resAddress?.publicKey) return obj - const recipientPublicKey = resAddress.publicKey + address: recipientAddress, + }); + if (!resAddress?.publicKey) return obj; + const recipientPublicKey = resAddress.publicKey; let requestEncryptBody: any = { action: 'DECRYPT_DATA', encryptedData: base64, - publicKey: recipientPublicKey - } - const resDecrypt = await qortalRequest(requestEncryptBody) + publicKey: recipientPublicKey, + }; + const resDecrypt = await qortalRequest(requestEncryptBody); - if (!resDecrypt) return obj - const decryptToUnit8Array = base64ToUint8Array(resDecrypt) - const responseData = uint8ArrayToObject(decryptToUnit8Array) + if (!resDecrypt) return obj; + const decryptToUnit8Array = base64ToUint8Array(resDecrypt); + const responseData = uint8ArrayToObject(decryptToUnit8Array); if (checkStructureMailMessages(responseData)) { obj = { @@ -59,15 +55,15 @@ export const fetchAndEvaluateMail = async (data: any) => { title: responseData.title, createdAt: responseData.createdAt, id: messageIdentifier, - isValid: true - } + isValid: true, + }; } - return obj + return obj; } catch (error) { - console.log({ error }) + console.log({ error }); } - } + }; - const res = await getBlogPost() - return res -} + const res = await getBlogPost(); + return res; +}; diff --git a/src/utils/fetchPosts.ts b/src/utils/fetchPosts.ts index 3336328..4558f38 100644 --- a/src/utils/fetchPosts.ts +++ b/src/utils/fetchPosts.ts @@ -1,34 +1,30 @@ -import { checkStructure } from './checkStructure' -import { extractTextFromSlate } from './extractTextFromSlate' +import { checkStructure } from './checkStructure'; +import { extractTextFromSlate } from './extractTextFromSlate'; export const fetchAndEvaluatePosts = async (data: any) => { const getBlogPost = async () => { - const { user, postId, content } = data + const { user, postId, content } = data; let obj: any = { ...content, - isValid: false - } + isValid: false, + }; - if (!user || !postId) return obj + if (!user || !postId) return obj; try { - const url = `/arbitrary/BLOG_POST/${user}/${postId}` + const url = `/arbitrary/BLOG_POST/${user}/${postId}`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const responseData = await response.json() + const responseData = await response.json(); if (checkStructure(responseData)) { - const findImage = responseData.postContent.find( - (data: any) => data?.type === 'image' - ) - const findText = responseData.postContent.find( - (data: any) => data?.type === 'editor' - ) + const findImage = responseData.postContent.find((data: any) => data?.type === 'image'); + const findText = responseData.postContent.find((data: any) => data?.type === 'editor'); obj = { content: responseData.postContent, ...content, @@ -37,17 +33,17 @@ export const fetchAndEvaluatePosts = async (data: any) => { createdAt: responseData.createdAt, id: postId, postImage: findImage ? findImage?.content?.image : '', - isValid: true - } + isValid: true, + }; if (findText && findText.content && !content?.description) { - obj.description = extractTextFromSlate(findText?.content) + obj.description = extractTextFromSlate(findText?.content); } } - return obj + return obj; } catch (error) {} - } + }; - const res = await getBlogPost() - return res -} + const res = await getBlogPost(); + return res; +}; diff --git a/src/utils/qortalRequestFunctions.ts b/src/utils/qortalRequestFunctions.ts index f584a97..f277c62 100644 --- a/src/utils/qortalRequestFunctions.ts +++ b/src/utils/qortalRequestFunctions.ts @@ -7,13 +7,13 @@ export async function getAccountNames(address: string): Promise { try { const res = await qortalRequest({ action: 'GET_ACCOUNT_NAMES', - address + address, }); const list = Array.isArray(res) ? res : []; if (!list.length) return [{ name: '', owner: address }]; return list.map((n: any) => ({ name: typeof n?.name === 'string' ? n.name : '', - owner: typeof n?.owner === 'string' ? n.owner : address + owner: typeof n?.owner === 'string' ? n.owner : address, })); } catch { return [{ name: '', owner: address }]; @@ -24,7 +24,7 @@ export async function getPrimaryAccountName(address: string): Promise { try { const res = await qortalRequest({ action: 'GET_PRIMARY_NAME', - address + address, }); return typeof res === 'string' ? res : ''; } catch { diff --git a/src/utils/time.ts b/src/utils/time.ts index 3b2830f..5f26b6a 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,24 +1,23 @@ -import moment from 'moment' +import moment from 'moment'; export function formatTimestamp(timestamp: number): string { - const now = moment() - const timestampMoment = moment(timestamp) - const elapsedTime = now.diff(timestampMoment, 'minutes') + const now = moment(); + const timestampMoment = moment(timestamp); + const elapsedTime = now.diff(timestampMoment, 'minutes'); if (elapsedTime < 1) { - return 'Just now' + return 'Just now'; } else if (elapsedTime < 60) { - return `${elapsedTime}m` + return `${elapsedTime}m`; } else if (elapsedTime < 1440) { - return `${Math.floor(elapsedTime / 60)}h` + return `${Math.floor(elapsedTime / 60)}h`; } else { - return timestampMoment.format('MMM D') + return timestampMoment.format('MMM D'); } } - export const formatDate = (unixTimestamp: number): string => { - const date = moment(unixTimestamp, 'x').fromNow() + const date = moment(unixTimestamp, 'x').fromNow(); - return date -} \ No newline at end of file + return date; +}; diff --git a/src/utils/toBase64.ts b/src/utils/toBase64.ts index 3ab61ad..6f22a14 100644 --- a/src/utils/toBase64.ts +++ b/src/utils/toBase64.ts @@ -1,90 +1,85 @@ export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { - const reader = new FileReader() - reader.readAsDataURL(file) + const reader = new FileReader(); + reader.readAsDataURL(file); reader.onload = () => { - const result = reader.result - reader.onload = null // remove onload handler - reader.onerror = null // remove onerror handler - resolve(result) - } + const result = reader.result; + reader.onload = null; // remove onload handler + reader.onerror = null; // remove onerror handler + resolve(result); + }; reader.onerror = (error) => { - reader.onload = null // remove onload handler - reader.onerror = null // remove onerror handler - reject(error) - } - }) + reader.onload = null; // remove onload handler + reader.onerror = null; // remove onerror handler + reject(error); + }; + }); export function objectToBase64(obj: any) { // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) + const jsonString = JSON.stringify(obj); // Step 2: Create a Blob from the JSON string - const blob = new Blob([jsonString], { type: 'application/json' }) + const blob = new Blob([jsonString], { type: 'application/json' }); // Step 3: Create a FileReader to read the Blob as a base64-encoded string return new Promise((resolve, reject) => { - const reader = new FileReader() + const reader = new FileReader(); reader.onloadend = () => { if (typeof reader.result === 'string') { // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace( - 'data:application/json;base64,', - '' - ) - resolve(base64) + const base64 = reader.result.replace('data:application/json;base64,', ''); + resolve(base64); } else { - reject(new Error('Failed to read the Blob as a base64-encoded string')) + reject(new Error('Failed to read the Blob as a base64-encoded string')); } - } + }; reader.onerror = () => { - reject(reader.error) - } - reader.readAsDataURL(blob) - }) + reject(reader.error); + }; + reader.readAsDataURL(blob); + }); } export function objectToUint8Array(obj: any) { // Convert the object to a JSON string - const jsonString = JSON.stringify(obj) + const jsonString = JSON.stringify(obj); // Encode the JSON string as a byte array using TextEncoder - const encoder = new TextEncoder() - const byteArray = encoder.encode(jsonString) + const encoder = new TextEncoder(); + const byteArray = encoder.encode(jsonString); // Create a new Uint8Array and set its content to the encoded byte array - const uint8Array = new Uint8Array(byteArray) + const uint8Array = new Uint8Array(byteArray); - return uint8Array + return uint8Array; } export function uint8ArrayToBase64(uint8Array: Uint8Array): string { - const length = uint8Array.length - let binaryString = '' - const chunkSize = 1024 * 1024 // Process 1MB at a time + const length = uint8Array.length; + let binaryString = ''; + const chunkSize = 1024 * 1024; // Process 1MB at a time for (let i = 0; i < length; i += chunkSize) { - const chunkEnd = Math.min(i + chunkSize, length) - const chunk = uint8Array.subarray(i, chunkEnd) - binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join( - '' - ) + const chunkEnd = Math.min(i + chunkSize, length); + const chunk = uint8Array.subarray(i, chunkEnd); + binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join(''); } - return btoa(binaryString) + return btoa(binaryString); } export function objectToUint8ArrayFromResponse(obj: any) { - const len = Object.keys(obj).length - const result = new Uint8Array(len) + const len = Object.keys(obj).length; + const result = new Uint8Array(len); for (let i = 0; i < len; i++) { - result[i] = obj[i] + result[i] = obj[i]; } - return result + return result; } // export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string { // let binary = '' @@ -99,46 +94,44 @@ export function objectToUint8ArrayFromResponse(obj: any) { // } export function base64ToUint8Array(base64: string) { - const binaryString = atob(base64) - const len = binaryString.length - const bytes = new Uint8Array(len) + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i) + bytes[i] = binaryString.charCodeAt(i); } - return bytes + return bytes; } export function uint8ArrayToObject(uint8Array: Uint8Array) { // Decode the byte array using TextDecoder - const decoder = new TextDecoder() - const jsonString = decoder.decode(uint8Array) + const decoder = new TextDecoder(); + const jsonString = decoder.decode(uint8Array); // Convert the JSON string back into an object - const obj = JSON.parse(jsonString) + const obj = JSON.parse(jsonString); - return obj + return obj; } export function processFileInChunks(file: File): Promise { - return new Promise( - (resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => { - const reader = new FileReader() + return new Promise((resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => { + const reader = new FileReader(); - reader.onload = function (event: ProgressEvent) { - const arrayBuffer = event.target?.result as ArrayBuffer - const uint8Array = new Uint8Array(arrayBuffer) - resolve(uint8Array) - } + reader.onload = function (event: ProgressEvent) { + const arrayBuffer = event.target?.result as ArrayBuffer; + const uint8Array = new Uint8Array(arrayBuffer); + resolve(uint8Array); + }; - reader.onerror = function (error: ProgressEvent) { - reject(error) - } + reader.onerror = function (error: ProgressEvent) { + reject(error); + }; - reader.readAsArrayBuffer(file) - } - ) + reader.readAsArrayBuffer(file); + }); } // export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise { diff --git a/src/webworkers/decodeBase64.js b/src/webworkers/decodeBase64.js index d5c2d5e..1c5ca50 100644 --- a/src/webworkers/decodeBase64.js +++ b/src/webworkers/decodeBase64.js @@ -10,30 +10,30 @@ self.addEventListener('message', async (event) => { // const res = await fetch(url2); // const data = await res.text(); // self.postMessage(data); - const url2 = `/arbitrary/VIDEO/crowetic/q-blog-video-xGR8HP` + const url2 = `/arbitrary/VIDEO/crowetic/q-blog-video-xGR8HP`; - const xhr = new XMLHttpRequest() - xhr.open('GET', url2, true) - xhr.responseType = 'blob' + const xhr = new XMLHttpRequest(); + xhr.open('GET', url2, true); + xhr.responseType = 'blob'; xhr.onload = () => { - const headers = xhr.getAllResponseHeaders() - const blob = xhr.response - const url = URL.createObjectURL(blob) - const byteLength = blob.size - const contentRange = `bytes 0-${byteLength}/${byteLength}` - const contentType = xhr.getResponseHeader('Content-Type') - self.postMessage(url) + const headers = xhr.getAllResponseHeaders(); + const blob = xhr.response; + const url = URL.createObjectURL(blob); + const byteLength = blob.size; + const contentRange = `bytes 0-${byteLength}/${byteLength}`; + const contentType = xhr.getResponseHeader('Content-Type'); + self.postMessage(url); // this.dispatchEvent(new CustomEvent('loaded', { detail: { headers, byteLength, contentRange, contentType } })); - } - xhr.send() + }; + xhr.send(); // fetch(url2) // .then(response => response.blob()) // .then(blob => { - // + // // // Create a new Blob with the 'video/mp4' MIME type // const mp4Blob = new Blob([blob], { type: 'video/mp4' }); - // + // // // Generate an object URL from the new Blob // const url = URL.createObjectURL(mp4Blob); @@ -43,9 +43,9 @@ self.addEventListener('message', async (event) => { // const response = await fetch(url2, { // method: 'GET' // }) - // + // // const responseData = await response.json() - // + // // const base64Data = responseData // const decodedData = atob(base64Data); // const byteNumbers = new Array(decodedData.length); @@ -59,4 +59,4 @@ self.addEventListener('message', async (event) => { // const url = URL.createObjectURL(blob); // self.postMessage(url); -}) +}); diff --git a/src/webworkers/getBlogWorker.js b/src/webworkers/getBlogWorker.js index d438d7c..a85a254 100644 --- a/src/webworkers/getBlogWorker.js +++ b/src/webworkers/getBlogWorker.js @@ -1,49 +1,45 @@ -import { checkStructure } from '../utils/checkStructure' +import { checkStructure } from '../utils/checkStructure'; function extractTextFromSlate(nodes) { - if (!Array.isArray(nodes)) return '' - let text = '' + if (!Array.isArray(nodes)) return ''; + let text = ''; for (const node of nodes) { if (node.text) { - text += node.text + text += node.text; } else if (node.children) { - text += extractTextFromSlate(node.children) + text += extractTextFromSlate(node.children); } } - return text + return text; } // worker.js self.onmessage = async (event) => { const getBlogPost = async () => { - const { data } = event - const { user, postId, content } = data + const { data } = event; + const { user, postId, content } = data; let obj = { remove: true, - id: postId - } + id: postId, + }; - if (!user || !postId) return obj + if (!user || !postId) return obj; try { - const url = `/arbitrary/BLOG_POST / ${user}/${postId}` + const url = `/arbitrary/BLOG_POST / ${user}/${postId}`; const response = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const responseData = await response.json() + const responseData = await response.json(); if (checkStructure(responseData)) { - const findImage = responseData.postContent.find( - (data) => data?.type === 'image' - ) - const findText = responseData.postContent.find( - (data) => data?.type === 'editor' - ) + const findImage = responseData.postContent.find((data) => data?.type === 'image'); + const findText = responseData.postContent.find((data) => data?.type === 'editor'); obj = { content: responseData.postContent, ...content, @@ -51,24 +47,24 @@ self.onmessage = async (event) => { title: responseData.title, createdAt: responseData.createdAt, id: postId, - postImage: findImage ? findImage?.content?.image : '' - } + postImage: findImage ? findImage?.content?.image : '', + }; if (findText && findText.content) { - obj.description = extractTextFromSlate(findText?.content) + obj.description = extractTextFromSlate(findText?.content); } } - return obj + return obj; } catch (error) {} - } + }; - const res = await getBlogPost() - self.postMessage(res) - // + const res = await getBlogPost(); + self.postMessage(res); + // // const { data } = event; // // Perform your computation using the data // const result = data.map((x) => x * 2); // // Send the result back to the main thread // self.postMessage(result); -} +}; diff --git a/src/wrappers/DownloadWrapper.tsx b/src/wrappers/DownloadWrapper.tsx index c3a7b58..329542e 100644 --- a/src/wrappers/DownloadWrapper.tsx +++ b/src/wrappers/DownloadWrapper.tsx @@ -1,12 +1,12 @@ -import React, { useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' +import React, { useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; -import { addUser } from '../state/features/authSlice' -import ShortUniqueId from 'short-unique-id' -import { RootState } from '../state/store' -import PublishBlogModal from '../components/modals/PublishBlogModal' -import EditBlogModal from '../components/modals/EditBlogModal' +import { addUser } from '../state/features/authSlice'; +import ShortUniqueId from 'short-unique-id'; +import { RootState } from '../state/store'; +import PublishBlogModal from '../components/modals/PublishBlogModal'; +import EditBlogModal from '../components/modals/EditBlogModal'; import { setAddToDownloads, @@ -14,68 +14,56 @@ import { setIsLoadingGlobal, toggleEditBlogModal, togglePublishBlogModal, - updateDownloads -} from '../state/features/globalSlice' + updateDownloads, +} from '../state/features/globalSlice'; -import { useFetchPosts } from '../hooks/useFetchPosts' -import { setNotification } from '../state/features/notificationsSlice' -import { DownloadTaskManager } from '../components/common/DownloadTaskManager' +import { useFetchPosts } from '../hooks/useFetchPosts'; +import { setNotification } from '../state/features/notificationsSlice'; +import { DownloadTaskManager } from '../components/common/DownloadTaskManager'; interface Props { - children: React.ReactNode + children: React.ReactNode; } -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); const defaultValues: MyContextInterface = { - downloadVideo: () => {} -} + downloadVideo: () => {}, +}; interface IDownloadVideoParams { - name: string - service: string - identifier: string - blogPost: any + name: string; + service: string; + identifier: string; + blogPost: any; } interface MyContextInterface { - downloadVideo: ({ - name, - service, - identifier, - blogPost - }: IDownloadVideoParams) => void + downloadVideo: ({ name, service, identifier, blogPost }: IDownloadVideoParams) => void; } -export const MyContext = React.createContext(defaultValues) +export const MyContext = React.createContext(defaultValues); const DownloadWrapper: React.FC = ({ children }) => { - const navigate = useNavigate() - const dispatch = useDispatch() - const { user } = useSelector((state: RootState) => state.auth) - const { audios, currAudio } = useSelector((state: RootState) => state.global) - const { getBlogPosts } = useFetchPosts() - const { downloads, currentBlog, isLoadingGlobal, isOpenEditBlogModal } = - useSelector((state: RootState) => state.global) + const navigate = useNavigate(); + const dispatch = useDispatch(); + const { user } = useSelector((state: RootState) => state.auth); + const { audios, currAudio } = useSelector((state: RootState) => state.global); + const { getBlogPosts } = useFetchPosts(); + const { downloads, currentBlog, isLoadingGlobal, isOpenEditBlogModal } = useSelector( + (state: RootState) => state.global, + ); - const addToPile = async ({ - name, - service, - identifier, - blogPost - }: IDownloadVideoParams) => { + const addToPile = async ({ name, service, identifier, blogPost }: IDownloadVideoParams) => { try { const res = await qortalRequest({ action: 'GET_QDN_RESOURCE_STATUS', name: name, service: service, - identifier: identifier - }) - if ( - res?.status === 'READY' || - (res.percentLoaded > 75 && res.totalChunkCount < 100) - ) { - return false + identifier: identifier, + }); + if (res?.status === 'READY' || (res.percentLoaded > 75 && res.totalChunkCount < 100)) { + return false; } } catch (error) {} - } + }; const fetchResource = async ({ name, service, identifier }: any) => { try { @@ -83,75 +71,67 @@ const DownloadWrapper: React.FC = ({ children }) => { action: 'GET_QDN_RESOURCE_PROPERTIES', name, service, - identifier - }) + identifier, + }); } catch (error) {} - } + }; const fetchVideoUrl = async ({ name, service, identifier }: any) => { try { - fetchResource({ name, service, identifier }) + fetchResource({ name, service, identifier }); let url = await qortalRequest({ action: 'GET_QDN_RESOURCE_URL', service: service, name: name, - identifier: identifier - }) + identifier: identifier, + }); if (url) { dispatch( updateDownloads({ name, service, identifier, - url - }) - ) + url, + }), + ); } } catch (error) {} - } + }; - const performDownload = ({ - name, - service, - identifier, - blogPost - }: IDownloadVideoParams) => { + const performDownload = ({ name, service, identifier, blogPost }: IDownloadVideoParams) => { dispatch( setAddToDownloads({ name, service, identifier, - blogPost - }) - ) + blogPost, + }), + ); - const url = `/arbitrary/${service}/${name}/${identifier}` - let isCalling = false - let percentLoaded = 0 - let timer = 24 + const url = `/arbitrary/${service}/${name}/${identifier}`; + let isCalling = false; + let percentLoaded = 0; + let timer = 24; const intervalId = setInterval(async () => { - if (isCalling) return - isCalling = true + if (isCalling) return; + isCalling = true; const res = await qortalRequest({ action: 'GET_QDN_RESOURCE_STATUS', name: name, service: service, - identifier: identifier - }) - isCalling = false + identifier: identifier, + }); + isCalling = false; if (res.localChunkCount) { if (res.percentLoaded) { - if ( - res.percentLoaded === percentLoaded && - res.percentLoaded !== 100 - ) { - timer = timer - 5 + if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) { + timer = timer - 5; } else { - timer = 24 + timer = 24; } if (timer < 0) { - timer = 24 - isCalling = true + timer = 24; + isCalling = true; dispatch( updateDownloads({ name, @@ -159,73 +139,66 @@ const DownloadWrapper: React.FC = ({ children }) => { identifier, status: { ...res, - status: 'REFETCHING' - } - }) - ) + status: 'REFETCHING', + }, + }), + ); setTimeout(() => { - isCalling = false + isCalling = false; fetchResource({ name, service, - identifier - }) - }, 25000) - return + identifier, + }); + }, 25000); + return; } - percentLoaded = res.percentLoaded + percentLoaded = res.percentLoaded; } dispatch( updateDownloads({ name, service, identifier, - status: res - }) - ) + status: res, + }), + ); } // check if progress is 100% and clear interval if true if (res?.status === 'READY') { - clearInterval(intervalId) + clearInterval(intervalId); dispatch( updateDownloads({ name, service, identifier, - status: res - }) - ) + status: res, + }), + ); } - }, 5000) // 1 second interval + }, 5000); // 1 second interval fetchVideoUrl({ name, service, - identifier - }) - } + identifier, + }); + }; - const downloadVideo = async ({ - name, - service, - identifier, - blogPost - }: IDownloadVideoParams) => { + const downloadVideo = async ({ name, service, identifier, blogPost }: IDownloadVideoParams) => { try { - - performDownload({ name, service, identifier, - blogPost - }) - return 'addedToList' + blogPost, + }); + return 'addedToList'; } catch (error) { - console.error(error) + console.error(error); } - } + }; return ( <> @@ -234,7 +207,7 @@ const DownloadWrapper: React.FC = ({ children }) => { {children} - ) -} + ); +}; -export default DownloadWrapper +export default DownloadWrapper; diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index 5aa820e..54129b6 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -1,12 +1,12 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; -import { addUser } from '../state/features/authSlice' -import ShortUniqueId from 'short-unique-id' -import { RootState } from '../state/store' -import PublishBlogModal from '../components/modals/PublishBlogModal' -import EditBlogModal from '../components/modals/EditBlogModal' +import { addUser } from '../state/features/authSlice'; +import ShortUniqueId from 'short-unique-id'; +import { RootState } from '../state/store'; +import PublishBlogModal from '../components/modals/PublishBlogModal'; +import EditBlogModal from '../components/modals/EditBlogModal'; import { setCurrentBlog, @@ -14,82 +14,83 @@ import { setNotificationCreatorComment, setNotifications, toggleEditBlogModal, - togglePublishBlogModal -} from '../state/features/globalSlice' -import NavBar from '../components/layout/Navbar/Navbar' -import PageLoader from '../components/common/PageLoader' -import { fetchAndEvaluatePosts } from '../utils/fetchPosts' + togglePublishBlogModal, +} from '../state/features/globalSlice'; +import NavBar from '../components/layout/Navbar/Navbar'; +import PageLoader from '../components/common/PageLoader'; +import { fetchAndEvaluatePosts } from '../utils/fetchPosts'; import { addFavorites, addPosts, addSubscriptions, addToHashMap, - BlogPost -} from '../state/features/blogSlice' -import { useFetchPosts } from '../hooks/useFetchPosts' -import { setNotification } from '../state/features/notificationsSlice' -import { AudioPlayer } from '../components/common/AudioPlayer' -import localForage from 'localforage' -import ConsentModal from '../components/modals/ConsentModal' -import localforage from 'localforage' -import { Item } from '../components/common/Comments/CommentEditor' -import { getAccountNames, getPrimaryAccountName, NameRecord } from '../utils/qortalRequestFunctions' -import { removePrefix } from '../utils/blogIdformats' + BlogPost, +} from '../state/features/blogSlice'; +import { useFetchPosts } from '../hooks/useFetchPosts'; +import { setNotification } from '../state/features/notificationsSlice'; +import { AudioPlayer } from '../components/common/AudioPlayer'; +import localForage from 'localforage'; +import ConsentModal from '../components/modals/ConsentModal'; +import localforage from 'localforage'; +import { Item } from '../components/common/Comments/CommentEditor'; +import { + getAccountNames, + getPrimaryAccountName, + NameRecord, +} from '../utils/qortalRequestFunctions'; +import { removePrefix } from '../utils/blogIdformats'; interface Props { - children: React.ReactNode + children: React.ReactNode; } -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); const notification = localforage.createInstance({ - name: 'notification' -}) + name: 'notification', +}); const GlobalWrapper: React.FC = ({ children }) => { - const navigate = useNavigate() - const dispatch = useDispatch() + const navigate = useNavigate(); + const dispatch = useDispatch(); - const [userAvatar, setUserAvatar] = useState('') - const [allNames, setAllNames] = useState([]) - const interval = useRef(null) - const { user } = useSelector((state: RootState) => state.auth) - const { audios, currAudio } = useSelector((state: RootState) => state.global) + const [userAvatar, setUserAvatar] = useState(''); + const [allNames, setAllNames] = useState([]); + const interval = useRef(null); + const { user } = useSelector((state: RootState) => state.auth); + const { audios, currAudio } = useSelector((state: RootState) => state.global); const notificationCreatorComment = useSelector( - (state: RootState) => state.global.notificationCreatorComment - ) - const notifications = useSelector( - (state: RootState) => state.global.notifications - ) + (state: RootState) => state.global.notificationCreatorComment, + ); + const notifications = useSelector((state: RootState) => state.global.notifications); const fullNotifications = useMemo(() => { - return notificationCreatorComment.length + notifications.length - }, [notificationCreatorComment, notifications]) - const [hasAttemptedToFetchBlogInitial, setHasAttemptedToFetchBlogInitial] = - useState(false) - const favoritesLocalRef = useRef(null) + return notificationCreatorComment.length + notifications.length; + }, [notificationCreatorComment, notifications]); + const [hasAttemptedToFetchBlogInitial, setHasAttemptedToFetchBlogInitial] = useState(false); + const favoritesLocalRef = useRef(null); const sendNotificationTab = async (fullNotifications: number) => { await qortalRequest({ action: 'SET_TAB_NOTIFICATIONS', - count: fullNotifications - }) - } + count: fullNotifications, + }); + }; useEffect(() => { - sendNotificationTab(fullNotifications) - }, [fullNotifications]) + sendNotificationTab(fullNotifications); + }, [fullNotifications]); useEffect(() => { - if (!user?.name) return - const dynamicInstanceName = `q-blog-favorites-${user.name}` // Replace this with your dynamic value + if (!user?.name) return; + const dynamicInstanceName = `q-blog-favorites-${user.name}`; // Replace this with your dynamic value favoritesLocalRef.current = localForage.createInstance({ - name: dynamicInstanceName - }) - getFavorites() - getSubscriptions() - getAvatar() - }, [user?.name]) + name: dynamicInstanceName, + }); + getFavorites(); + getSubscriptions(); + getAvatar(); + }, [user?.name]); const getAvatar = async () => { try { @@ -97,50 +98,47 @@ const GlobalWrapper: React.FC = ({ children }) => { action: 'GET_QDN_RESOURCE_URL', name: user?.name, service: 'THUMBNAIL', - identifier: 'qortal_avatar' - }) + identifier: 'qortal_avatar', + }); - if (url === 'Resource does not exist') return + if (url === 'Resource does not exist') return; - setUserAvatar(url) + setUserAvatar(url); } catch (error) { - console.error(error) + console.error(error); } - } + }; const getSubscriptions = async () => { try { - if (!user?.name) return - const listName = `q-blog-subscriptions-${user.name}` + if (!user?.name) return; + const listName = `q-blog-subscriptions-${user.name}`; const response = await qortalRequest({ action: 'GET_LIST_ITEMS', - list_name: listName - }) + list_name: listName, + }); - dispatch(addSubscriptions(response)) + dispatch(addSubscriptions(response)); } catch (error) {} - } + }; - const { - isOpenPublishBlogModal, - currentBlog, - isLoadingGlobal, - isOpenEditBlogModal - } = useSelector((state: RootState) => state.global) + const { isOpenPublishBlogModal, currentBlog, isLoadingGlobal, isOpenEditBlogModal } = useSelector( + (state: RootState) => state.global, + ); async function verifyIfBlogIdExtists(username: string, identifier: string) { - let doesExist = true + let doesExist = true; //TODO - SHOULD REMOVE NAME FILTER AND IDENTIFIER SHOULD BE EXACT // const url2 = `/arbitrary/resources/search?mode=ALL&service=BLOG&identifier=${identifier}&name=${username}&limit=1&includemetadata=true` - const url2 = `/arbitrary/resources?service=BLOG&identifier=${identifier}&name=${username}&limit=1&includemetadata=true` + const url2 = `/arbitrary/resources?service=BLOG&identifier=${identifier}&name=${username}&limit=1&includemetadata=true`; const responseBlogs = await fetch(url2, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) + 'Content-Type': 'application/json', + }, + }); - const dataMetadata = await responseBlogs.json() + const dataMetadata = await responseBlogs.json(); // const url = `/arbitrary/metadata/BLOG/${username}/${identifier}}` // const responseBlogs = await fetch(url, { // method: 'GET', @@ -150,38 +148,38 @@ const GlobalWrapper: React.FC = ({ children }) => { // }) // const responseDataBlogs = await responseBlogs.json() if (dataMetadata.length === 0) { - doesExist = false + doesExist = false; } - return doesExist + return doesExist; } async function getBlog(name: string): Promise { //TODO NAME SHOULD BE EXACT - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG&identifier=q-blog-&exactmatchnames=true&name=${name}&prefix=true&limit=20&includemetadata=true` + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG&identifier=q-blog-&exactmatchnames=true&name=${name}&prefix=true&limit=20&includemetadata=true`; const responseBlogs = await fetch(url, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseDataBlogs = await responseBlogs.json() + 'Content-Type': 'application/json', + }, + }); + const responseDataBlogs = await responseBlogs.json(); const filterOut = responseDataBlogs.filter((blog: any) => - blog.identifier.startsWith('q-blog-') - ) - let blog + blog.identifier.startsWith('q-blog-'), + ); + let blog; if (filterOut.length === 0) { - dispatch(setCurrentBlog(null)) - return null + dispatch(setCurrentBlog(null)); + return null; } - blog = filterOut[0] - const urlBlog = `/arbitrary/BLOG/${blog.name}/${blog.identifier}` + blog = filterOut[0]; + const urlBlog = `/arbitrary/BLOG/${blog.name}/${blog.identifier}`; const response = await fetch(urlBlog, { method: 'GET', headers: { - 'Content-Type': 'application/json' - } - }) - const responseData = await response.json() + 'Content-Type': 'application/json', + }, + }); + const responseData = await response.json(); dispatch( setCurrentBlog({ @@ -192,10 +190,10 @@ const GlobalWrapper: React.FC = ({ children }) => { blogImage: responseData?.blogImage || '', category: blog.metadata?.category, tags: blog.metadata?.tags || [], - navbarConfig: responseData?.navbarConfig || null - }) - ) - return blog.identifier + navbarConfig: responseData?.navbarConfig || null, + }), + ); + return blog.identifier; // const response = await fetch("/names/address/" + address); // const nameData = await response.json(); @@ -209,41 +207,41 @@ const GlobalWrapper: React.FC = ({ children }) => { const askForAccountInformation = React.useCallback(async () => { try { let account = await qortalRequest({ - action: 'GET_USER_ACCOUNT' - }) + action: 'GET_USER_ACCOUNT', + }); - const names = await getAccountNames(account.address) - const primaryName = await getPrimaryAccountName(account.address) - setAllNames(names) + const names = await getAccountNames(account.address); + const primaryName = await getPrimaryAccountName(account.address); + setAllNames(names); - dispatch(addUser({ ...account, name: primaryName })) + dispatch(addUser({ ...account, name: primaryName })); if (primaryName) { - await getBlog(primaryName) + await getBlog(primaryName); } else { - dispatch(setCurrentBlog(null)) + dispatch(setCurrentBlog(null)); } - setHasAttemptedToFetchBlogInitial(true) + setHasAttemptedToFetchBlogInitial(true); } catch (error) { - console.error(error) + console.error(error); } - }, []) + }, []); function objectToBase64(obj: any) { // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) + const jsonString = JSON.stringify(obj); // Step 2: Convert the JSON string to a Uint8Array of bytes - const utf8Encoder = new TextEncoder() - const uint8Array = utf8Encoder.encode(jsonString) + const utf8Encoder = new TextEncoder(); + const uint8Array = utf8Encoder.encode(jsonString); // Step 3: Convert the Uint8Array to a base64-encoded string - let base64 = '' + let base64 = ''; // For browsers - const numberArray = Array.from(uint8Array) // Convert Uint8Array to a regular array - const binaryString = String.fromCharCode.apply(null, numberArray) - base64 = btoa(binaryString) + const numberArray = Array.from(uint8Array); // Convert Uint8Array to a regular array + const binaryString = String.fromCharCode.apply(null, numberArray); + base64 = btoa(binaryString); - return base64 + return base64; } const createBlog = React.useCallback( @@ -252,42 +250,41 @@ const GlobalWrapper: React.FC = ({ children }) => { description: string, category: string, tags: string[], - blogIdentifier: string + blogIdentifier: string, ) => { - if (!user || !user.name) - throw new Error('Cannot publish: You do not have a Qortal name') - if (!title) throw new Error('A title is required') - if (!description) throw new Error('A description is required') - const name = user.name - const id = uid() - let formatBlogIdentifier = blogIdentifier + if (!user || !user.name) throw new Error('Cannot publish: You do not have a Qortal name'); + if (!title) throw new Error('A title is required'); + if (!description) throw new Error('A description is required'); + const name = user.name; + const id = uid(); + let formatBlogIdentifier = blogIdentifier; if (formatBlogIdentifier.endsWith('-')) { - formatBlogIdentifier = formatBlogIdentifier.slice(0, -1) + formatBlogIdentifier = formatBlogIdentifier.slice(0, -1); } if (formatBlogIdentifier.startsWith('-')) { - formatBlogIdentifier = formatBlogIdentifier.slice(1) + formatBlogIdentifier = formatBlogIdentifier.slice(1); } if (!formatBlogIdentifier) { - throw new Error('Please insert a valid blog id') + throw new Error('Please insert a valid blog id'); } - const identifier = `q-blog-${formatBlogIdentifier}` - const doesExitst = await verifyIfBlogIdExtists(name, identifier) + const identifier = `q-blog-${formatBlogIdentifier}`; + const doesExitst = await verifyIfBlogIdExtists(name, identifier); if (doesExitst) { - throw new Error('The blog identifier already exists') + throw new Error('The blog identifier already exists'); } - const formattedTags: { [key: string]: string } = {} + const formattedTags: { [key: string]: string } = {}; tags.forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); const blogobj = { title, description, blogImage: '', - createdAt: Date.now() - } - const blogPostToBase64 = objectToBase64(blogobj) + createdAt: Date.now(), + }; + const blogPostToBase64 = objectToBase64(blogobj); try { const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', @@ -298,86 +295,79 @@ const GlobalWrapper: React.FC = ({ children }) => { description, category, identifier: identifier, - ...formattedTags - }) + ...formattedTags, + }); // navigate(`/${user.name}/${identifier}`) await new Promise((res, rej) => { setTimeout(() => { - res() - }, 1000) - }) + res(); + }, 1000); + }); const blogfullObj = { ...blogobj, blogId: identifier, category, - tags - } + tags, + }; - dispatch(setCurrentBlog(blogfullObj)) + dispatch(setCurrentBlog(blogfullObj)); // getBlog(name) dispatch( setNotification({ msg: 'Blog successfully created', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); } catch (error: any) { - let notificationObj: any = null + let notificationObj: any = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to publish blog', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to publish blog', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to publish blog', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); if (error instanceof Error) { - throw new Error(error.message) + throw new Error(error.message); } else { - throw new Error('An unknown error occurred') + throw new Error('An unknown error occurred'); } } }, - [user] - ) + [user], + ); const editBlog = React.useCallback( - async ( - title: string, - description: string, - category: string, - tags: string[] - ) => { - if (!user || !user.name) - throw new Error('Cannot update: your Qortal name is not accessible') + async (title: string, description: string, category: string, tags: string[]) => { + if (!user || !user.name) throw new Error('Cannot update: your Qortal name is not accessible'); - if (!currentBlog) - throw new Error('Your blog is not available. Refresh and try again.') - if (!title) throw new Error('A title is required') - if (!description) throw new Error('A description is required') - const name = user.name - const formattedTags: { [key: string]: string } = {} + if (!currentBlog) throw new Error('Your blog is not available. Refresh and try again.'); + if (!title) throw new Error('A title is required'); + if (!description) throw new Error('A description is required'); + const name = user.name; + const formattedTags: { [key: string]: string } = {}; tags.forEach((tag: string, i: number) => { - formattedTags[`tag${i + 1}`] = tag - }) + formattedTags[`tag${i + 1}`] = tag; + }); const blogobj = { ...currentBlog, title, - description - } - const blogPostToBase64 = objectToBase64(blogobj) + description, + }; + const blogPostToBase64 = objectToBase64(blogobj); try { const resourceResponse = await qortalRequest({ action: 'PUBLISH_QDN_RESOURCE', @@ -388,117 +378,116 @@ const GlobalWrapper: React.FC = ({ children }) => { description, category, ...formattedTags, - identifier: currentBlog.blogId - }) + identifier: currentBlog.blogId, + }); await new Promise((res, rej) => { setTimeout(() => { - res() - }, 1000) - }) + res(); + }, 1000); + }); const blogfullObj = { ...blogobj, tags, - category - } + category, + }; // blogobj.tags = tags // blogobj.category = category // getBlog(name) - dispatch(setCurrentBlog(blogfullObj)) + dispatch(setCurrentBlog(blogfullObj)); dispatch( setNotification({ msg: 'Blog successfully updated', - alertType: 'success' - }) - ) + alertType: 'success', + }), + ); } catch (error: any) { - let notificationObj: any = null + let notificationObj: any = null; if (typeof error === 'string') { notificationObj = { msg: error || 'Failed to update blog', - alertType: 'error' - } + alertType: 'error', + }; } else if (typeof error?.error === 'string') { notificationObj = { msg: error?.error || 'Failed to update blog', - alertType: 'error' - } + alertType: 'error', + }; } else { notificationObj = { msg: error?.message || 'Failed to update blog', - alertType: 'error' - } + alertType: 'error', + }; } - if (!notificationObj) return - dispatch(setNotification(notificationObj)) + if (!notificationObj) return; + dispatch(setNotification(notificationObj)); if (error instanceof Error) { - throw new Error(error.message) + throw new Error(error.message); } else { - throw new Error('An unknown error occurred') + throw new Error('An unknown error occurred'); } } }, - [user, currentBlog] - ) + [user, currentBlog], + ); React.useEffect(() => { - askForAccountInformation() - }, []) + askForAccountInformation(); + }, []); const switchActiveName = React.useCallback( async (newName: string) => { - if (!user) return - dispatch(addUser({ ...user, name: newName })) - const blogId = await getBlog(newName) - setHasAttemptedToFetchBlogInitial(true) + if (!user) return; + dispatch(addUser({ ...user, name: newName })); + const blogId = await getBlog(newName); + setHasAttemptedToFetchBlogInitial(true); if (blogId) { - navigate(`/${newName}/${removePrefix(blogId)}`) + navigate(`/${newName}/${removePrefix(blogId)}`); } else { - navigate('/') + navigate('/'); } }, - [user, navigate] - ) + [user, navigate], + ); const onClosePublishBlogModal = React.useCallback(() => { - dispatch(togglePublishBlogModal(false)) - }, []) + dispatch(togglePublishBlogModal(false)); + }, []); const onCloseEditBlogModal = React.useCallback(() => { - dispatch(toggleEditBlogModal(false)) - }, []) + dispatch(toggleEditBlogModal(false)); + }, []); const getFavorites = useCallback(async () => { try { - const allItems: any[] = [] + const allItems: any[] = []; if (!favoritesLocalRef?.current) { - return + return; } favoritesLocalRef?.current .iterate(function (value: any, key: string) { // Handle each key-value pair here - allItems.push({ id: key, ...(value || {}) }) + allItems.push({ id: key, ...(value || {}) }); }) .then(function () { - dispatch(addFavorites(allItems)) + dispatch(addFavorites(allItems)); }) .catch(function (error: any) { // Handle any errors here - }) + }); } catch (error) {} - }, [user?.name]) + }, [user?.name]); const checkNotifications = useCallback(async (username: string) => { try { - let notificationComments: Item[] = - (await notification.getItem('comments')) || [] + let notificationComments: Item[] = (await notification.getItem('comments')) || []; notificationComments = notificationComments .filter((nc) => nc.postId && nc.postName && nc.lastSeen) - .sort((a, b) => b.lastSeen - a.lastSeen) + .sort((a, b) => b.lastSeen - a.lastSeen); - let listOfNotifications = [] + let listOfNotifications = []; for (const comment of notificationComments) { const response: any[] = await qortalRequest({ action: 'SEARCH_QDN_RESOURCES', @@ -509,137 +498,128 @@ const GlobalWrapper: React.FC = ({ children }) => { limit: 1, offset: 0, reverse: true, - mode: 'ALL' - }) - if (response.length === 0) return + mode: 'ALL', + }); + if (response.length === 0) return; - if ( - response[0].created > comment.lastSeen && - response[0].name !== username - ) { - const string = response[0].identifier - const startIndex = string.indexOf('blog_') + 5 - const result = string.substr(startIndex, 12) + if (response[0].created > comment.lastSeen && response[0].name !== username) { + const string = response[0].identifier; + const startIndex = string.indexOf('blog_') + 5; + const result = string.substr(startIndex, 12); listOfNotifications.push({ ...response[0], partialPostId: result, postId: comment.postId, - postName: comment.postName - }) + postName: comment.postName, + }); } } - dispatch(setNotifications(listOfNotifications)) + dispatch(setNotifications(listOfNotifications)); } catch (error) { - console.log({ error }) + console.log({ error }); } - }, []) + }, []); - const checkNotificationCreatorComment = useCallback( - async (username: string) => { - try { - const currentDate = Date.now() - const threeDaysAgo = currentDate - 259200000 + const checkNotificationCreatorComment = useCallback(async (username: string) => { + try { + const currentDate = Date.now(); + const threeDaysAgo = currentDate - 259200000; + const response: any[] = await qortalRequest({ + action: 'SEARCH_QDN_RESOURCES', + service: 'BLOG_POST', + name: username, + exactMatchNames: true, + limit: 5, + offset: 0, + reverse: true, + mode: 'ALL', + }); + const filterPosts = response.filter((post) => post.created > threeDaysAgo); + let comments = []; + for (const post of filterPosts) { const response: any[] = await qortalRequest({ action: 'SEARCH_QDN_RESOURCES', - service: 'BLOG_POST', - name: username, + service: 'BLOG_COMMENT', + query: `qcomment_v1_qblog_${post.identifier.slice(-12)}`, exactMatchNames: true, - limit: 5, + excludeBlocked: true, + limit: 1, offset: 0, reverse: true, - mode: 'ALL' - }) - const filterPosts = response.filter( - (post) => post.created > threeDaysAgo - ) - let comments = [] - for (const post of filterPosts) { - const response: any[] = await qortalRequest({ - action: 'SEARCH_QDN_RESOURCES', - service: 'BLOG_COMMENT', - query: `qcomment_v1_qblog_${post.identifier.slice(-12)}`, - exactMatchNames: true, - excludeBlocked: true, - limit: 1, - offset: 0, - reverse: true, - mode: 'ALL' - }) - if (response.length > 0 && response[0].name !== username) { - comments.push({ - ...response[0], - postName: post.name, - postId: post.identifier - }) - } + mode: 'ALL', + }); + if (response.length > 0 && response[0].name !== username) { + comments.push({ + ...response[0], + postName: post.name, + postId: post.identifier, + }); } - let listOfNotifications = [] - let notificationComments: any = - (await notification.getItem('post-comments')) || {} - for (const comment of comments) { - const savedReference = notificationComments[comment.postId] - if (savedReference) { - if (savedReference.lastSeen < comment.created) { - listOfNotifications.push(comment) - } - } else { - listOfNotifications.push(comment) - notificationComments[comment.postId] = { - lastSeen: 0, - postName: comment?.postName, - id: comment?.identifier, - postId: comment?.postId - } - } - } - - dispatch(setNotificationCreatorComment(listOfNotifications)) - await notification.setItem('post-comments', notificationComments) - } catch (error) { - console.log({ error }) } - }, - [] - ) + let listOfNotifications = []; + let notificationComments: any = (await notification.getItem('post-comments')) || {}; + for (const comment of comments) { + const savedReference = notificationComments[comment.postId]; + if (savedReference) { + if (savedReference.lastSeen < comment.created) { + listOfNotifications.push(comment); + } + } else { + listOfNotifications.push(comment); + notificationComments[comment.postId] = { + lastSeen: 0, + postName: comment?.postName, + id: comment?.identifier, + postId: comment?.postId, + }; + } + } + + dispatch(setNotificationCreatorComment(listOfNotifications)); + await notification.setItem('post-comments', notificationComments); + } catch (error) { + console.log({ error }); + } + }, []); const checkNotificationsFunc = useCallback( (username: string) => { - let isCalling = false + let isCalling = false; interval.current = setInterval(async () => { - if (isCalling) return - isCalling = true - const res = await checkNotifications(username) - isCalling = false - }, 20000) + if (isCalling) return; + isCalling = true; + const res = await checkNotifications(username); + isCalling = false; + }, 20000); }, - [checkNotifications] - ) + [checkNotifications], + ); const checkNotificationCreatorCommentFunc = useCallback( (username: string) => { - let isCalling = false + let isCalling = false; interval.current = setInterval(async () => { - if (isCalling) return - isCalling = true - const res = await checkNotificationCreatorComment(username) - isCalling = false - }, 20000) + if (isCalling) return; + isCalling = true; + const res = await checkNotificationCreatorComment(username); + isCalling = false; + }, 20000); }, - [checkNotificationCreatorComment] - ) + [checkNotificationCreatorComment], + ); useEffect(() => { - if (!user?.name) return - checkNotificationsFunc(user.name) + if (!user?.name) return; + checkNotificationsFunc(user.name); // creator of posts comments coming in - checkNotificationCreatorCommentFunc(user.name) + checkNotificationCreatorCommentFunc(user.name); return () => { if (interval?.current) { - clearInterval(interval.current) + clearInterval(interval.current); } - } - }, [checkNotificationsFunc, user]) + }; + }, [checkNotificationsFunc, user]); return ( <> @@ -674,11 +654,9 @@ const GlobalWrapper: React.FC = ({ children }) => { {children} - {audios && audios.length > 0 && ( - - )} + {audios && audios.length > 0 && } - ) -} + ); +}; -export default GlobalWrapper +export default GlobalWrapper; diff --git a/tests/axe-smoke.test.tsx b/tests/axe-smoke.test.tsx index f22e72b..d9dd8fa 100644 --- a/tests/axe-smoke.test.tsx +++ b/tests/axe-smoke.test.tsx @@ -1,15 +1,15 @@ -import { describe, it, expect } from 'vitest' -import { render } from '@testing-library/react' -import { axe } from 'jest-axe' +import { describe, it, expect } from 'vitest'; +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; function Hello() { - return + return ; } describe('a11y smoke', () => { it('has no detectable violations', async () => { - const { container } = render() - const results = await axe(container) - expect(results).toHaveNoViolations() - }) -}) + const { container } = render(); + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/tests/msw/handlers.ts b/tests/msw/handlers.ts index 1027ca4..99fa5a4 100644 --- a/tests/msw/handlers.ts +++ b/tests/msw/handlers.ts @@ -1,7 +1,7 @@ -import { http, HttpResponse } from 'msw' +import { http, HttpResponse } from 'msw'; export const handlers = [ http.get('/api/health', () => { - return HttpResponse.json({ ok: true }) + return HttpResponse.json({ ok: true }); }), -] +]; diff --git a/tests/msw/server.ts b/tests/msw/server.ts index 86f7d61..e52fee0 100644 --- a/tests/msw/server.ts +++ b/tests/msw/server.ts @@ -1,4 +1,4 @@ -import { setupServer } from 'msw/node' -import { handlers } from './handlers' +import { setupServer } from 'msw/node'; +import { handlers } from './handlers'; -export const server = setupServer(...handlers) +export const server = setupServer(...handlers); diff --git a/tests/setup.ts b/tests/setup.ts index 74ea3c9..b19470d 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,11 +1,11 @@ // tests/setup.ts — registers matchers & MSW -import '@testing-library/jest-dom/vitest' -import { afterAll, afterEach, beforeAll, expect } from 'vitest' -import { toHaveNoViolations } from 'jest-axe' -import { server } from './msw/server' +import '@testing-library/jest-dom/vitest'; +import { afterAll, afterEach, beforeAll, expect } from 'vitest'; +import { toHaveNoViolations } from 'jest-axe'; +import { server } from './msw/server'; -expect.extend(toHaveNoViolations) +expect.extend(toHaveNoViolations); -beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })) -afterEach(() => server.resetHandlers()) -afterAll(() => server.close()) +beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); diff --git a/tests/types/expect-extensions.d.ts b/tests/types/expect-extensions.d.ts index 16a6d5e..12dacb8 100644 --- a/tests/types/expect-extensions.d.ts +++ b/tests/types/expect-extensions.d.ts @@ -1,12 +1,12 @@ // tests/types/expect-extensions.d.ts -import 'vitest' -import 'jest-axe' +import 'vitest'; +import 'jest-axe'; declare module 'vitest' { interface Assertion { - toHaveNoViolations(): void + toHaveNoViolations(): void; } interface AsymmetricMatchersContaining { - toHaveNoViolations(): void + toHaveNoViolations(): void; } } diff --git a/tsconfig.json b/tsconfig.json index 2d6dc53..0f173dd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,5 +15,5 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src"], + "include": ["src"] } diff --git a/vite.config.ts b/vite.config.ts index 75de9e5..2d34436 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react-swc'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], - base: '' -}) + base: '', +}); diff --git a/vitest.config.ts b/vitest.config.ts index 4f0e4a4..e60f06c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vitest/config' -import path from 'node:path' +import { defineConfig } from 'vitest/config'; +import path from 'node:path'; export default defineConfig({ test: { @@ -17,4 +17,4 @@ export default defineConfig({ '@': path.resolve(__dirname, './src'), }, }, -}) +}); -- 2.43.0 From 0871c31b3428059ee1a83f797a0a0fd6ecc6e992 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Thu, 21 Aug 2025 18:51:12 -0400 Subject: [PATCH 30/42] Add new tests --- .gitea/workflows/ci.yml | 2 + docs/TESTING.md | 43 +- eslint.config.mjs | 9 + package-lock.json | 11469 ++++--------------- package.json | 4 +- tests/components/PostPreview.a11y.test.tsx | 51 + tests/hooks/useFetchPosts.test.tsx | 29 + tests/pages/BlogIndividualPost.test.tsx | 60 + tests/pages/BlogIndividualProfile.test.tsx | 40 + tests/pages/BlogList.test.tsx | 66 + tests/setup.ts | 32 + tests/state/blogSlice.test.ts | 64 + tests/state/globalSlice.test.ts | 47 + tests/tsconfig.json | 12 + tests/types/expect-extensions.d.ts | 10 +- tests/types/jest-axe.d.ts | 7 + tests/utils/blogIdformats.test.ts | 28 + tests/utils/checkStructure.test.ts | 46 + tests/utils/extractTextFromSlate.test.ts | 16 + tests/utils/fetchMail.test.ts | 67 + tests/utils/fetchPosts.test.ts | 45 + tests/utils/qortalRequestFunctions.test.ts | 45 + tests/utils/time.test.ts | 40 + tests/utils/toBase64.test.ts | 88 + vitest.config.ts | 14 + 25 files changed, 2979 insertions(+), 9355 deletions(-) create mode 100644 tests/components/PostPreview.a11y.test.tsx create mode 100644 tests/hooks/useFetchPosts.test.tsx create mode 100644 tests/pages/BlogIndividualPost.test.tsx create mode 100644 tests/pages/BlogIndividualProfile.test.tsx create mode 100644 tests/pages/BlogList.test.tsx create mode 100644 tests/state/blogSlice.test.ts create mode 100644 tests/state/globalSlice.test.ts create mode 100644 tests/tsconfig.json create mode 100644 tests/types/jest-axe.d.ts create mode 100644 tests/utils/blogIdformats.test.ts create mode 100644 tests/utils/checkStructure.test.ts create mode 100644 tests/utils/extractTextFromSlate.test.ts create mode 100644 tests/utils/fetchMail.test.ts create mode 100644 tests/utils/fetchPosts.test.ts create mode 100644 tests/utils/qortalRequestFunctions.test.ts create mode 100644 tests/utils/time.test.ts create mode 100644 tests/utils/toBase64.test.ts diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 3533480..b8c60f6 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -100,6 +100,8 @@ jobs: shell: bash run: | npm test --silent -- --run || npm run test -- --run + # Show summary and ensure coverage thresholds are enforced by Vitest config + if [ -d coverage ]; then ls -lah coverage; fi - name: Build (vite) shell: bash diff --git a/docs/TESTING.md b/docs/TESTING.md index 8bd87bf..ee91f9a 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -1,33 +1,52 @@ -# Testing — Q-Blog (Phase 0) +# Testing — Q-Blog ## Local ```bash -pnpm typecheck -pnpm lint -pnpm test:run +# ensure Node 20.x is active (use nvm if needed) +npm run typecheck +npm run lint:phase0 +npm run test:run +npm run build ``` MSW is enabled in tests; unhandled requests are bypassed. ## CI (Gitea) -- Workflow: `.gitea/workflows/ci-no-marketplace.yml` -- Matrix: single ubuntu, Node 20.16.0 via tarball -- Steps: typecheck → lint → vitest (coverage) → build (if Vite config exists) +- Workflow: `.gitea/workflows/ci.yml` +- Runner: self-hosted +- Steps: checkout → Node setup → deps → typecheck → lint (phase0) → format check → tests (Vitest + RTL + MSW) → build (Vite) ## a11y smoke -`tests/axe-smoke.test.tsx` runs jest-axe; add more targeted checks as UI grows. +`tests/axe-smoke.test.tsx` runs jest-axe. + +Additional tests added: + +- `tests/utils/blogIdformats.test.ts`: blog id helpers. +- `tests/utils/extractTextFromSlate.test.ts`: Slate plain-text extraction. +- `tests/utils/time.test.ts`: relative time helpers. +- `tests/utils/toBase64.test.ts`: binary encode/decode helpers. +- `tests/state/blogSlice.test.ts`: reducer behaviors and upserts. +- `tests/hooks/useFetchPosts.test.tsx`: minimal sanity for hook utilities. +- `tests/utils/checkStructure.test.ts`: blog and mail structure validation. +- `tests/utils/fetchPosts.test.ts`: maps BLOG_POST fetch via MSW. +- `tests/utils/qortalRequestFunctions.test.ts`: qortalRequest wrappers behavior. +- `tests/utils/fetchMail.test.ts`: mail decrypt/mapping flow with mocked qortalRequest. +- `tests/pages/BlogList.test.tsx`: renders posts and new-post banner (MUI ThemeProvider). +- `tests/pages/BlogIndividualProfile.test.tsx`: loads blog title from API (MSW + ThemeProvider). --- -## Audit Notes (v0.0.1) +## Notes -- ⚠️ Note: MSW/jest-axe not detected in test sources; test stack may be incomplete compared to documentation. -- ⚠️ Note: Ensure axe accessibility smoke tests and MSW handlers are implemented or update this doc. +- MSW is configured in `tests/setup.ts`; unhandled requests are bypassed by default. +- Coverage reporter is v8 (`text`, `lcov`). Consider adding thresholds as the suite expands. +- Test env adds a minimal `IntersectionObserver` polyfill in `tests/setup.ts` for components using `react-intersection-observer`. +- UI tests wrap components in MUI `ThemeProvider` with a default theme to satisfy styled theme usage. -## Test Scenarios — Multi‑Blog +## Test Scenarios — Multi‑Blog (future) - Header: 0 vs 1 vs N blogs; menu opens/closes; selection navigates and sets active blog. - Name root redirect: single blog → posts; else → `/{{name}}/blogs`; polite loading text in `aria-live`. diff --git a/eslint.config.mjs b/eslint.config.mjs index 5aefeda..08c088b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -41,4 +41,13 @@ export default [ 'jsx-a11y/label-has-associated-control': 'error', }, }, + { + files: ['tests/**/*.{ts,tsx,js,jsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'no-unused-expressions': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + }, + }, ]; diff --git a/package-lock.json b/package-lock.json index de51adc..a39a317 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "q-blog", - "version": "0.0.1", - "lockfileVersion": 2, + "version": "0.0.2", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "q-blog", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", @@ -78,7 +78,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -103,90 +102,187 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.21.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT", + "peer": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "peer": true, "dependencies": { - "@babel/types": "^7.18.6" - }, + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -209,14 +305,25 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", + "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" @@ -237,45 +344,57 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "@babel/helper-plugin-utils": "^7.27.1" }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" @@ -467,65 +586,71 @@ } }, "node_modules/@emotion/babel-plugin": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", - "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", "find-root": "^1.1.0", "source-map": "^0.5.7", - "stylis": "4.1.3" + "stylis": "4.2.0" } }, "node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" } }, "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.0" + "@emotion/memoize": "^0.9.0" } }, "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" }, "node_modules/@emotion/react": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", - "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -538,33 +663,36 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", "dependencies": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/styled": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", - "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -579,30 +707,35 @@ "node_modules/@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", + "license": "MIT" }, "node_modules/@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", @@ -622,13 +755,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", - "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -638,13 +772,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz", - "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -654,13 +789,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz", - "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -670,13 +806,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz", - "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -686,13 +823,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz", - "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -702,13 +840,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz", - "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -718,13 +857,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz", - "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -734,13 +874,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz", - "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -750,13 +891,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz", - "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -766,13 +908,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz", - "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -782,13 +925,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz", - "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -798,13 +942,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz", - "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -814,13 +959,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz", - "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -830,13 +976,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz", - "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -846,13 +993,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz", - "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -862,13 +1010,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz", - "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -895,13 +1044,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", - "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -928,13 +1078,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", - "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -961,13 +1112,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", - "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -977,13 +1129,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz", - "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -993,13 +1146,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz", - "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1009,13 +1163,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz", - "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1128,19 +1283,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { "version": "9.33.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", @@ -1179,47 +1321,54 @@ } }, "node_modules/@formatjs/ecma402-abstract": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", - "integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", + "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "license": "MIT", "dependencies": { - "@formatjs/intl-localematcher": "0.2.32", - "tslib": "^2.4.0" + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.1", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" } }, "node_modules/@formatjs/fast-memoize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz", - "integrity": "sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz", - "integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", + "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", + "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "1.14.3", - "@formatjs/icu-skeleton-parser": "1.3.18", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/icu-skeleton-parser": "1.8.14", + "tslib": "^2.8.0" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz", - "integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==", + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", + "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", + "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "1.14.3", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.3.4", + "tslib": "^2.8.0" } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.2.32", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", - "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", + "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/@humanfs/core": { @@ -1289,9 +1438,9 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", - "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.15.tgz", + "integrity": "sha512-SwHMGa8Z47LawQN0rog0sT+6JpiL0B7eW9p1Bb7iCeKDGTI5Ez25TSc2l8kw52VV7hA4sX/C78CGkMrKXfuspA==", "dev": true, "license": "MIT", "dependencies": { @@ -1338,92 +1487,6 @@ } } }, - "node_modules/@inquirer/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@inquirer/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@inquirer/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@inquirer/core/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@inquirer/core/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/core/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@inquirer/figures": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", @@ -1453,36 +1516,40 @@ } }, "node_modules/@internationalized/date": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.1.0.tgz", - "integrity": "sha512-wjeur7K4AecT+YwoBmBXQ/+n5lP69tuZc34hw09s44EozZK7FZHSyfPvRp5/xEb2D6abLboskDY4jG+Nt0TNUQ==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz", + "integrity": "sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" } }, "node_modules/@internationalized/message": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.0.tgz", - "integrity": "sha512-Oo5m70FcBdADf7G8NkUffVSfuCdeAYVfsvNjZDi9ELpjvkc4YNJVTHt/NyTI9K7FgAVoELxiP9YmN0sJ+HNHYQ==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.8.tgz", + "integrity": "sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14", + "@swc/helpers": "^0.5.0", "intl-messageformat": "^10.1.0" } }, "node_modules/@internationalized/number": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.2.0.tgz", - "integrity": "sha512-GUXkhXSX1Ee2RURnzl+47uvbOxnlMnvP9Er+QePTjDjOPWuunmLKlEkYkEcLiiJp7y4l9QxGDLOlVr8m69LS5w==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.4.tgz", + "integrity": "sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" } }, "node_modules/@internationalized/string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.1.0.tgz", - "integrity": "sha512-TJQKiyUb+wyAfKF59UNeZ/kELMnkxyecnyPCnBI1ma4NaXReJW+7Cc2mObXAqraIBJUVv7rgI46RLKrLgi35ng==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.7.tgz", + "integrity": "sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" } }, "node_modules/@isaacs/cliui": { @@ -1503,6 +1570,37 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1537,22 +1635,24 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -1574,7 +1674,8 @@ "node_modules/@juggle/resize-observer": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "license": "Apache-2.0" }, "node_modules/@mswjs/interceptors": { "version": "0.39.6", @@ -1594,65 +1695,35 @@ "node": ">=18" } }, - "node_modules/@mui/base": { - "version": "5.0.0-alpha.121", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.121.tgz", - "integrity": "sha512-8nJRY76UqlJV+q/Yzo0tgGfPWEOa+4N9rjO81fMmcJqP0I6m54hLDXsjvMg4tvelY5eKHXUK6Tb7en+GHfTqZA==", - "dependencies": { - "@babel/runtime": "^7.21.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.13.tgz", - "integrity": "sha512-lx+GXBR9h/ApZsEP728tl0pyZyuajto+VnBgsoAzw1d5+CbmOo8ZWieKwVUGxZlPT1wMYNUYS5NtKzCli0xYjw==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.18.0.tgz", + "integrity": "sha512-jbhwoQ1AY200PSSOrNXmrFCaSDSJWP7qk6urkTmIirvRXDROkqe+QwcLlUiw/PrREwsIF/vm3/dAXvjlMHF0RA==", + "license": "MIT", "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz", - "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.18.0.tgz", + "integrity": "sha512-1s0vEZj5XFXDMmz3Arl/R7IncFqJ+WQ95LDp1roHWGDE2oCO3IS4/hmiOv1/8SD9r6B7tv9GLiqVZYHo+6PkTg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0" + "@babel/runtime": "^7.23.9" }, "engines": { "node": ">=12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1661,21 +1732,22 @@ } }, "node_modules/@mui/material": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.13.tgz", - "integrity": "sha512-2CnSj43F+159LbGmTLLQs5xbGYMiYlpTByQhP7c7cMX6opbScctBFE1PuyElpAmwW8Ag9ysfZH1d1MFAmJQkjg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.18.0.tgz", + "integrity": "sha512-bbH/HaJZpFtXGvWg3TsBWG4eyt3gah3E7nCNU8GLyRjVoWcA91Vm/T+sjHfUcwgJSw9iLtucfHBoq+qW/T30aA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-alpha.121", - "@mui/core-downloads-tracker": "^5.11.13", - "@mui/system": "^5.11.13", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.18.0", + "@mui/system": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.2.0", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -1683,14 +1755,14 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1704,13 +1776,28 @@ } } }, + "node_modules/@mui/material/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/private-theming": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz", - "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz", + "integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0", - "@mui/utils": "^5.11.13", + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.17.1", "prop-types": "^15.8.1" }, "engines": { @@ -1718,11 +1805,11 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1731,13 +1818,15 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz", - "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.18.0.tgz", + "integrity": "sha512-BN/vKV/O6uaQh2z5rXV+MBlVrEkwoS/TK75rFQ2mjxA7+NBo8qtTAOA4UaM0XeJfn7kh2wZ+xQw2HAx0u+TiBg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -1745,12 +1834,12 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1762,17 +1851,18 @@ } }, "node_modules/@mui/system": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.13.tgz", - "integrity": "sha512-OWP0Alp6C8ufnGm9+CZcl3d+OoRXL2PnrRT5ohaMLxvGL9OfNcL2t4JOjMmA0k1UAGd6E/Ygbu5lEPrZSDlvCg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.18.0.tgz", + "integrity": "sha512-ojZGVcRWqWhu557cdO3pWHloIGJdzVtxs3rk0F9L+x55LsUjcMUVkEhiF7E4TMxZoF9MmIHGGs0ZX3FDLAf0Xw==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0", - "@mui/private-theming": "^5.11.13", - "@mui/styled-engine": "^5.11.11", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "clsx": "^1.2.1", - "csstype": "^3.1.1", + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.17.1", + "@mui/styled-engine": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "clsx": "^2.1.0", + "csstype": "^3.1.3", "prop-types": "^15.8.1" }, "engines": { @@ -1780,13 +1870,13 @@ }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1800,12 +1890,31 @@ } } }, - "node_modules/@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "node_modules/@mui/system/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", "peerDependencies": { - "@types/react": "*" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz", + "integrity": "sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -1814,25 +1923,47 @@ } }, "node_modules/@mui/utils": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz", - "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz", + "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@babel/runtime": "^7.23.9", + "@mui/types": "~7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.2.0" + "react-is": "^19.0.0" }, "engines": { "node": ">=12.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/mui" + "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "react": "^17.0.0 || ^18.0.0" + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@nodelib/fs.scandir": { @@ -1910,316 +2041,360 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" } }, "node_modules/@react-aria/focus": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.11.0.tgz", - "integrity": "sha512-yPuWs9bAR9CMfIwyOPm2oXLPF19gNkUqW+ozSPhWbLMTEa8Ma09eHW1br4xbN+4ONOm/dCJsIkxTNPUkiLdQoA==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.0.tgz", + "integrity": "sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.14.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" + "@react-aria/interactions": "^3.25.4", + "@react-aria/utils": "^3.30.0", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/i18n": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.7.0.tgz", - "integrity": "sha512-PZCWmhO9mJvelwiYlsXLY6W4L2o+oza3xnDx0cZDVqp/Hf+OwMAPHWtZsFRTKdjk4TaOPB/ISc9HknWn6UpY4w==", + "version": "3.12.11", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.11.tgz", + "integrity": "sha512-1mxUinHbGJ6nJ/uSl62dl48vdZfWTBZePNF/wWQy98gR0qNFXLeusd7CsEmJT1971CR5i/WNYUo1ezNlIJnu6A==", + "license": "Apache-2.0", "dependencies": { - "@internationalized/date": "^3.1.0", - "@internationalized/message": "^3.1.0", - "@internationalized/number": "^3.2.0", - "@internationalized/string": "^3.1.0", - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14" + "@internationalized/date": "^3.8.2", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.4", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.0", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/interactions": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.14.0.tgz", - "integrity": "sha512-e1Tkr0UTuYFpV21PJrXy7jEY542Vl+C2Fo70oukZ1fN+wtfQkzodsTIzyepXb7kVMGmC34wDisMJUrksVkfY2w==", + "version": "3.25.4", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.4.tgz", + "integrity": "sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14" + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/overlays": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.13.0.tgz", - "integrity": "sha512-hRZyhAYzrlCcEWQ2k2jP24b0wc5/355Xl5w5FZHRmPeU1U4XlFwKX/eFlBs/li9Sprm1bTDXrCY480Kl6UsKDA==", + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.28.0.tgz", + "integrity": "sha512-qaHahAXTmxXULgg2/UfWEIwfgdKsn27XYryXAWWDu2CAZTcbI+5mGwYrQZSDWraM6v5PUUepzOVvm7hjTqiMFw==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/focus": "^3.11.0", - "@react-aria/i18n": "^3.7.0", - "@react-aria/interactions": "^3.14.0", - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-aria/visually-hidden": "^3.7.0", - "@react-stately/overlays": "^3.5.0", - "@react-types/button": "^3.7.1", - "@react-types/overlays": "^3.7.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14" + "@react-aria/focus": "^3.21.0", + "@react-aria/i18n": "^3.12.11", + "@react-aria/interactions": "^3.25.4", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.0", + "@react-aria/visually-hidden": "^3.8.26", + "@react-stately/overlays": "^3.6.18", + "@react-types/button": "^3.13.0", + "@react-types/overlays": "^3.9.0", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/ssr": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.5.0.tgz", - "integrity": "sha512-h0MJdSWOd1qObLnJ8mprU31wI8tmKFJMuwT22MpWq6psisOOZaga6Ml4u6Ee6M6duWWISjXvqO4Sb/J0PBA+nQ==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/utils": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.15.0.tgz", - "integrity": "sha512-aJZBG++iz1UwTW5gXFaHicKju4p0MPhAyBTcf2awHYWeTUUslDjJcEnNg7kjBYZBOrOSlA2rAt7/7C5CCURQPg==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.0.tgz", + "integrity": "sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.5.0", - "@react-stately/utils": "^3.6.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-aria/visually-hidden": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.7.0.tgz", - "integrity": "sha512-v/0ujJ67H6LjwY8J7mIGPVB1K8suBArLV+w8UGdX/wFXRL7H4r2fiqlrwAElWSmNbhDQl5BDm/Zh/ub9jB9yzA==", + "version": "3.8.26", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.26.tgz", + "integrity": "sha512-Lz36lTVaQbv5Kn74sPv0l9SnLQ5XHKCoq2zilP14Eb4QixDIqR7Ovj43m+6wi9pynf29jtOb/8D/9jrTjbmmgw==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.14.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" + "@react-aria/interactions": "^3.25.4", + "@react-aria/utils": "^3.30.0", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-dnd/asap": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", - "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" + "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==", + "license": "MIT" }, "node_modules/@react-dnd/invariant": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", - "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" + "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==", + "license": "MIT" }, "node_modules/@react-dnd/shallowequal": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", - "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" + "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==", + "license": "MIT" }, "node_modules/@react-spectrum/layout": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.5.0.tgz", - "integrity": "sha512-Yjy0uvGUHjczl44PQlMqOEBoJIe4coYwDQCoVl20YrTzGiMdwLV24Hs9uqK3I6yKmAoNxTehe0CdOVgwuVRF9g==", + "version": "3.6.17", + "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.6.17.tgz", + "integrity": "sha512-lj1KxFTr8N5K2ZPx0gFuBzzwQAgfMCE1v1eARVJza8W9IcyKtFK8BKK28g3Kf+x9UgbDw6/+T7+cqw6lHR/gFw==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-spectrum/utils": "^3.9.0", - "@react-types/layout": "^3.3.6", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" + "@react-aria/utils": "^3.30.0", + "@react-spectrum/utils": "^3.12.7", + "@react-types/layout": "^3.3.25", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { "@react-spectrum/provider": "^3.0.0", - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-spectrum/provider": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.7.0.tgz", - "integrity": "sha512-6Uch60R5PKJC+ZY3VweVadaCoaIj6Nd85TKfFH4jf8NEzXv5CqjwUzwV5aQ6u3k1K24Ves7RpLlK53HW1wUm6w==", + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.10.8.tgz", + "integrity": "sha512-Cr8A0cy2TB4bSVNne9sfUcFYzsp4RikNRHhEyiIF6s6QFMtp0SEFRIX3eW8fP+8vfIHhEC23MvNFK2PPkcabUg==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/i18n": "^3.7.0", - "@react-aria/overlays": "^3.13.0", - "@react-aria/utils": "^3.15.0", - "@react-spectrum/utils": "^3.9.0", - "@react-types/provider": "^3.6.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" + "@react-aria/i18n": "^3.12.11", + "@react-aria/overlays": "^3.28.0", + "@react-aria/utils": "^3.30.0", + "@react-spectrum/utils": "^3.12.7", + "@react-types/provider": "^3.8.11", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-spectrum/theme-default": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.0.tgz", - "integrity": "sha512-DagwngJ9T7yGgtdTwKj9LaHbg3AWLW2/ILJK92xbPj/yAAEVXJ8vhiP9Tg3W3mZMsxBCNwTVLyOLheu7d+D8og==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.20.tgz", + "integrity": "sha512-O19RMdTWJ9Y8JNtl6hOlGslD/o8AkNSO+O8hRbe7vnHW0m3rGyFh0MZIXfpKH3BNcc/8tF7jxh5GbjhjpcUZ8A==", + "license": "Apache-2.0", "dependencies": { - "@react-types/provider": "^3.6.0", - "@swc/helpers": "^0.4.14" + "@react-types/provider": "^3.8.11", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-spectrum/utils": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.9.0.tgz", - "integrity": "sha512-I1JKwiyLE54mAT7Z2wadVKoai1Q60QFg4XNuYUwk8TwWAEz7DUUHucntHAND6dqFpVIMK1Rk9lcZBft43FF2BQ==", + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.12.7.tgz", + "integrity": "sha512-OMbK9xtIU28hc6jZpOQ34Zr+a98r46QwsU0eTjgN0WNEY7SOPgWMyNbBW9qpSxGgMMT61BBuNjhX1FyOvccRvg==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/i18n": "^3.7.0", - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" + "@react-aria/i18n": "^3.12.11", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.0", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-spectrum/view": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.5.0.tgz", - "integrity": "sha512-Zzsy2S6gT14vtli52tjM5VP1EdMOP+bppNCmwX+saUJuxaEdj6oYjP89quE4HwALzYceOfmXIuGDyEVeDcuSsQ==", + "version": "3.6.21", + "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.6.21.tgz", + "integrity": "sha512-TV9hOugWv7NdDwm3ni2LPu0LIxXpnEoVfCXKtoR105CjDJPl6MNTdN6riebEGte9Lzuw87ROhcfGIvSfdq0OtA==", + "license": "Apache-2.0", "dependencies": { - "@react-aria/i18n": "^3.7.0", - "@react-aria/utils": "^3.15.0", - "@react-spectrum/utils": "^3.9.0", - "@react-types/shared": "^3.17.0", - "@react-types/view": "^3.4.0", - "@swc/helpers": "^0.4.14" + "@react-aria/utils": "^3.30.0", + "@react-spectrum/utils": "^3.12.7", + "@react-types/shared": "^3.31.0", + "@react-types/view": "^3.4.19", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { "@react-spectrum/provider": "^3.0.0", - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" } }, "node_modules/@react-stately/overlays": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.5.0.tgz", - "integrity": "sha512-r+U/G0Y4tCfI5wyBeIu+hmcZVRN8ChoK2zM1srPH9nDKsijQard2goX+9YmKng2LJ01Re/P6F8S8jYbpfEdLfQ==", + "version": "3.6.18", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.18.tgz", + "integrity": "sha512-g8n2FtDCxIg2wQ09R7lrM2niuxMPCdP17bxsPV9hyYnN6m42aAKGOhzWrFOK+3phQKgk/E1JQZEvKw1cyyGo1A==", + "license": "Apache-2.0", "dependencies": { - "@react-stately/utils": "^3.6.0", - "@react-types/overlays": "^3.7.0", - "@swc/helpers": "^0.4.14" + "@react-stately/utils": "^3.10.8", + "@react-types/overlays": "^3.9.0", + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-stately/utils": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.6.0.tgz", - "integrity": "sha512-rptF7iUWDrquaYvBAS4QQhOBQyLBncDeHF03WnHXAxnuPJXNcr9cXJtjJPGCs036ZB8Q2hc9BGG5wNyMkF5v+Q==", + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", "dependencies": { - "@swc/helpers": "^0.4.14" + "@swc/helpers": "^0.5.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/button": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.7.1.tgz", - "integrity": "sha512-c+8xjmqWSjI5/mEHVLbVSp0eh/z2UU8Ga+wqjbEUZUjm8uopYj1PaCAwZ7YgcAebyQrL/21GyjK6tFHKzuUdJQ==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.13.0.tgz", + "integrity": "sha512-hwvcNnBjDeNvWheWfBhmkJSzC48ub5rZq0DnpemB3XKOvv5WcF9p6rrQZsQ3egNGkh0Z+bKfr2QfotgOkccHSw==", + "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.17.0" + "@react-types/shared": "^3.31.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/layout": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.6.tgz", - "integrity": "sha512-8YF7nb+0AtSDRj+rejMRS5djWpqAf5Q6sJzgdccn42JQxW7jvxoKqN0pCRPqpMzQqae5hOsYYpVNfuIHwUUkUA==", + "version": "3.3.25", + "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.25.tgz", + "integrity": "sha512-k3yivNwCQ6FIxwLPe0Q6Ka38AZ4BOtMMjMSgwJMtKfv2fZ4RuL3ucRZNUdTkQI2pBMFWqH3EqhdYrZ9gMLDlbw==", + "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.17.0" + "@react-types/shared": "^3.31.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/overlays": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.7.0.tgz", - "integrity": "sha512-LstucncZ8dM+xJYEijI1V6jGH20w5XO/T60r7JTrgQElMC86phPeoWkMTN4c2lsRikybolDbvXL6XsF76YO56A==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.9.0.tgz", + "integrity": "sha512-T2DqMcDN5p8vb4vu2igoLrAtuewaNImLS8jsK7th7OjwQZfIWJn5Y45jSxHtXJUddEg1LkUjXYPSXCMerMcULw==", + "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.17.0" + "@react-types/shared": "^3.31.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/provider": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.6.0.tgz", - "integrity": "sha512-29+X5yMr1uHUc/BmFoyBqfWyMT3YGZJffaprtGGRQkCuwMbqoA8GI+rGJzNAMAD/1cPNo237PAGsi4NAup5tAQ==", + "version": "3.8.11", + "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.8.11.tgz", + "integrity": "sha512-8IFH/tKZ3VO/57MeYHJ4UTSQX15doDdgoTiGKbFMQsi9pjh4mPdCPpShiGjym9UWX0F+VEDBJZeLOypqhfjQoQ==", + "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.17.0" + "@react-types/shared": "^3.31.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/shared": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.17.0.tgz", - "integrity": "sha512-1SNZ/RhVrrQ1e6yE0bPV7d5Sfp+Uv0dfUEhwF9MAu2v5msu7AMewnsiojKNA0QA6Ing1gpDLjHCxtayQfuxqcg==", + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.31.0.tgz", + "integrity": "sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==", + "license": "Apache-2.0", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@react-types/view": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.0.tgz", - "integrity": "sha512-lXDLbOYRFwo8hxrSDIYUmnxOGZlHBDVEFe1ZKAXJ+qPwntzxhzUeAuddTiMDhEETKgTclmlIsJnwz+HNv6dhSQ==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.19.tgz", + "integrity": "sha512-1qZLd0K6Zt5KGCoiAK95o10L+7cKVw9Dszd8A5+Cs3yoTqRXxFh5pgkCdO5LYtoFHmVFzThMM9n8bx/7ZhxlVA==", + "license": "Apache-2.0", "dependencies": { - "@react-types/shared": "^3.17.0" + "@react-types/shared": "^3.31.0" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, "node_modules/@reduxjs/toolkit": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", - "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", + "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", + "license": "MIT", "dependencies": { - "immer": "^9.0.16", - "redux": "^4.2.0", + "immer": "^9.0.21", + "redux": "^4.2.1", "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" + "reselect": "^4.1.8" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18", @@ -2235,17 +2410,25 @@ } }, "node_modules/@remix-run/router": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", - "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=14.0.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", + "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", "cpu": [ "arm" ], @@ -2257,9 +2440,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", + "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", "cpu": [ "arm64" ], @@ -2271,9 +2454,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", + "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", "cpu": [ "arm64" ], @@ -2285,9 +2468,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", + "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", "cpu": [ "x64" ], @@ -2299,9 +2482,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", + "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", "cpu": [ "arm64" ], @@ -2313,9 +2496,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", + "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", "cpu": [ "x64" ], @@ -2327,9 +2510,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", + "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", "cpu": [ "arm" ], @@ -2341,9 +2524,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", + "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", "cpu": [ "arm" ], @@ -2355,9 +2538,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", + "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", "cpu": [ "arm64" ], @@ -2369,9 +2552,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", + "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", "cpu": [ "arm64" ], @@ -2383,9 +2566,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", + "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", "cpu": [ "loong64" ], @@ -2397,9 +2580,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", + "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", "cpu": [ "ppc64" ], @@ -2411,9 +2594,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", + "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", "cpu": [ "riscv64" ], @@ -2425,9 +2608,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", + "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", "cpu": [ "riscv64" ], @@ -2439,9 +2622,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", + "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", "cpu": [ "s390x" ], @@ -2453,9 +2636,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", + "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", "cpu": [ "x64" ], @@ -2467,9 +2650,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", + "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", "cpu": [ "x64" ], @@ -2481,9 +2664,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", + "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", "cpu": [ "arm64" ], @@ -2495,9 +2678,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", + "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", "cpu": [ "ia32" ], @@ -2509,9 +2692,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", + "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", "cpu": [ "x64" ], @@ -2530,11 +2713,16 @@ "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.41.tgz", - "integrity": "sha512-v6P2dfqJDpZ/7RXPvWge9oI6YgolDM0jtNhQZ2qdXrLBzaWQdDoBGBTJ8KN/nTgGhX3IkNvSB1fafXQ+nVnqAQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.4.tgz", + "integrity": "sha512-bCq2GCuKV16DSOOEdaRqHMm1Ok4YEoLoNdgdzp8BS/Hxxr/0NVCHBUgRLLRy/TlJGv20Idx+djd5FIDvsnqMaw==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.24" + }, "engines": { "node": ">=10" }, @@ -2543,26 +2731,35 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.41", - "@swc/core-darwin-x64": "1.3.41", - "@swc/core-linux-arm-gnueabihf": "1.3.41", - "@swc/core-linux-arm64-gnu": "1.3.41", - "@swc/core-linux-arm64-musl": "1.3.41", - "@swc/core-linux-x64-gnu": "1.3.41", - "@swc/core-linux-x64-musl": "1.3.41", - "@swc/core-win32-arm64-msvc": "1.3.41", - "@swc/core-win32-ia32-msvc": "1.3.41", - "@swc/core-win32-x64-msvc": "1.3.41" + "@swc/core-darwin-arm64": "1.13.4", + "@swc/core-darwin-x64": "1.13.4", + "@swc/core-linux-arm-gnueabihf": "1.13.4", + "@swc/core-linux-arm64-gnu": "1.13.4", + "@swc/core-linux-arm64-musl": "1.13.4", + "@swc/core-linux-x64-gnu": "1.13.4", + "@swc/core-linux-x64-musl": "1.13.4", + "@swc/core-win32-arm64-msvc": "1.13.4", + "@swc/core-win32-ia32-msvc": "1.13.4", + "@swc/core-win32-x64-msvc": "1.13.4" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.41.tgz", - "integrity": "sha512-D4fybODToO/BvuP35bionDUrSuTVVr8eW+mApr1unOqb3mfiqOrVv0VP2fpWNRYiA+xMq+oBCB6KcGpL60HKWQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.4.tgz", + "integrity": "sha512-CGbTu9dGBwgklUj+NAQAYyPjBuoHaNRWK4QXJRv1QNIkhtE27aY7QA9uEON14SODxsio3t8+Pjjl2Mzx1Pxf+g==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2572,13 +2769,14 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.41.tgz", - "integrity": "sha512-0RoVyiPCnylf3TG77C3S86PRSmaq+SaYB4VDLJFz3qcEHz1pfP0LhyskhgX4wjQV1mveDzFEn1BVAuo0eOMwZA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.4.tgz", + "integrity": "sha512-qLFwYmLrqHNCf+JO9YLJT6IP/f9LfbXILTaqyfluFLW1GCfJyvUrSt3CWaL2lwwyT1EbBh6BVaAAecXiJIo3vg==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2588,13 +2786,14 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.41.tgz", - "integrity": "sha512-mZW7GeY7Uw1nkKoWpx898ou20oCSt8MR+jAVuAhMjX+G4Zr0WWXYSigWNiRymhR6Q9KhyvoFpMckguSvYWmXsw==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.4.tgz", + "integrity": "sha512-y7SeNIA9em3+smNMpr781idKuNwJNAqewiotv+pIR5FpXdXXNjHWW+jORbqQYd61k6YirA5WQv+Af4UzqEX17g==", "cpu": [ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2604,13 +2803,14 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.41.tgz", - "integrity": "sha512-e91LGn+6KuLFw3sWk5swwGc/dP4tXs0mg3HrhjImRoofU02Bb9aHcj5zgrSO8ZByvDtm/Knn16h1ojxIMOFaxg==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.4.tgz", + "integrity": "sha512-u0c51VdzRmXaphLgghY9+B2Frzler6nIv+J788nqIh6I0ah3MmMW8LTJKZfdaJa3oFxzGNKXsJiaU2OFexNkug==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2620,13 +2820,14 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.41.tgz", - "integrity": "sha512-Q7hmrniLWsQ7zjtImGcjx1tl5/Qxpel+fC+OXTnGvAyyoGssSftIBlXMnqVLteL78zhxIPAzi+gizWAe5RGqrA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.4.tgz", + "integrity": "sha512-Z92GJ98x8yQHn4I/NPqwAQyHNkkMslrccNVgFcnY1msrb6iGSw5uFg2H2YpvQ5u2/Yt6CRpLIUVVh8SGg1+gFA==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2636,13 +2837,14 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.41.tgz", - "integrity": "sha512-h4sv1sCfZQgRIwmykz8WPqVpbvHb13Qm3SsrbOudhAp2MuzpWzsgMP5hAEpdCP/nWreiCz3aoM6L8JeakRDq0g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.4.tgz", + "integrity": "sha512-rSUcxgpFF0L8Fk1CbUf946XCX1CRp6eaHfKqplqFNWCHv8HyqAtSFvgCHhT+bXru6Ca/p3sLC775SUeSWhsJ9w==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2652,13 +2854,14 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.41.tgz", - "integrity": "sha512-Z7c26i38378d0NT/dcz8qPSAXm41lqhNzykdhKhI+95mA9m4pskP18T/0I45rmyx1ywifypu+Ip+SXmKeVSPgQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.4.tgz", + "integrity": "sha512-qY77eFUvmdXNSmTW+I1fsz4enDuB0I2fE7gy6l9O4koSfjcCxkXw2X8x0lmKLm3FRiINS1XvZSg2G+q4NNQCRQ==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2668,13 +2871,14 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.41.tgz", - "integrity": "sha512-I0CYnPc+ZGc912YeN0TykIOf/Q7yJQHRwDuhewwD6RkbiSEaVfSux5pAmmdoKw2aGMSq+cwLmgPe9HYLRNz+4w==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.4.tgz", + "integrity": "sha512-xjPeDrOf6elCokxuyxwoskM00JJFQMTT2hTQZE24okjG3JiXzSFV+TmzYSp+LWNxPpnufnUUy/9Ee8+AcpslGw==", "cpu": [ "arm64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2684,13 +2888,14 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.41.tgz", - "integrity": "sha512-EygN4CVDWF29/U2T5fXGfWyLvRbMd2hiUgkciAl7zHuyJ6nKl+kpodqV2A0Wd4sFtSNedU0gQEBEXEe7cqvmsA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.4.tgz", + "integrity": "sha512-Ta+Bblc9tE9X9vQlpa3r3+mVnHYdKn09QsZ6qQHvuXGKWSS99DiyxKTYX2vxwMuoTObR0BHvnhNbaGZSV1VwNA==", "cpu": [ "ia32" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2700,13 +2905,14 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.41.tgz", - "integrity": "sha512-Mfp8qD1hNwWWRy0ISdwQJu1g0UYoVTtuQlO0z3aGbXqL51ew9e56+8j3M1U9i95lXFyWkARgjDCcKkQi+WezyA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.4.tgz", + "integrity": "sha512-pHnb4QwGiuWs4Z9ePSgJ48HP3NZIno6l75SB8YLCiPVDiLhvCLKEjz/caPRsFsmet9BEP8e3bAf2MV8MXgaTSg==", "cpu": [ "x64" ], "dev": true, + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2715,12 +2921,30 @@ "node": ">=10" } }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.24", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.24.tgz", + "integrity": "sha512-tjTMh3V4vAORHtdTprLlfoMptu1WfTZG9Rsca6yOKyNYsRr+MUXutKmliB17orgSZk5DpnDxs8GUdd/qwYxOng==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" } }, "node_modules/@testing-library/dom": { @@ -2744,21 +2968,10 @@ "node": ">=18" } }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, "node_modules/@testing-library/jest-dom": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.7.0.tgz", - "integrity": "sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz", + "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2857,10 +3070,11 @@ "license": "MIT" }, "node_modules/@types/eslint": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", - "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/estree": "*", @@ -2868,10 +3082,11 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/eslint": "*", @@ -2879,25 +3094,29 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "license": "MIT", "dependencies": { - "@types/react": "*", "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" } }, "node_modules/@types/is-hotkey": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz", - "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz", + "integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==", + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -2907,9 +3126,10 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.14.191", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" }, "node_modules/@types/node": { "version": "24.3.0", @@ -2922,14 +3142,16 @@ } }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.23", @@ -2942,10 +3164,11 @@ } }, "node_modules/@types/react-copy-to-clipboard": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz", - "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.7.tgz", + "integrity": "sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -2961,26 +3184,20 @@ } }, "node_modules/@types/react-grid-layout": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz", - "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz", + "integrity": "sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ==", + "license": "MIT", "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "dependencies": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { "@types/react": "*" } }, @@ -3001,20 +3218,21 @@ "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", - "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", + "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/type-utils": "8.39.1", - "@typescript-eslint/utils": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/type-utils": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3028,7 +3246,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.39.1", + "@typescript-eslint/parser": "^8.40.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -3044,16 +3262,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", - "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz", + "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "debug": "^4.3.4" }, "engines": { @@ -3069,14 +3287,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", - "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz", + "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.1", - "@typescript-eslint/types": "^8.39.1", + "@typescript-eslint/tsconfig-utils": "^8.40.0", + "@typescript-eslint/types": "^8.40.0", "debug": "^4.3.4" }, "engines": { @@ -3091,14 +3309,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", - "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz", + "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1" + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3109,9 +3327,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", - "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz", + "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==", "dev": true, "license": "MIT", "engines": { @@ -3126,15 +3344,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", - "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", + "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3151,9 +3369,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", - "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz", + "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==", "dev": true, "license": "MIT", "engines": { @@ -3165,16 +3383,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", - "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz", + "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.39.1", - "@typescript-eslint/tsconfig-utils": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", + "@typescript-eslint/project-service": "8.40.0", + "@typescript-eslint/tsconfig-utils": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3220,16 +3438,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", - "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", + "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1" + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3244,13 +3462,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", - "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz", + "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.1", + "@typescript-eslint/types": "8.40.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3262,15 +3480,17 @@ } }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", - "integrity": "sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", + "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", "dev": true, + "license": "MIT", "dependencies": { - "@swc/core": "^1.3.35" + "@rolldown/pluginutils": "1.0.0-beta.27", + "@swc/core": "^1.12.11" }, "peerDependencies": { - "vite": "^4" + "vite": "^4 || ^5 || ^6 || ^7" } }, "node_modules/@vitest/coverage-v8": { @@ -3396,163 +3616,178 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -3561,6 +3796,7 @@ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, + "license": "BSD-3-Clause", "peer": true }, "node_modules/@xtuc/long": { @@ -3568,6 +3804,7 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, + "license": "Apache-2.0", "peer": true }, "node_modules/acorn": { @@ -3583,14 +3820,18 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, + "license": "MIT", "peer": true, + "engines": { + "node": ">=10.13.0" + }, "peerDependencies": { - "acorn": "^8" + "acorn": "^8.14.0" } }, "node_modules/acorn-jsx": { @@ -3618,6 +3859,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3629,11 +3871,57 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -3678,14 +3966,19 @@ } }, "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/argparse": { @@ -3696,13 +3989,13 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "dependencies": { + "dequal": "^2.0.3" } }, "node_modules/array-buffer-byte-length": { @@ -3854,12 +4147,14 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/attr-accept": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", - "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -3891,12 +4186,13 @@ } }, "node_modules/axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3914,6 +4210,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -3925,25 +4222,21 @@ } }, "node_modules/babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" }, "peerDependencies": { "styled-components": ">= 2" } }, - "node_modules/babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3956,6 +4249,7 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -3963,7 +4257,8 @@ "node_modules/blueimp-canvas-to-blob": { "version": "3.29.0", "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", - "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" + "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.12", @@ -3990,10 +4285,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", "funding": [ { "type": "opencollective", @@ -4002,14 +4296,19 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -4023,6 +4322,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/cac": { @@ -4058,7 +4358,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4089,6 +4388,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4097,15 +4397,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001469", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", - "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", - "dev": true, + "version": "1.0.30001736", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz", + "integrity": "sha512-ImpN5gLEY8gWeqfLUyEF4b7mYWcYoR2Si1VhnrbM4JizRFmfGaAQ12PhNykq6nvI4XvKLrsp8Xde74D5phJOSw==", "funding": [ { "type": "opencollective", @@ -4114,14 +4414,19 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "CC-BY-4.0", "peer": true }, "node_modules/chai": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", - "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.1.tgz", + "integrity": "sha512-48af6xm9gQK8rhIcOxWwdGzIervm8BVTin+yRp9HEvU20BtVZ2lBywlIJBzwaDtvo0FvjeL7QdCADoUoqIbV3A==", "dev": true, "license": "MIT", "dependencies": { @@ -4136,24 +4441,20 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/check-error": { @@ -4167,19 +4468,21 @@ } }, "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6.0" } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/cli-width": { "version": "4.1.0", @@ -4206,42 +4509,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4296,30 +4563,39 @@ } }, "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -4332,12 +4608,14 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/compressorjs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz", "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==", + "license": "MIT", "dependencies": { "blueimp-canvas-to-blob": "^3.29.0", "is-blob": "^2.1.0" @@ -4346,7 +4624,8 @@ "node_modules/compute-scroll-into-view": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -4358,7 +4637,8 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", @@ -4374,6 +4654,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", "dependencies": { "toggle-selection": "^1.0.6" } @@ -4382,6 +4663,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -4412,6 +4694,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", "engines": { "node": ">=4" } @@ -4420,6 +4703,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", @@ -4448,9 +4732,10 @@ } }, "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -4548,7 +4833,6 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, "license": "MIT" }, "node_modules/deep-eql": { @@ -4608,6 +4892,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -4618,7 +4903,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -4637,6 +4921,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", + "license": "MIT", "bin": { "direction": "cli.js" }, @@ -4649,6 +4934,7 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", + "license": "MIT", "dependencies": { "@react-dnd/asap": "^5.0.1", "@react-dnd/invariant": "^4.0.1", @@ -4667,6 +4953,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -4676,7 +4963,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -4695,10 +4981,10 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.334", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz", - "integrity": "sha512-laZ1odk+TRen6q0GeyQx/JEkpD3iSZT7ewopCpKqg9bTjP1l8XRfU3Bg20CFjNPZkp5+NDBl3iqd4o/kPO+Vew==", - "dev": true, + "version": "1.5.208", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.208.tgz", + "integrity": "sha512-ozZyibehoe7tOhNaf16lKmljVf+3npZcJIEbJRVftVsmAg5TeA1mGS9dVCZzOwr2xT7xK15V0p7+GZqSPgkuPg==", + "license": "ISC", "peer": true }, "node_modules/emoji-regex": { @@ -4713,15 +4999,17 @@ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -4748,6 +5036,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -4825,7 +5114,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4835,24 +5123,22 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -4865,7 +5151,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4909,11 +5194,12 @@ } }, "node_modules/esbuild": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", - "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -4921,35 +5207,35 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.11", - "@esbuild/android-arm64": "0.17.11", - "@esbuild/android-x64": "0.17.11", - "@esbuild/darwin-arm64": "0.17.11", - "@esbuild/darwin-x64": "0.17.11", - "@esbuild/freebsd-arm64": "0.17.11", - "@esbuild/freebsd-x64": "0.17.11", - "@esbuild/linux-arm": "0.17.11", - "@esbuild/linux-arm64": "0.17.11", - "@esbuild/linux-ia32": "0.17.11", - "@esbuild/linux-loong64": "0.17.11", - "@esbuild/linux-mips64el": "0.17.11", - "@esbuild/linux-ppc64": "0.17.11", - "@esbuild/linux-riscv64": "0.17.11", - "@esbuild/linux-s390x": "0.17.11", - "@esbuild/linux-x64": "0.17.11", - "@esbuild/netbsd-x64": "0.17.11", - "@esbuild/openbsd-x64": "0.17.11", - "@esbuild/sunos-x64": "0.17.11", - "@esbuild/win32-arm64": "0.17.11", - "@esbuild/win32-ia32": "0.17.11", - "@esbuild/win32-x64": "0.17.11" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4958,6 +5244,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -5072,6 +5359,16 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", @@ -5086,93 +5383,6 @@ } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", @@ -5189,37 +5399,17 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "Apache-2.0", "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { @@ -5253,21 +5443,12 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5275,21 +5456,12 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -5304,13 +5476,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/estree-walker/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5326,6 +5491,7 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=0.8.x" @@ -5344,7 +5510,14 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz", + "integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg==", + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", @@ -5380,7 +5553,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -5389,6 +5563,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -5413,11 +5605,12 @@ } }, "node_modules/file-selector": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", - "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.7.0" }, "engines": { "node": ">= 12" @@ -5439,7 +5632,8 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -5480,15 +5674,16 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -5532,12 +5727,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -5599,6 +5797,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5613,7 +5821,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -5638,7 +5845,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -5705,6 +5911,7 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, + "license": "BSD-2-Clause", "peer": true }, "node_modules/glob/node_modules/brace-expansion": { @@ -5734,11 +5941,16 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -5762,7 +5974,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5776,6 +5987,7 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, + "license": "ISC", "peer": true }, "node_modules/graphemer": { @@ -5795,17 +6007,6 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -5820,11 +6021,13 @@ } }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/has-property-descriptors": { @@ -5860,7 +6063,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5873,7 +6075,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -5889,7 +6090,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5909,6 +6109,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } @@ -5916,7 +6117,8 @@ "node_modules/hoist-non-react-statics/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", @@ -5992,21 +6194,24 @@ "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/immer": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", - "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==", + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6054,14 +6259,15 @@ } }, "node_modules/intl-messageformat": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.3.tgz", - "integrity": "sha512-un/f07/g2e/3Q8e1ghDKET+el22Bi49M7O/rHxd597R+oLpPOMykSv5s51cABVfu3FZW+fea4hrzf2MHu1W4hw==", + "version": "10.7.16", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", + "integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==", + "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "1.14.3", - "@formatjs/fast-memoize": "2.0.1", - "@formatjs/icu-messageformat-parser": "2.3.0", - "tslib": "^2.4.0" + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.2", + "tslib": "^2.8.0" } }, "node_modules/is-array-buffer": { @@ -6085,7 +6291,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -6127,6 +6334,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", + "license": "MIT", "engines": { "node": ">=6" }, @@ -6165,11 +6373,15 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6281,7 +6493,8 @@ "node_modules/is-hotkey": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", - "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" + "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==", + "license": "MIT" }, "node_modules/is-map": { "version": "2.0.3", @@ -6347,6 +6560,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6542,29 +6756,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-source-maps": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", @@ -6581,9 +6772,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6626,22 +6817,6 @@ "node": ">= 16.0.0" } }, - "node_modules/jest-axe/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/jest-axe/node_modules/axe-core": { "version": "4.10.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", @@ -6652,66 +6827,6 @@ "node": ">=4" } }, - "node_modules/jest-axe/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-axe/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-axe/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-axe/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-axe/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -6729,66 +6844,16 @@ } }, "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-diff/node_modules/pretty-format": { @@ -6806,31 +6871,12 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-diff/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/jest-get-type": { "version": "29.6.3", @@ -6859,66 +6905,16 @@ } }, "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { @@ -6936,37 +6932,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/node": "*", @@ -6977,21 +6955,12 @@ "node": ">= 10.13.0" } }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "has-flag": "^4.0.0" @@ -7006,7 +6975,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -7062,14 +7032,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -7082,13 +7053,15 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7101,7 +7074,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -7173,6 +7146,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } @@ -7180,13 +7154,15 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6.11.5" @@ -7197,6 +7173,7 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dev": true, + "license": "MIT", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -7210,6 +7187,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", "dependencies": { "lie": "3.1.1" } @@ -7233,12 +7211,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -7251,6 +7225,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -7259,9 +7234,9 @@ } }, "node_modules/loupe": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", - "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", "dev": true, "license": "MIT" }, @@ -7325,7 +7300,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -7336,6 +7310,7 @@ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/merge2": { @@ -7366,6 +7341,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7374,6 +7350,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -7415,9 +7392,10 @@ } }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", "engines": { "node": "*" } @@ -7514,13 +7492,14 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true, + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT", "peer": true }, "node_modules/nwsapi": { @@ -7534,6 +7513,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7706,6 +7686,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -7717,6 +7698,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -7766,7 +7748,8 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", @@ -7796,6 +7779,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", "engines": { "node": ">=8" } @@ -7821,6 +7805,7 @@ "version": "1.4.6", "resolved": "https://registry.npmjs.org/philliplm-react-modern-audio-player/-/philliplm-react-modern-audio-player-1.4.6.tgz", "integrity": "sha512-2C/1lpQJmD0gWJMVt6k/QNZoakxHwxGhRFyw17OVAwbpXmptisr9aHDzT/VPFKoIr5qj+y1WajJ4lnghbfzx9Q==", + "license": "MIT", "dependencies": { "@react-spectrum/layout": "^3.3.1", "@react-spectrum/provider": "^3.4.1", @@ -7840,13 +7825,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -7896,7 +7881,8 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -7966,6 +7952,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -7975,12 +7962,14 @@ "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/psl": { "version": "1.15.0", @@ -8038,15 +8027,17 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -8058,6 +8049,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "license": "MIT", "dependencies": { "copy-to-clipboard": "^3.3.1", "prop-types": "^15.8.1" @@ -8070,6 +8062,7 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "license": "MIT", "dependencies": { "@react-dnd/invariant": "^4.0.1", "@react-dnd/shallowequal": "^4.0.1", @@ -8099,28 +8092,31 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", + "license": "MIT", "dependencies": { "dnd-core": "^16.0.1" } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-draggable": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", - "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.5.0.tgz", + "integrity": "sha512-VC+HBLEZ0XJxnOxVAZsdRi8rD04Iz3SiiKOoYzamjylUcju/hP9np/aZdLHf/7WOD268WMoNJMvYfB5yAK45cw==", + "license": "MIT", "dependencies": { - "clsx": "^1.1.1", + "clsx": "^2.1.1", "prop-types": "^15.8.1" }, "peerDependencies": { @@ -8129,12 +8125,13 @@ } }, "node_modules/react-dropzone": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", - "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "version": "14.3.8", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz", + "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==", + "license": "MIT", "dependencies": { - "attr-accept": "^2.2.2", - "file-selector": "^0.6.0", + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", "prop-types": "^15.8.1" }, "engines": { @@ -8145,15 +8142,17 @@ } }, "node_modules/react-grid-layout": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", - "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.2.tgz", + "integrity": "sha512-vT7xmQqszTT+sQw/LfisrEO4le1EPNnSEMVHy6sBZyzS3yGkMywdOd+5iEFFwQwt0NSaGkxuRmYwa1JsP6OJdw==", + "license": "MIT", "dependencies": { - "clsx": "^1.1.1", - "lodash.isequal": "^4.0.0", + "clsx": "^2.1.1", + "fast-equals": "^4.0.3", "prop-types": "^15.8.1", - "react-draggable": "^4.0.0", - "react-resizable": "^3.0.4" + "react-draggable": "^4.4.6", + "react-resizable": "^3.0.5", + "resize-observer-polyfill": "^1.5.1" }, "peerDependencies": { "react": ">= 16.3.0", @@ -8161,38 +8160,49 @@ } }, "node_modules/react-icons": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", - "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", + "license": "MIT", "peerDependencies": { "react": "*" } }, "node_modules/react-intersection-observer": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz", - "integrity": "sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz", + "integrity": "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==", + "license": "MIT", "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", + "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "license": "MIT" }, "node_modules/react-masonry-css": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz", "integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==", + "license": "MIT", "peerDependencies": { "react": ">=16.0.0" } }, "node_modules/react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.1", "@types/hoist-non-react-statics": "^3.3.1", @@ -8207,7 +8217,7 @@ "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0", "react-native": ">=0.59", - "redux": "^4" + "redux": "^4 || ^5.0.0-beta.0" }, "peerDependenciesMeta": { "@types/react": { @@ -8227,10 +8237,17 @@ } } }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/react-resizable": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", + "license": "MIT", "dependencies": { "prop-types": "15.x", "react-draggable": "^4.0.3" @@ -8240,9 +8257,10 @@ } }, "node_modules/react-resize-detector": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.0.4.tgz", - "integrity": "sha512-ln9pMAob8y8mc9UI4aZuuWFiyMqBjnTs/sxe9Vs9dPXUjwCTeKK1FP8I75ufnb/2mEEZXG6wOo/fjMcBRRuAXw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz", + "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==", + "license": "MIT", "dependencies": { "lodash": "^4.17.21" }, @@ -8252,29 +8270,31 @@ } }, "node_modules/react-router": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", - "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.4.0" + "@remix-run/router": "1.23.0" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8" } }, "node_modules/react-router-dom": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", - "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", "dependencies": { - "@remix-run/router": "1.4.0", - "react-router": "6.9.0" + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" }, "engines": { - "node": ">=14" + "node": ">=14.0.0" }, "peerDependencies": { "react": ">=16.8", @@ -8282,9 +8302,10 @@ } }, "node_modules/react-toastify": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.2.tgz", - "integrity": "sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "license": "MIT", "dependencies": { "clsx": "^1.1.1" }, @@ -8293,10 +8314,20 @@ "react-dom": ">=16" } }, + "node_modules/react-toastify/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -8309,15 +8340,13 @@ } }, "node_modules/react-virtuoso": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.3.3.tgz", - "integrity": "sha512-x0DeGmVAVOVaTXRMG7jzrHBwK7+dkt7n0G3tNmZXphQUBgkVBYuZoaJltQeZGFN42++3XvrgwStKCtmzgMJ0lA==", - "engines": { - "node": ">=10" - }, + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.14.0.tgz", + "integrity": "sha512-fR+eiCvirSNIRvvCD7ueJPRsacGQvUbjkwgWzBZXVq+yWypoH7mRUvWJzGHIdoRaCZCT+6mMMMwIG2S1BW3uwA==", + "license": "MIT", "peerDependencies": { - "react": ">=16 || >=17 || >= 18", - "react-dom": ">=16 || >=17 || >= 18" + "react": ">=16 || >=17 || >= 18 || >= 19", + "react-dom": ">=16 || >=17 || >= 18 || >=19" } }, "node_modules/redent": { @@ -8338,6 +8367,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.9.2" } @@ -8346,6 +8376,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "license": "MIT", "peerDependencies": { "redux": "^4" } @@ -8373,11 +8404,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -8409,6 +8435,17 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -8417,22 +8454,33 @@ "license": "MIT" }, "node_modules/reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8441,6 +8489,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -8457,10 +8506,11 @@ } }, "node_modules/rollup": { - "version": "3.19.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz", - "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -8542,6 +8592,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "peer": true }, "node_modules/safe-push-apply": { @@ -8600,22 +8651,26 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } }, "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -8625,10 +8680,51 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/scroll-into-view-if-needed": { "version": "2.2.31", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", "dependencies": { "compute-scroll-into-view": "^1.0.20" } @@ -8647,10 +8743,11 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "randombytes": "^2.1.0" @@ -8708,7 +8805,8 @@ "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -8737,6 +8835,7 @@ "version": "4.4.4", "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz", "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw==", + "license": "Apache-2.0", "bin": { "short-unique-id": "bin/short-unique-id", "suid": "bin/short-unique-id" @@ -8842,6 +8941,7 @@ "version": "0.91.4", "resolved": "https://registry.npmjs.org/slate/-/slate-0.91.4.tgz", "integrity": "sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==", + "license": "MIT", "dependencies": { "immer": "^9.0.6", "is-plain-object": "^5.0.0", @@ -8852,6 +8952,7 @@ "version": "0.86.0", "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.86.0.tgz", "integrity": "sha512-OxObL9tbhgwvSlnKSCpGIh7wnuaqvOj5jRExGjEyCU2Ke8ctf22HjT+jw7GEi9ttLzNTUmTEU3YIzqKGeqN+og==", + "license": "MIT", "dependencies": { "is-plain-object": "^5.0.0" }, @@ -8863,6 +8964,7 @@ "version": "0.91.11", "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.91.11.tgz", "integrity": "sha512-2nS29rc2kuTTJrEUOXGyTkFATmTEw/R9KuUXadUYiz+UVwuFOUMnBKuwJWyuIBOsFipS+06SkIayEf5CKdARRQ==", + "license": "MIT", "dependencies": { "@juggle/resize-observer": "^3.4.0", "@types/is-hotkey": "^0.1.1", @@ -8884,6 +8986,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -8903,6 +9006,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -8914,6 +9018,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "engines": { "node": ">=0.10.0" @@ -9123,9 +9228,9 @@ } }, "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", "dev": true, "license": "MIT", "engines": { @@ -9182,9 +9287,10 @@ "license": "MIT" }, "node_modules/styled-components": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", - "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", @@ -9213,17 +9319,23 @@ "node_modules/styled-components/node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" }, - "node_modules/stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" + "node_modules/styled-components/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/supports-color": { + "node_modules/styled-components/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -9231,10 +9343,30 @@ "node": ">=4" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -9250,24 +9382,26 @@ "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -9279,17 +9413,18 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -9357,12 +9492,14 @@ "node_modules/tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==", + "license": "MIT" }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", @@ -9492,7 +9629,8 @@ "node_modules/toggle-selection": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" }, "node_modules/tough-cookie": { "version": "5.1.2", @@ -9534,14 +9672,16 @@ } }, "node_modules/ts-key-enum": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.12.tgz", - "integrity": "sha512-Ety4IvKMaeG34AyXMp5r11XiVZNDRL+XWxXbVVJjLvq2vxKRttEANBE7Za1bxCAZRdH2/sZT6jFyyTWxXz28hw==" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.13.tgz", + "integrity": "sha512-zixs6j8+NhzazLUQ1SiFrlo1EFWG/DbqLuUGcWWZ5zhwjRT7kbi1hBlofxdqel+h28zrby2It5TrOyKp04kvqw==", + "license": "MIT" }, "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -9662,16 +9802,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz", - "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.40.0.tgz", + "integrity": "sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.39.1", - "@typescript-eslint/parser": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1" + "@typescript-eslint/eslint-plugin": "8.40.0", + "@typescript-eslint/parser": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9722,10 +9862,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "funding": [ { "type": "opencollective", @@ -9734,15 +9873,20 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -9753,6 +9897,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -9769,23 +9914,24 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/vite": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.0.tgz", - "integrity": "sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==", + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.21", - "resolve": "^1.22.1", - "rollup": "^3.18.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -9793,12 +9939,16 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", + "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", @@ -9811,6 +9961,9 @@ "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -10222,20 +10375,6 @@ "node": ">=18" } }, - "node_modules/vite-node/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite-node/node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, "node_modules/vite-node/node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -10310,9 +10449,9 @@ } }, "node_modules/vite-node/node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", + "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", "dev": true, "license": "MIT", "dependencies": { @@ -10326,38 +10465,38 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", + "@rollup/rollup-android-arm-eabi": "4.47.1", + "@rollup/rollup-android-arm64": "4.47.1", + "@rollup/rollup-darwin-arm64": "4.47.1", + "@rollup/rollup-darwin-x64": "4.47.1", + "@rollup/rollup-freebsd-arm64": "4.47.1", + "@rollup/rollup-freebsd-x64": "4.47.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", + "@rollup/rollup-linux-arm-musleabihf": "4.47.1", + "@rollup/rollup-linux-arm64-gnu": "4.47.1", + "@rollup/rollup-linux-arm64-musl": "4.47.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", + "@rollup/rollup-linux-ppc64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-musl": "4.47.1", + "@rollup/rollup-linux-s390x-gnu": "4.47.1", + "@rollup/rollup-linux-x64-gnu": "4.47.1", + "@rollup/rollup-linux-x64-musl": "4.47.1", + "@rollup/rollup-win32-arm64-msvc": "4.47.1", + "@rollup/rollup-win32-ia32-msvc": "4.47.1", + "@rollup/rollup-win32-x64-msvc": "4.47.1", "fsevents": "~2.3.2" } }, "node_modules/vite-node/node_modules/vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", @@ -10424,6 +10563,21 @@ } } }, + "node_modules/vite-node/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -10871,13 +11025,6 @@ "node": ">=18" } }, - "node_modules/vitest/node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/vitest/node_modules/@vitest/mocker": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", @@ -10979,9 +11126,9 @@ } }, "node_modules/vitest/node_modules/rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", + "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", "dev": true, "license": "MIT", "dependencies": { @@ -10995,38 +11142,38 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", + "@rollup/rollup-android-arm-eabi": "4.47.1", + "@rollup/rollup-android-arm64": "4.47.1", + "@rollup/rollup-darwin-arm64": "4.47.1", + "@rollup/rollup-darwin-x64": "4.47.1", + "@rollup/rollup-freebsd-arm64": "4.47.1", + "@rollup/rollup-freebsd-x64": "4.47.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", + "@rollup/rollup-linux-arm-musleabihf": "4.47.1", + "@rollup/rollup-linux-arm64-gnu": "4.47.1", + "@rollup/rollup-linux-arm64-musl": "4.47.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", + "@rollup/rollup-linux-ppc64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-musl": "4.47.1", + "@rollup/rollup-linux-s390x-gnu": "4.47.1", + "@rollup/rollup-linux-x64-gnu": "4.47.1", + "@rollup/rollup-linux-x64-musl": "4.47.1", + "@rollup/rollup-win32-arm64-msvc": "4.47.1", + "@rollup/rollup-win32-ia32-msvc": "4.47.1", + "@rollup/rollup-win32-x64-msvc": "4.47.1", "fsevents": "~2.3.2" } }, "node_modules/vitest/node_modules/vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", + "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", @@ -11093,6 +11240,21 @@ } } }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -11107,10 +11269,11 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -11121,9 +11284,10 @@ } }, "node_modules/wavesurfer.js": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.2.tgz", - "integrity": "sha512-aPAU4OADQsyH8mIw2nXmoni8KHo8s1f1bd5ZUrxhN4P/VMWd+oPDqEwA01XPSEfasAJW6mZ/EHQ2bZ9nOWRrNw==" + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.4.tgz", + "integrity": "sha512-nBbc0pD/3FdClxKUKL1UW2V9AJPL+JOjC8T6/YF9/FCAn4uo+H6Y8VBkXo9UJXIHoBewoc7iXj3tPeL0UCJhjA==", + "license": "BSD-3-Clause" }, "node_modules/webidl-conversions": { "version": "7.0.0", @@ -11136,36 +11300,38 @@ } }, "node_modules/webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", + "schema-utils": "^4.3.2", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -11184,15 +11350,42 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -11367,6 +11560,7 @@ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", "dev": true, + "license": "MIT", "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -11382,22 +11576,38 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/worker-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" }, "engines": { - "node": ">=12" + "node": ">= 10.13.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/wrap-ansi-cjs": { @@ -11419,42 +11629,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -11490,17 +11664,39 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/ws": { @@ -11552,10 +11748,18 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC", + "peer": true + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -11638,9 +11842,9 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "dev": true, "license": "MIT", "engines": { @@ -11650,7420 +11854,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true - }, - "@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", - "dev": true, - "requires": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", - "requires": { - "@babel/types": "^7.21.3", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" - }, - "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", - "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" - }, - "@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", - "requires": { - "@babel/types": "^7.28.2" - } - }, - "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "requires": { - "regenerator-runtime": "^0.13.11" - } - }, - "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", - "requires": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - } - }, - "@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", - "dev": true - }, - "@bundled-es-modules/cookie": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", - "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", - "dev": true, - "requires": { - "cookie": "^0.7.2" - } - }, - "@bundled-es-modules/statuses": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", - "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", - "dev": true, - "requires": { - "statuses": "^2.0.1" - } - }, - "@bundled-es-modules/tough-cookie": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", - "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", - "dev": true, - "requires": { - "@types/tough-cookie": "^4.0.5", - "tough-cookie": "^4.1.4" - }, - "dependencies": { - "tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - } - } - }, - "@csstools/color-helpers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", - "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", - "dev": true - }, - "@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true, - "requires": {} - }, - "@csstools/css-color-parser": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", - "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", - "dev": true, - "requires": { - "@csstools/color-helpers": "^5.0.2", - "@csstools/css-calc": "^2.1.4" - } - }, - "@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "dev": true, - "requires": {} - }, - "@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "dev": true - }, - "@emotion/babel-plugin": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.6.tgz", - "integrity": "sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==", - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/serialize": "^1.1.1", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.1.3" - } - }, - "@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", - "requires": { - "@emotion/memoize": "^0.8.0", - "@emotion/sheet": "^1.2.1", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" - } - }, - "@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" - }, - "@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", - "requires": { - "@emotion/memoize": "^0.8.0" - } - }, - "@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "@emotion/react": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.6.tgz", - "integrity": "sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/cache": "^11.10.5", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0", - "@emotion/weak-memoize": "^0.3.0", - "hoist-non-react-statics": "^3.3.1" - } - }, - "@emotion/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==", - "requires": { - "@emotion/hash": "^0.9.0", - "@emotion/memoize": "^0.8.0", - "@emotion/unitless": "^0.8.0", - "@emotion/utils": "^1.2.0", - "csstype": "^3.0.2" - } - }, - "@emotion/sheet": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.1.tgz", - "integrity": "sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==" - }, - "@emotion/styled": { - "version": "11.10.6", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.10.6.tgz", - "integrity": "sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.10.6", - "@emotion/is-prop-valid": "^1.2.0", - "@emotion/serialize": "^1.1.1", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", - "@emotion/utils": "^1.2.0" - } - }, - "@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, - "@emotion/unitless": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", - "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" - }, - "@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", - "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", - "requires": {} - }, - "@emotion/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" - }, - "@emotion/weak-memoize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", - "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" - }, - "@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.11.tgz", - "integrity": "sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.11.tgz", - "integrity": "sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.11.tgz", - "integrity": "sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.11.tgz", - "integrity": "sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.11.tgz", - "integrity": "sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.11.tgz", - "integrity": "sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.11.tgz", - "integrity": "sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.11.tgz", - "integrity": "sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.11.tgz", - "integrity": "sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.11.tgz", - "integrity": "sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.11.tgz", - "integrity": "sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.11.tgz", - "integrity": "sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.11.tgz", - "integrity": "sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.11.tgz", - "integrity": "sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.11.tgz", - "integrity": "sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.11.tgz", - "integrity": "sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.11.tgz", - "integrity": "sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.11.tgz", - "integrity": "sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==", - "dev": true, - "optional": true - }, - "@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.11.tgz", - "integrity": "sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.11.tgz", - "integrity": "sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.11.tgz", - "integrity": "sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz", - "integrity": "sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==", - "dev": true, - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.4.3" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - } - } - }, - "@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true - }, - "@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "dev": true, - "requires": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - } - }, - "@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", - "dev": true - }, - "@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.15" - } - }, - "@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true - } - } - }, - "@eslint/js": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", - "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", - "dev": true - }, - "@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true - }, - "@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", - "dev": true, - "requires": { - "@eslint/core": "^0.15.2", - "levn": "^0.4.1" - } - }, - "@formatjs/ecma402-abstract": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", - "integrity": "sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==", - "requires": { - "@formatjs/intl-localematcher": "0.2.32", - "tslib": "^2.4.0" - } - }, - "@formatjs/fast-memoize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz", - "integrity": "sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==", - "requires": { - "tslib": "^2.4.0" - } - }, - "@formatjs/icu-messageformat-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz", - "integrity": "sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==", - "requires": { - "@formatjs/ecma402-abstract": "1.14.3", - "@formatjs/icu-skeleton-parser": "1.3.18", - "tslib": "^2.4.0" - } - }, - "@formatjs/icu-skeleton-parser": { - "version": "1.3.18", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz", - "integrity": "sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==", - "requires": { - "@formatjs/ecma402-abstract": "1.14.3", - "tslib": "^2.4.0" - } - }, - "@formatjs/intl-localematcher": { - "version": "0.2.32", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", - "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", - "requires": { - "tslib": "^2.4.0" - } - }, - "@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true - }, - "@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "requires": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "dependencies": { - "@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true - } - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true - }, - "@inquirer/confirm": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", - "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", - "dev": true, - "requires": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8" - } - }, - "@inquirer/core": { - "version": "10.1.15", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", - "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", - "dev": true, - "requires": { - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "@inquirer/figures": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", - "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", - "dev": true - }, - "@inquirer/type": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", - "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", - "dev": true, - "requires": {} - }, - "@internationalized/date": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.1.0.tgz", - "integrity": "sha512-wjeur7K4AecT+YwoBmBXQ/+n5lP69tuZc34hw09s44EozZK7FZHSyfPvRp5/xEb2D6abLboskDY4jG+Nt0TNUQ==", - "requires": { - "@swc/helpers": "^0.4.14" - } - }, - "@internationalized/message": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.0.tgz", - "integrity": "sha512-Oo5m70FcBdADf7G8NkUffVSfuCdeAYVfsvNjZDi9ELpjvkc4YNJVTHt/NyTI9K7FgAVoELxiP9YmN0sJ+HNHYQ==", - "requires": { - "@swc/helpers": "^0.4.14", - "intl-messageformat": "^10.1.0" - } - }, - "@internationalized/number": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.2.0.tgz", - "integrity": "sha512-GUXkhXSX1Ee2RURnzl+47uvbOxnlMnvP9Er+QePTjDjOPWuunmLKlEkYkEcLiiJp7y4l9QxGDLOlVr8m69LS5w==", - "requires": { - "@swc/helpers": "^0.4.14" - } - }, - "@internationalized/string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.1.0.tgz", - "integrity": "sha512-TJQKiyUb+wyAfKF59UNeZ/kELMnkxyecnyPCnBI1ma4NaXReJW+7Cc2mObXAqraIBJUVv7rgI46RLKrLgi35ng==", - "requires": { - "@swc/helpers": "^0.4.14" - } - }, - "@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "requires": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.27.8" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@juggle/resize-observer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" - }, - "@mswjs/interceptors": { - "version": "0.39.6", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.6.tgz", - "integrity": "sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==", - "dev": true, - "requires": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - } - }, - "@mui/base": { - "version": "5.0.0-alpha.121", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.121.tgz", - "integrity": "sha512-8nJRY76UqlJV+q/Yzo0tgGfPWEOa+4N9rjO81fMmcJqP0I6m54hLDXsjvMg4tvelY5eKHXUK6Tb7en+GHfTqZA==", - "requires": { - "@babel/runtime": "^7.21.0", - "@emotion/is-prop-valid": "^1.2.0", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "@popperjs/core": "^2.11.6", - "clsx": "^1.2.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - } - }, - "@mui/core-downloads-tracker": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.13.tgz", - "integrity": "sha512-lx+GXBR9h/ApZsEP728tl0pyZyuajto+VnBgsoAzw1d5+CbmOo8ZWieKwVUGxZlPT1wMYNUYS5NtKzCli0xYjw==" - }, - "@mui/icons-material": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.11.11.tgz", - "integrity": "sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==", - "requires": { - "@babel/runtime": "^7.21.0" - } - }, - "@mui/material": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.13.tgz", - "integrity": "sha512-2CnSj43F+159LbGmTLLQs5xbGYMiYlpTByQhP7c7cMX6opbScctBFE1PuyElpAmwW8Ag9ysfZH1d1MFAmJQkjg==", - "requires": { - "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-alpha.121", - "@mui/core-downloads-tracker": "^5.11.13", - "@mui/system": "^5.11.13", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "@types/react-transition-group": "^4.4.5", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - } - }, - "@mui/private-theming": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.13.tgz", - "integrity": "sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==", - "requires": { - "@babel/runtime": "^7.21.0", - "@mui/utils": "^5.11.13", - "prop-types": "^15.8.1" - } - }, - "@mui/styled-engine": { - "version": "5.11.11", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.11.tgz", - "integrity": "sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==", - "requires": { - "@babel/runtime": "^7.21.0", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - } - }, - "@mui/system": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.13.tgz", - "integrity": "sha512-OWP0Alp6C8ufnGm9+CZcl3d+OoRXL2PnrRT5ohaMLxvGL9OfNcL2t4JOjMmA0k1UAGd6E/Ygbu5lEPrZSDlvCg==", - "requires": { - "@babel/runtime": "^7.21.0", - "@mui/private-theming": "^5.11.13", - "@mui/styled-engine": "^5.11.11", - "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.13", - "clsx": "^1.2.1", - "csstype": "^3.1.1", - "prop-types": "^15.8.1" - } - }, - "@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", - "requires": {} - }, - "@mui/utils": { - "version": "5.11.13", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.13.tgz", - "integrity": "sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==", - "requires": { - "@babel/runtime": "^7.21.0", - "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true - }, - "@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dev": true, - "requires": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true - }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "optional": true - }, - "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" - }, - "@react-aria/focus": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.11.0.tgz", - "integrity": "sha512-yPuWs9bAR9CMfIwyOPm2oXLPF19gNkUqW+ozSPhWbLMTEa8Ma09eHW1br4xbN+4ONOm/dCJsIkxTNPUkiLdQoA==", - "requires": { - "@react-aria/interactions": "^3.14.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - } - }, - "@react-aria/i18n": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.7.0.tgz", - "integrity": "sha512-PZCWmhO9mJvelwiYlsXLY6W4L2o+oza3xnDx0cZDVqp/Hf+OwMAPHWtZsFRTKdjk4TaOPB/ISc9HknWn6UpY4w==", - "requires": { - "@internationalized/date": "^3.1.0", - "@internationalized/message": "^3.1.0", - "@internationalized/number": "^3.2.0", - "@internationalized/string": "^3.1.0", - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14" - } - }, - "@react-aria/interactions": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.14.0.tgz", - "integrity": "sha512-e1Tkr0UTuYFpV21PJrXy7jEY542Vl+C2Fo70oukZ1fN+wtfQkzodsTIzyepXb7kVMGmC34wDisMJUrksVkfY2w==", - "requires": { - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14" - } - }, - "@react-aria/overlays": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.13.0.tgz", - "integrity": "sha512-hRZyhAYzrlCcEWQ2k2jP24b0wc5/355Xl5w5FZHRmPeU1U4XlFwKX/eFlBs/li9Sprm1bTDXrCY480Kl6UsKDA==", - "requires": { - "@react-aria/focus": "^3.11.0", - "@react-aria/i18n": "^3.7.0", - "@react-aria/interactions": "^3.14.0", - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-aria/visually-hidden": "^3.7.0", - "@react-stately/overlays": "^3.5.0", - "@react-types/button": "^3.7.1", - "@react-types/overlays": "^3.7.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14" - } - }, - "@react-aria/ssr": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.5.0.tgz", - "integrity": "sha512-h0MJdSWOd1qObLnJ8mprU31wI8tmKFJMuwT22MpWq6psisOOZaga6Ml4u6Ee6M6duWWISjXvqO4Sb/J0PBA+nQ==", - "requires": { - "@swc/helpers": "^0.4.14" - } - }, - "@react-aria/utils": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.15.0.tgz", - "integrity": "sha512-aJZBG++iz1UwTW5gXFaHicKju4p0MPhAyBTcf2awHYWeTUUslDjJcEnNg7kjBYZBOrOSlA2rAt7/7C5CCURQPg==", - "requires": { - "@react-aria/ssr": "^3.5.0", - "@react-stately/utils": "^3.6.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - } - }, - "@react-aria/visually-hidden": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.7.0.tgz", - "integrity": "sha512-v/0ujJ67H6LjwY8J7mIGPVB1K8suBArLV+w8UGdX/wFXRL7H4r2fiqlrwAElWSmNbhDQl5BDm/Zh/ub9jB9yzA==", - "requires": { - "@react-aria/interactions": "^3.14.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - } - }, - "@react-dnd/asap": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", - "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" - }, - "@react-dnd/invariant": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", - "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" - }, - "@react-dnd/shallowequal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", - "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" - }, - "@react-spectrum/layout": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.5.0.tgz", - "integrity": "sha512-Yjy0uvGUHjczl44PQlMqOEBoJIe4coYwDQCoVl20YrTzGiMdwLV24Hs9uqK3I6yKmAoNxTehe0CdOVgwuVRF9g==", - "requires": { - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-spectrum/utils": "^3.9.0", - "@react-types/layout": "^3.3.6", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - } - }, - "@react-spectrum/provider": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.7.0.tgz", - "integrity": "sha512-6Uch60R5PKJC+ZY3VweVadaCoaIj6Nd85TKfFH4jf8NEzXv5CqjwUzwV5aQ6u3k1K24Ves7RpLlK53HW1wUm6w==", - "requires": { - "@react-aria/i18n": "^3.7.0", - "@react-aria/overlays": "^3.13.0", - "@react-aria/utils": "^3.15.0", - "@react-spectrum/utils": "^3.9.0", - "@react-types/provider": "^3.6.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - } - }, - "@react-spectrum/theme-default": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.0.tgz", - "integrity": "sha512-DagwngJ9T7yGgtdTwKj9LaHbg3AWLW2/ILJK92xbPj/yAAEVXJ8vhiP9Tg3W3mZMsxBCNwTVLyOLheu7d+D8og==", - "requires": { - "@react-types/provider": "^3.6.0", - "@swc/helpers": "^0.4.14" - } - }, - "@react-spectrum/utils": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.9.0.tgz", - "integrity": "sha512-I1JKwiyLE54mAT7Z2wadVKoai1Q60QFg4XNuYUwk8TwWAEz7DUUHucntHAND6dqFpVIMK1Rk9lcZBft43FF2BQ==", - "requires": { - "@react-aria/i18n": "^3.7.0", - "@react-aria/ssr": "^3.5.0", - "@react-aria/utils": "^3.15.0", - "@react-types/shared": "^3.17.0", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - } - }, - "@react-spectrum/view": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.5.0.tgz", - "integrity": "sha512-Zzsy2S6gT14vtli52tjM5VP1EdMOP+bppNCmwX+saUJuxaEdj6oYjP89quE4HwALzYceOfmXIuGDyEVeDcuSsQ==", - "requires": { - "@react-aria/i18n": "^3.7.0", - "@react-aria/utils": "^3.15.0", - "@react-spectrum/utils": "^3.9.0", - "@react-types/shared": "^3.17.0", - "@react-types/view": "^3.4.0", - "@swc/helpers": "^0.4.14" - } - }, - "@react-stately/overlays": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.5.0.tgz", - "integrity": "sha512-r+U/G0Y4tCfI5wyBeIu+hmcZVRN8ChoK2zM1srPH9nDKsijQard2goX+9YmKng2LJ01Re/P6F8S8jYbpfEdLfQ==", - "requires": { - "@react-stately/utils": "^3.6.0", - "@react-types/overlays": "^3.7.0", - "@swc/helpers": "^0.4.14" - } - }, - "@react-stately/utils": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.6.0.tgz", - "integrity": "sha512-rptF7iUWDrquaYvBAS4QQhOBQyLBncDeHF03WnHXAxnuPJXNcr9cXJtjJPGCs036ZB8Q2hc9BGG5wNyMkF5v+Q==", - "requires": { - "@swc/helpers": "^0.4.14" - } - }, - "@react-types/button": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.7.1.tgz", - "integrity": "sha512-c+8xjmqWSjI5/mEHVLbVSp0eh/z2UU8Ga+wqjbEUZUjm8uopYj1PaCAwZ7YgcAebyQrL/21GyjK6tFHKzuUdJQ==", - "requires": { - "@react-types/shared": "^3.17.0" - } - }, - "@react-types/layout": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.6.tgz", - "integrity": "sha512-8YF7nb+0AtSDRj+rejMRS5djWpqAf5Q6sJzgdccn42JQxW7jvxoKqN0pCRPqpMzQqae5hOsYYpVNfuIHwUUkUA==", - "requires": { - "@react-types/shared": "^3.17.0" - } - }, - "@react-types/overlays": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.7.0.tgz", - "integrity": "sha512-LstucncZ8dM+xJYEijI1V6jGH20w5XO/T60r7JTrgQElMC86phPeoWkMTN4c2lsRikybolDbvXL6XsF76YO56A==", - "requires": { - "@react-types/shared": "^3.17.0" - } - }, - "@react-types/provider": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.6.0.tgz", - "integrity": "sha512-29+X5yMr1uHUc/BmFoyBqfWyMT3YGZJffaprtGGRQkCuwMbqoA8GI+rGJzNAMAD/1cPNo237PAGsi4NAup5tAQ==", - "requires": { - "@react-types/shared": "^3.17.0" - } - }, - "@react-types/shared": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.17.0.tgz", - "integrity": "sha512-1SNZ/RhVrrQ1e6yE0bPV7d5Sfp+Uv0dfUEhwF9MAu2v5msu7AMewnsiojKNA0QA6Ing1gpDLjHCxtayQfuxqcg==", - "requires": {} - }, - "@react-types/view": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.0.tgz", - "integrity": "sha512-lXDLbOYRFwo8hxrSDIYUmnxOGZlHBDVEFe1ZKAXJ+qPwntzxhzUeAuddTiMDhEETKgTclmlIsJnwz+HNv6dhSQ==", - "requires": { - "@react-types/shared": "^3.17.0" - } - }, - "@reduxjs/toolkit": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.3.tgz", - "integrity": "sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==", - "requires": { - "immer": "^9.0.16", - "redux": "^4.2.0", - "redux-thunk": "^2.4.2", - "reselect": "^4.1.7" - } - }, - "@remix-run/router": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", - "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==" - }, - "@rollup/rollup-android-arm-eabi": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", - "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-android-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", - "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", - "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-darwin-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", - "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-arm64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", - "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-freebsd-x64": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", - "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", - "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", - "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", - "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-arm64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", - "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", - "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", - "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", - "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-riscv64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", - "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-s390x-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", - "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-gnu": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", - "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-linux-x64-musl": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", - "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-arm64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", - "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-ia32-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", - "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", - "dev": true, - "optional": true - }, - "@rollup/rollup-win32-x64-msvc": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", - "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", - "dev": true, - "optional": true - }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "@swc/core": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.41.tgz", - "integrity": "sha512-v6P2dfqJDpZ/7RXPvWge9oI6YgolDM0jtNhQZ2qdXrLBzaWQdDoBGBTJ8KN/nTgGhX3IkNvSB1fafXQ+nVnqAQ==", - "dev": true, - "requires": { - "@swc/core-darwin-arm64": "1.3.41", - "@swc/core-darwin-x64": "1.3.41", - "@swc/core-linux-arm-gnueabihf": "1.3.41", - "@swc/core-linux-arm64-gnu": "1.3.41", - "@swc/core-linux-arm64-musl": "1.3.41", - "@swc/core-linux-x64-gnu": "1.3.41", - "@swc/core-linux-x64-musl": "1.3.41", - "@swc/core-win32-arm64-msvc": "1.3.41", - "@swc/core-win32-ia32-msvc": "1.3.41", - "@swc/core-win32-x64-msvc": "1.3.41" - } - }, - "@swc/core-darwin-arm64": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.41.tgz", - "integrity": "sha512-D4fybODToO/BvuP35bionDUrSuTVVr8eW+mApr1unOqb3mfiqOrVv0VP2fpWNRYiA+xMq+oBCB6KcGpL60HKWQ==", - "dev": true, - "optional": true - }, - "@swc/core-darwin-x64": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.41.tgz", - "integrity": "sha512-0RoVyiPCnylf3TG77C3S86PRSmaq+SaYB4VDLJFz3qcEHz1pfP0LhyskhgX4wjQV1mveDzFEn1BVAuo0eOMwZA==", - "dev": true, - "optional": true - }, - "@swc/core-linux-arm-gnueabihf": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.41.tgz", - "integrity": "sha512-mZW7GeY7Uw1nkKoWpx898ou20oCSt8MR+jAVuAhMjX+G4Zr0WWXYSigWNiRymhR6Q9KhyvoFpMckguSvYWmXsw==", - "dev": true, - "optional": true - }, - "@swc/core-linux-arm64-gnu": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.41.tgz", - "integrity": "sha512-e91LGn+6KuLFw3sWk5swwGc/dP4tXs0mg3HrhjImRoofU02Bb9aHcj5zgrSO8ZByvDtm/Knn16h1ojxIMOFaxg==", - "dev": true, - "optional": true - }, - "@swc/core-linux-arm64-musl": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.41.tgz", - "integrity": "sha512-Q7hmrniLWsQ7zjtImGcjx1tl5/Qxpel+fC+OXTnGvAyyoGssSftIBlXMnqVLteL78zhxIPAzi+gizWAe5RGqrA==", - "dev": true, - "optional": true - }, - "@swc/core-linux-x64-gnu": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.41.tgz", - "integrity": "sha512-h4sv1sCfZQgRIwmykz8WPqVpbvHb13Qm3SsrbOudhAp2MuzpWzsgMP5hAEpdCP/nWreiCz3aoM6L8JeakRDq0g==", - "dev": true, - "optional": true - }, - "@swc/core-linux-x64-musl": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.41.tgz", - "integrity": "sha512-Z7c26i38378d0NT/dcz8qPSAXm41lqhNzykdhKhI+95mA9m4pskP18T/0I45rmyx1ywifypu+Ip+SXmKeVSPgQ==", - "dev": true, - "optional": true - }, - "@swc/core-win32-arm64-msvc": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.41.tgz", - "integrity": "sha512-I0CYnPc+ZGc912YeN0TykIOf/Q7yJQHRwDuhewwD6RkbiSEaVfSux5pAmmdoKw2aGMSq+cwLmgPe9HYLRNz+4w==", - "dev": true, - "optional": true - }, - "@swc/core-win32-ia32-msvc": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.41.tgz", - "integrity": "sha512-EygN4CVDWF29/U2T5fXGfWyLvRbMd2hiUgkciAl7zHuyJ6nKl+kpodqV2A0Wd4sFtSNedU0gQEBEXEe7cqvmsA==", - "dev": true, - "optional": true - }, - "@swc/core-win32-x64-msvc": { - "version": "1.3.41", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.41.tgz", - "integrity": "sha512-Mfp8qD1hNwWWRy0ISdwQJu1g0UYoVTtuQlO0z3aGbXqL51ew9e56+8j3M1U9i95lXFyWkARgjDCcKkQi+WezyA==", - "dev": true, - "optional": true - }, - "@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", - "requires": { - "tslib": "^2.4.0" - } - }, - "@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "dev": true, - "peer": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "dependencies": { - "aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "peer": true, - "requires": { - "dequal": "^2.0.3" - } - } - } - }, - "@testing-library/jest-dom": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.7.0.tgz", - "integrity": "sha512-RI2e97YZ7MRa+vxP4UUnMuMFL2buSsf0ollxUbTgrbPLKhMn8KVTx7raS6DYjC7v1NDVrioOvaShxsguLNISCA==", - "dev": true, - "requires": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "dependencies": { - "dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true - } - } - }, - "@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.12.5" - } - }, - "@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", - "dev": true, - "requires": {} - }, - "@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true - }, - "@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", - "dev": true, - "requires": { - "@types/deep-eql": "*" - } - }, - "@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "dev": true - }, - "@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true - }, - "@types/eslint": { - "version": "8.21.3", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.3.tgz", - "integrity": "sha512-fa7GkppZVEByMWGbTtE5MbmXWJTVbrjjaS8K6uQj+XtuuUv1fsuPAxhygfqLmsb/Ufb3CV8deFCpiMfAgi00Sw==", - "dev": true, - "peer": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "peer": true - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/is-hotkey": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz", - "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==" - }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "@types/lodash": { - "version": "4.14.191", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz", - "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==" - }, - "@types/node": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", - "devOptional": true, - "requires": { - "undici-types": "~7.10.0" - } - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "@types/react": { - "version": "18.3.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", - "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", - "requires": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-copy-to-clipboard": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz", - "integrity": "sha512-otTJsJpofYAeaIeOwV5xBUGpo6exXG2HX7X4nseToCB2VgPEBxGBHCm/FecZ676doNR7HCSTVtmohxfG2b3/yQ==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, - "requires": {} - }, - "@types/react-grid-layout": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/react-grid-layout/-/react-grid-layout-1.3.2.tgz", - "integrity": "sha512-ZzpBEOC1JTQ7MGe1h1cPKSLP4jSWuxc+yvT4TsAlEW9+EFPzAf8nxQfFd7ea9gL17Em7PbwJZAsiwfQQBUklZQ==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-is": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", - "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", - "requires": { - "@types/react": "*" - } - }, - "@types/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", - "requires": { - "@types/react": "*" - } - }, - "@types/statuses": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", - "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", - "dev": true - }, - "@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true - }, - "@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", - "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/type-utils": "8.39.1", - "@typescript-eslint/utils": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "dependencies": { - "ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true - } - } - }, - "@typescript-eslint/parser": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", - "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/project-service": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", - "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", - "dev": true, - "requires": { - "@typescript-eslint/tsconfig-utils": "^8.39.1", - "@typescript-eslint/types": "^8.39.1", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", - "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1" - } - }, - "@typescript-eslint/tsconfig-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", - "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", - "dev": true, - "requires": {} - }, - "@typescript-eslint/type-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", - "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - } - }, - "@typescript-eslint/types": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", - "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", - "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", - "dev": true, - "requires": { - "@typescript-eslint/project-service": "8.39.1", - "@typescript-eslint/tsconfig-utils": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", - "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", - "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", - "dev": true, - "requires": { - "@typescript-eslint/types": "8.39.1", - "eslint-visitor-keys": "^4.2.1" - } - }, - "@vitejs/plugin-react-swc": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.2.0.tgz", - "integrity": "sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==", - "dev": true, - "requires": { - "@swc/core": "^1.3.35" - } - }, - "@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.3.0", - "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", - "magicast": "^0.3.5", - "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" - } - }, - "@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, - "requires": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - } - }, - "@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, - "requires": { - "tinyrainbow": "^2.0.0" - } - }, - "@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", - "dev": true, - "requires": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" - } - }, - "@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", - "dev": true, - "requires": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", - "pathe": "^2.0.3" - } - }, - "@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, - "requires": { - "tinyspy": "^4.0.3" - } - }, - "@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dev": true, - "requires": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "peer": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "peer": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "peer": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "peer": true - }, - "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peer": true, - "requires": {} - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true - }, - "array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - } - }, - "array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - } - }, - "array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - } - }, - "array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - } - }, - "arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - } - }, - "assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true - }, - "ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true - }, - "ast-v8-to-istanbul": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", - "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.29", - "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" - }, - "dependencies": { - "js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true - } - } - }, - "async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "attr-accept": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", - "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" - }, - "available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "requires": { - "possible-typed-array-names": "^1.0.0" - } - }, - "axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", - "dev": true - }, - "axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true - }, - "babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "requires": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - } - }, - "babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" - } - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "blueimp-canvas-to-blob": { - "version": "3.29.0", - "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz", - "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==" - }, - "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "peer": true - }, - "cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true - }, - "call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - } - }, - "call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - } - }, - "call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camelize": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", - "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" - }, - "caniuse-lite": { - "version": "1.0.30001469", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001469.tgz", - "integrity": "sha512-Rcp7221ScNqQPP3W+lVOYDyjdR6dC+neEQCttoNr5bAyz54AboB4iwpnWgyi8P4YUsPybVzT4LgWiBbI3drL4g==", - "dev": true, - "peer": true - }, - "chai": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", - "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", - "dev": true, - "requires": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - } - } - }, - "check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true - }, - "classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" - }, - "cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "peer": true - }, - "compressorjs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz", - "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==", - "requires": { - "blueimp-canvas-to-blob": "^3.29.0", - "is-blob": "^2.1.0" - } - }, - "compute-scroll-into-view": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true - }, - "copy-to-clipboard": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", - "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", - "requires": { - "toggle-selection": "^1.0.6" - } - }, - "cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" - }, - "css-to-react-native": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", - "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", - "requires": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true - }, - "cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "dev": true, - "requires": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - } - }, - "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true - }, - "data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "dev": true, - "requires": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - } - }, - "data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - } - }, - "data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - } - }, - "data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - } - }, - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "requires": { - "ms": "^2.1.3" - } - }, - "decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true - }, - "deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "requires": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "peer": true - }, - "diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true - }, - "direction": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", - "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==" - }, - "dnd-core": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", - "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", - "requires": { - "@react-dnd/asap": "^5.0.1", - "@react-dnd/invariant": "^4.0.1", - "redux": "^4.2.0" - } - }, - "dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "peer": true - }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.334", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.334.tgz", - "integrity": "sha512-laZ1odk+TRen6q0GeyQx/JEkpD3iSZT7ewopCpKqg9bTjP1l8XRfU3Bg20CFjNPZkp5+NDBl3iqd4o/kPO+Vew==", - "dev": true, - "peer": true - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - } - }, - "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "peer": true - }, - "es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0" - } - }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "requires": { - "hasown": "^2.0.2" - } - }, - "es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "requires": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - } - }, - "esbuild": { - "version": "0.17.11", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.11.tgz", - "integrity": "sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.17.11", - "@esbuild/android-arm64": "0.17.11", - "@esbuild/android-x64": "0.17.11", - "@esbuild/darwin-arm64": "0.17.11", - "@esbuild/darwin-x64": "0.17.11", - "@esbuild/freebsd-arm64": "0.17.11", - "@esbuild/freebsd-x64": "0.17.11", - "@esbuild/linux-arm": "0.17.11", - "@esbuild/linux-arm64": "0.17.11", - "@esbuild/linux-ia32": "0.17.11", - "@esbuild/linux-loong64": "0.17.11", - "@esbuild/linux-mips64el": "0.17.11", - "@esbuild/linux-ppc64": "0.17.11", - "@esbuild/linux-riscv64": "0.17.11", - "@esbuild/linux-s390x": "0.17.11", - "@esbuild/linux-x64": "0.17.11", - "@esbuild/netbsd-x64": "0.17.11", - "@esbuild/openbsd-x64": "0.17.11", - "@esbuild/sunos-x64": "0.17.11", - "@esbuild/win32-arm64": "0.17.11", - "@esbuild/win32-ia32": "0.17.11", - "@esbuild/win32-x64": "0.17.11" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "eslint": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", - "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.33.0", - "@eslint/plugin-kit": "^0.3.5", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "dependencies": { - "@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", - "dev": true, - "requires": {} - }, - "eslint-plugin-jsx-a11y": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", - "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "dev": true, - "requires": { - "aria-query": "^5.3.2", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.1" - } - }, - "eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "peer": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true - }, - "espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "requires": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - } - }, - "esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true - }, - "estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "requires": { - "@types/estree": "^1.0.0" - }, - "dependencies": { - "@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - } - } - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true - }, - "expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "requires": { - "flat-cache": "^4.0.0" - } - }, - "file-selector": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", - "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", - "requires": { - "tslib": "^2.4.0" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - } - }, - "flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, - "for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "requires": { - "is-callable": "^1.2.7" - } - }, - "foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - } - }, - "get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - } - }, - "get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - } - }, - "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "requires": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - } - }, - "gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "peer": true - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "graphql": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", - "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.0" - } - }, - "has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.3" - } - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "requires": { - "function-bind": "^1.1.2" - } - }, - "headers-polyfill": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", - "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", - "dev": true - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^3.1.1" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "requires": { - "agent-base": "^7.1.2", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "immer": { - "version": "9.0.19", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz", - "integrity": "sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - } - }, - "intl-messageformat": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.3.3.tgz", - "integrity": "sha512-un/f07/g2e/3Q8e1ghDKET+el22Bi49M7O/rHxd597R+oLpPOMykSv5s51cABVfu3FZW+fea4hrzf2MHu1W4hw==", - "requires": { - "@formatjs/ecma402-abstract": "1.14.3", - "@formatjs/fast-memoize": "2.0.1", - "@formatjs/icu-messageformat-parser": "2.3.0", - "tslib": "^2.4.0" - } - }, - "is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "requires": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - } - }, - "is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "requires": { - "has-bigints": "^1.0.2" - } - }, - "is-blob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", - "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==" - }, - "is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - } - }, - "is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hotkey": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", - "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" - }, - "is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true - }, - "is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true - }, - "is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - } - }, - "is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - } - }, - "is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "requires": { - "which-typed-array": "^1.1.16" - } - }, - "is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true - }, - "is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "requires": { - "call-bound": "^1.0.3" - } - }, - "is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - } - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - } - }, - "istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "jest-axe": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jest-axe/-/jest-axe-10.0.0.tgz", - "integrity": "sha512-9QR0M7//o5UVRnEUUm68IsGapHrcKGakYy9dKWWMX79LmeUKguDI6DREyljC5I13j78OUmtKLF5My6ccffLFBg==", - "dev": true, - "requires": { - "axe-core": "4.10.2", - "chalk": "4.1.2", - "jest-matcher-utils": "29.2.2", - "lodash.merge": "4.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "axe-core": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", - "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true - }, - "jest-matcher-utils": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.2.2.tgz", - "integrity": "sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.2.1", - "jest-get-type": "^29.2.0", - "pretty-format": "^29.2.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "requires": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", - "dev": true, - "requires": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true - }, - "jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - } - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true - }, - "language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "requires": { - "language-subtag-registry": "^0.3.20" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "requires": { - "immediate": "~3.0.5" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "peer": true - }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "requires": { - "lie": "3.1.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loupe": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", - "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", - "dev": true - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "peer": true - }, - "magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "magicast": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", - "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, - "requires": { - "@babel/parser": "^7.25.4", - "@babel/types": "^7.25.4", - "source-map-js": "^1.2.0" - } - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "requires": { - "semver": "^7.5.3" - } - }, - "math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "peer": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true - }, - "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "msw": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.10.5.tgz", - "integrity": "sha512-0EsQCrCI1HbhpBWd89DvmxY6plmvrM96b0sCIztnvcNHQbXn5vqwm1KlXslo6u4wN9LFGLC1WFjjgljcQhe40A==", - "dev": true, - "requires": { - "@bundled-es-modules/cookie": "^2.0.1", - "@bundled-es-modules/statuses": "^1.0.1", - "@bundled-es-modules/tough-cookie": "^0.1.6", - "@inquirer/confirm": "^5.0.0", - "@mswjs/interceptors": "^0.39.1", - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/until": "^2.1.0", - "@types/cookie": "^0.6.0", - "@types/statuses": "^2.0.4", - "graphql": "^16.8.1", - "headers-polyfill": "^4.0.2", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "path-to-regexp": "^6.3.0", - "picocolors": "^1.1.1", - "strict-event-emitter": "^0.5.1", - "type-fest": "^4.26.1", - "yargs": "^17.7.2" - } - }, - "mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true - }, - "nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true, - "peer": true - }, - "nwsapi": { - "version": "2.2.21", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", - "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - } - }, - "object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - } - }, - "object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - } - }, - "optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - } - }, - "outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true - }, - "own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "requires": { - "entities": "^6.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - } - }, - "path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true - }, - "pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true - }, - "philliplm-react-modern-audio-player": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/philliplm-react-modern-audio-player/-/philliplm-react-modern-audio-player-1.4.6.tgz", - "integrity": "sha512-2C/1lpQJmD0gWJMVt6k/QNZoakxHwxGhRFyw17OVAwbpXmptisr9aHDzT/VPFKoIr5qj+y1WajJ4lnghbfzx9Q==", - "requires": { - "@react-spectrum/layout": "^3.3.1", - "@react-spectrum/provider": "^3.4.1", - "@react-spectrum/theme-default": "^3.3.1", - "@react-spectrum/view": "^3.2.1", - "classnames": "^2.3.1", - "react-icons": "^4.4.0", - "styled-components": "^5.3.5", - "wavesurfer.js": "^6.2.0" - } - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true - }, - "postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "requires": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - } - }, - "postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true - }, - "pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "peer": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true - } - } - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "requires": { - "punycode": "^2.3.1" - } - }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-copy-to-clipboard": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", - "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", - "requires": { - "copy-to-clipboard": "^3.3.1", - "prop-types": "^15.8.1" - } - }, - "react-dnd": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", - "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", - "requires": { - "@react-dnd/invariant": "^4.0.1", - "@react-dnd/shallowequal": "^4.0.1", - "dnd-core": "^16.0.1", - "fast-deep-equal": "^3.1.3", - "hoist-non-react-statics": "^3.3.2" - } - }, - "react-dnd-html5-backend": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", - "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", - "requires": { - "dnd-core": "^16.0.1" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "react-draggable": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", - "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", - "requires": { - "clsx": "^1.1.1", - "prop-types": "^15.8.1" - } - }, - "react-dropzone": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", - "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", - "requires": { - "attr-accept": "^2.2.2", - "file-selector": "^0.6.0", - "prop-types": "^15.8.1" - } - }, - "react-grid-layout": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.3.4.tgz", - "integrity": "sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==", - "requires": { - "clsx": "^1.1.1", - "lodash.isequal": "^4.0.0", - "prop-types": "^15.8.1", - "react-draggable": "^4.0.0", - "react-resizable": "^3.0.4" - } - }, - "react-icons": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", - "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", - "requires": {} - }, - "react-intersection-observer": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.3.tgz", - "integrity": "sha512-WNRqMQvKpupr6MzecAQI0Pj0+JQong307knLP4g/nBex7kYfIaZsPpXaIhKHR+oV8z+goUbH9e10j6lGRnTzlQ==", - "requires": {} - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "react-masonry-css": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz", - "integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==", - "requires": {} - }, - "react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - } - }, - "react-resizable": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz", - "integrity": "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==", - "requires": { - "prop-types": "15.x", - "react-draggable": "^4.0.3" - } - }, - "react-resize-detector": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.0.4.tgz", - "integrity": "sha512-ln9pMAob8y8mc9UI4aZuuWFiyMqBjnTs/sxe9Vs9dPXUjwCTeKK1FP8I75ufnb/2mEEZXG6wOo/fjMcBRRuAXw==", - "requires": { - "lodash": "^4.17.21" - } - }, - "react-router": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", - "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", - "requires": { - "@remix-run/router": "1.4.0" - } - }, - "react-router-dom": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", - "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", - "requires": { - "@remix-run/router": "1.4.0", - "react-router": "6.9.0" - } - }, - "react-toastify": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.2.tgz", - "integrity": "sha512-PBfzXO5jMGEtdYR5jxrORlNZZe/EuOkwvwKijMatsZZm8IZwLj01YvobeJYNjFcA6uy6CVrx2fzL9GWbhWPTDA==", - "requires": { - "clsx": "^1.1.1" - } - }, - "react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "react-virtuoso": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.3.3.tgz", - "integrity": "sha512-x0DeGmVAVOVaTXRMG7jzrHBwK7+dkt7n0G3tNmZXphQUBgkVBYuZoaJltQeZGFN42++3XvrgwStKCtmzgMJ0lA==", - "requires": {} - }, - "redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "requires": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - } - }, - "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "requires": {} - }, - "reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true - }, - "rollup": { - "version": "3.19.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.19.1.tgz", - "integrity": "sha512-lAbrdN7neYCg/8WaoWn/ckzCtz+jr70GFfYdlf50OF7387HTg+wiuiqJRFYawwSPpqfqDNYqK7smY/ks2iAudg==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "peer": true - }, - "safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - } - }, - "safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "scroll-into-view-if-needed": { - "version": "2.2.31", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", - "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", - "requires": { - "compute-scroll-into-view": "^1.0.20" - } - }, - "semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, - "peer": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - } - }, - "set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "requires": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - } - }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "short-unique-id": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-4.4.4.tgz", - "integrity": "sha512-oLF1NCmtbiTWl2SqdXZQbo5KM1b7axdp0RgQLq8qCBBLoq+o3A5wmLrNM6bZIh54/a8BJ3l69kTXuxwZ+XCYuw==" - }, - "side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - } - }, - "side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - } - }, - "side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - } - }, - "side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - } - }, - "siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true - }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true - }, - "slate": { - "version": "0.91.4", - "resolved": "https://registry.npmjs.org/slate/-/slate-0.91.4.tgz", - "integrity": "sha512-aUJ3rpjrdi5SbJ5G1Qjr3arytfRkEStTmHjBfWq2A2Q8MybacIzkScSvGJjQkdTk3djCK9C9SEOt39sSeZFwTw==", - "requires": { - "immer": "^9.0.6", - "is-plain-object": "^5.0.0", - "tiny-warning": "^1.0.3" - } - }, - "slate-history": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.86.0.tgz", - "integrity": "sha512-OxObL9tbhgwvSlnKSCpGIh7wnuaqvOj5jRExGjEyCU2Ke8ctf22HjT+jw7GEi9ttLzNTUmTEU3YIzqKGeqN+og==", - "requires": { - "is-plain-object": "^5.0.0" - } - }, - "slate-react": { - "version": "0.91.11", - "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.91.11.tgz", - "integrity": "sha512-2nS29rc2kuTTJrEUOXGyTkFATmTEw/R9KuUXadUYiz+UVwuFOUMnBKuwJWyuIBOsFipS+06SkIayEf5CKdARRQ==", - "requires": { - "@juggle/resize-observer": "^3.4.0", - "@types/is-hotkey": "^0.1.1", - "@types/lodash": "^4.14.149", - "direction": "^1.0.3", - "is-hotkey": "^0.1.6", - "is-plain-object": "^5.0.0", - "lodash": "^4.17.4", - "scroll-into-view-if-needed": "^2.2.20", - "tiny-invariant": "1.0.6" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - }, - "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true - } - } - }, - "stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true - }, - "statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "dev": true - }, - "std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "dev": true - }, - "stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - } - }, - "strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "string.prototype.includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", - "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3" - } - }, - "string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - } - }, - "string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "requires": { - "ansi-regex": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true - } - } - }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "requires": { - "min-indent": "^1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, - "requires": { - "js-tokens": "^9.0.1" - }, - "dependencies": { - "js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true - } - } - }, - "styled-components": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", - "integrity": "sha512-Aj3kb13B75DQBo2oRwRa/APdB5rSmwUfN5exyarpX+x/tlM/rwZA2vVk2vQgVSP6WKaZJHWwiFrzgHt+CLtB4A==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "dependencies": { - "@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" - } - } - }, - "stylis": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz", - "integrity": "sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true - }, - "terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - }, - "terser-webpack-plugin": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", - "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.5" - } - }, - "test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "tiny-invariant": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true - }, - "tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true - }, - "tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, - "requires": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "dependencies": { - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true - } - } - }, - "tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true - }, - "tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true - }, - "tinyspy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", - "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", - "dev": true - }, - "tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "requires": { - "tldts-core": "^6.1.86" - } - }, - "tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" - }, - "tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "requires": { - "tldts": "^6.1.32" - } - }, - "tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "requires": { - "punycode": "^2.3.1" - } - }, - "ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "requires": {} - }, - "ts-key-enum": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.12.tgz", - "integrity": "sha512-Ety4IvKMaeG34AyXMp5r11XiVZNDRL+XWxXbVVJjLvq2vxKRttEANBE7Za1bxCAZRdH2/sZT6jFyyTWxXz28hw==" - }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true - }, - "typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - } - }, - "typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "requires": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - } - }, - "typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - } - }, - "typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "requires": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - } - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true - }, - "typescript-eslint": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz", - "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", - "dev": true, - "requires": { - "@typescript-eslint/eslint-plugin": "8.39.1", - "@typescript-eslint/parser": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1" - } - }, - "unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "requires": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - } - }, - "undici-types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "devOptional": true - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "dev": true, - "peer": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "requires": {} - }, - "vite": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.2.0.tgz", - "integrity": "sha512-AbDTyzzwuKoRtMIRLGNxhLRuv1FpRgdIw+1y6AQG73Q5+vtecmvzKo/yk8X/vrHDpETRTx01ABijqUHIzBXi0g==", - "dev": true, - "requires": { - "esbuild": "^0.17.5", - "fsevents": "~2.3.2", - "postcss": "^8.4.21", - "resolve": "^1.22.1", - "rollup": "^3.18.0" - } - }, - "vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "requires": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "dependencies": { - "@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", - "dev": true, - "optional": true - }, - "@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true - }, - "esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, - "requires": { - "@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" - } - }, - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true - }, - "rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "@types/estree": "1.0.8", - "fsevents": "~2.3.2" - } - }, - "vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", - "dev": true, - "requires": { - "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "fsevents": "~2.3.3", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" - } - } - } - }, - "vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", - "dev": true, - "requires": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", - "pathe": "^2.0.3", - "picomatch": "^4.0.2", - "std-env": "^3.9.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", - "why-is-node-running": "^2.3.0" - }, - "dependencies": { - "@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", - "dev": true, - "optional": true - }, - "@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true - }, - "@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "requires": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - } - }, - "esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, - "requires": { - "@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" - } - }, - "fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "requires": {} - }, - "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true - }, - "rollup": { - "version": "4.46.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", - "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.46.2", - "@rollup/rollup-android-arm64": "4.46.2", - "@rollup/rollup-darwin-arm64": "4.46.2", - "@rollup/rollup-darwin-x64": "4.46.2", - "@rollup/rollup-freebsd-arm64": "4.46.2", - "@rollup/rollup-freebsd-x64": "4.46.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", - "@rollup/rollup-linux-arm-musleabihf": "4.46.2", - "@rollup/rollup-linux-arm64-gnu": "4.46.2", - "@rollup/rollup-linux-arm64-musl": "4.46.2", - "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", - "@rollup/rollup-linux-ppc64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-gnu": "4.46.2", - "@rollup/rollup-linux-riscv64-musl": "4.46.2", - "@rollup/rollup-linux-s390x-gnu": "4.46.2", - "@rollup/rollup-linux-x64-gnu": "4.46.2", - "@rollup/rollup-linux-x64-musl": "4.46.2", - "@rollup/rollup-win32-arm64-msvc": "4.46.2", - "@rollup/rollup-win32-ia32-msvc": "4.46.2", - "@rollup/rollup-win32-x64-msvc": "4.46.2", - "@types/estree": "1.0.8", - "fsevents": "~2.3.2" - } - }, - "vite": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", - "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", - "dev": true, - "requires": { - "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "fsevents": "~2.3.3", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.14" - } - } - } - }, - "w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "requires": { - "xml-name-validator": "^5.0.0" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "peer": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wavesurfer.js": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-6.6.2.tgz", - "integrity": "sha512-aPAU4OADQsyH8mIw2nXmoni8KHo8s1f1bd5ZUrxhN4P/VMWd+oPDqEwA01XPSEfasAJW6mZ/EHQ2bZ9nOWRrNw==" - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "webpack": { - "version": "5.76.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.2.tgz", - "integrity": "sha512-Th05ggRm23rVzEOlX8y67NkYCHa9nTNcwHPBhdg+lKG+mtiW7XgggjAeeLnADAe7mLjJ6LUNfgHAuRRh+Z6J7w==", - "dev": true, - "peer": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "peer": true - }, - "whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true - }, - "whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "requires": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "requires": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - } - }, - "which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "requires": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - } - }, - "which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "requires": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - } - }, - "which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - } - }, - "why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "requires": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - } - }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true - }, - "worker-loader": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", - "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", - "dev": true, - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true - } - } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - }, - "yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "dev": true - } } } diff --git a/package.json b/package.json index 6896ce1..2e0d49d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "q-blog", "private": true, - "version": "0.0.1", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite", @@ -10,7 +10,7 @@ "lint": "eslint .", "format": "prettier --check .", "format:write": "prettier --write .", - "test": "vitest", + "test": "vitest run", "test:run": "vitest run", "typecheck": "tsc -p tsconfig.json -noEmit", "lint:phase0": "LINT_SCOPE=phase0 eslint .", diff --git a/tests/components/PostPreview.a11y.test.tsx b/tests/components/PostPreview.a11y.test.tsx new file mode 100644 index 0000000..648e1b0 --- /dev/null +++ b/tests/components/PostPreview.a11y.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { Provider } from 'react-redux'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { axe } from 'jest-axe'; +import PostPreview from '@/pages/BlogList/PostPreview'; +import { store } from '@/state/store'; + +const original = (global as any).qortalRequest; + +beforeEach(() => { + (global as any).qortalRequest = vi.fn().mockResolvedValue('http://avatar/url'); +}); +afterEach(() => { + (global as any).qortalRequest = original; + vi.restoreAllMocks(); +}); + +describe('PostPreview a11y', () => { + it('has no detectable violations', async () => { + const { container } = render( + + + + + , + ); + // Wait for avatar effect to settle to avoid act warning + await screen.findByAltText("alice's avatar"); + const results = await axe(container, { + rules: { + // ignore aria-prohibited-attr warnings from container div without a role + 'aria-prohibited-attr': { enabled: false }, + }, + }); + expect(results).toHaveNoViolations(); + }); +}); diff --git a/tests/hooks/useFetchPosts.test.tsx b/tests/hooks/useFetchPosts.test.tsx new file mode 100644 index 0000000..6caa3c3 --- /dev/null +++ b/tests/hooks/useFetchPosts.test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; +import { render } from '@testing-library/react'; +import { useFetchPosts } from '@/hooks/useFetchPosts'; + +function Harness({ onReady }: { onReady: (api: ReturnType) => void }) { + const api = useFetchPosts(); + React.useEffect(() => { + onReady(api); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return null; +} + +describe('useFetchPosts', () => { + it('checkAndUpdatePost returns true for new posts', () => { + let api!: ReturnType; + render( + + (api = a)} /> + , + ); + + const p = { id: '1', user: 'u', title: 't', description: 'd', createdAt: 0 }; + expect(api.checkAndUpdatePost(p as any)).toBe(true); + }); +}); diff --git a/tests/pages/BlogIndividualPost.test.tsx b/tests/pages/BlogIndividualPost.test.tsx new file mode 100644 index 0000000..0d4ec57 --- /dev/null +++ b/tests/pages/BlogIndividualPost.test.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { BlogIndividualPost } from '@/pages/BlogIndividualPost/BlogIndividualPost'; +import { store } from '@/state/store'; +import { server } from '../msw/server'; +import { http, HttpResponse } from 'msw'; + +const postResponse = { + title: 'Unit Test Post', + createdAt: 111, + postContent: [ + { type: 'editor', version: 1, id: 'ed1', content: [{ text: 'Hello world' }] }, + { type: 'image', version: 1, id: 'img1', content: { image: 'data:' } }, + ], +}; + +describe('BlogIndividualPost', () => { + it('renders post title and content scaffold', async () => { + // BLOG meta + server.use( + http.get('/arbitrary/BLOG/:user/:blog', ({ params }) => + HttpResponse.json({ + title: 'My Blog', + createdAt: 1, + blogId: params.blog, + description: 'd', + blogImage: '', + }), + ), + // BLOG_POST expects combined identifier: q-blog--post- + http.get('/arbitrary/BLOG_POST/:user/:fullId', ({ params }) => { + const { fullId, user } = params as any; + if (typeof fullId === 'string' && fullId.includes('-post-') && user) { + return HttpResponse.json(postResponse); + } + return HttpResponse.json({}, { status: 404 }); + }), + // Comments search and other relative API calls used by child components + http.get('/arbitrary/resources/search', () => HttpResponse.json([])), + ); + + render( + + + + + } /> + + + + , + ); + + expect(await screen.findByText('Unit Test Post')).toBeInTheDocument(); + }); +}); diff --git a/tests/pages/BlogIndividualProfile.test.tsx b/tests/pages/BlogIndividualProfile.test.tsx new file mode 100644 index 0000000..c9b2729 --- /dev/null +++ b/tests/pages/BlogIndividualProfile.test.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { BlogIndividualProfile } from '@/pages/BlogIndividualProfile/BlogIndividualProfile'; +import { store } from '@/state/store'; +import { server } from '../msw/server'; +import { http, HttpResponse } from 'msw'; + +describe('BlogIndividualProfile', () => { + it('renders blog title from API', async () => { + server.use( + http.get('/arbitrary/BLOG/:user/:blog', ({ params }) => { + return HttpResponse.json({ + title: 'My Blog', + createdAt: 1, + blogId: params.blog, + description: 'd', + blogImage: '', + }); + }), + ); + + render( + + + + + } /> + + + + , + ); + + expect(await screen.findByText('My Blog')).toBeInTheDocument(); + }); +}); diff --git a/tests/pages/BlogList.test.tsx b/tests/pages/BlogList.test.tsx new file mode 100644 index 0000000..2667e9a --- /dev/null +++ b/tests/pages/BlogList.test.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { describe, it, expect, beforeEach } from 'vitest'; +import { Provider } from 'react-redux'; +import { MemoryRouter } from 'react-router-dom'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { render, screen } from '@testing-library/react'; +import { BlogList } from '@/pages/BlogList/BlogList'; +import { store } from '@/state/store'; +import { addPosts, setCountNewPosts } from '@/state/features/blogSlice'; +import { addUser } from '@/state/features/authSlice'; + +describe('BlogList', () => { + beforeEach(() => { + // reset likely mutated lists by replacing posts + store.dispatch(addPosts([] as any)); + store.dispatch(setCountNewPosts(0)); + }); + + it('renders posts provided in store', async () => { + store.dispatch( + addPosts([ + { + id: 'q-blog-b1-post-1', + user: 'alice', + title: 'Post A', + description: 'Desc A', + createdAt: 0, + }, + { + id: 'q-blog-b2-post-2', + user: 'bob', + title: 'Post B', + description: 'Desc B', + createdAt: 0, + }, + ] as any), + ); + render( + + + + + + + , + ); + + expect(await screen.findByText('Post A')).toBeInTheDocument(); + expect(screen.getByText('Post B')).toBeInTheDocument(); + }); + + it('shows new posts banner when countNewPosts > 0', async () => { + store.dispatch(setCountNewPosts(2)); + render( + + + + + + + , + ); + expect(await screen.findByText('There are 2 new posts')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /Load new Posts/i })).toBeInTheDocument(); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts index b19470d..db6fc6a 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -9,3 +9,35 @@ expect.extend(toHaveNoViolations); beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); + +// Polyfill IntersectionObserver for components relying on it (e.g., LazyLoad) +if (!('IntersectionObserver' in globalThis)) { + // minimal stub sufficient for react-intersection-observer + class IO { + constructor(_: any) {} + observe() {} + unobserve() {} + disconnect() {} + takeRecords() { + return []; + } + } + // @ts-expect-error test env polyfill + globalThis.IntersectionObserver = IO; +} + +// Polyfill ResizeObserver for components relying on it (e.g., react-resize-detector) +if (!('ResizeObserver' in globalThis)) { + class RO { + observe() {} + unobserve() {} + disconnect() {} + } + globalThis.ResizeObserver = RO as any; +} + +// Provide HTMLDocument constructor reference for libs checking it explicitly +if (!('HTMLDocument' in globalThis)) { + // @ts-expect-error test env polyfill + globalThis.HTMLDocument = document?.constructor || (window as any).Document; +} diff --git a/tests/state/blogSlice.test.ts b/tests/state/blogSlice.test.ts new file mode 100644 index 0000000..9be023a --- /dev/null +++ b/tests/state/blogSlice.test.ts @@ -0,0 +1,64 @@ +import { describe, it, expect } from 'vitest'; +import reducer, { + addPosts, + removePost, + addToHashMap, + upsertPosts, + upsertPostsBeginning, + populateFavorites, + setCountNewPosts, + BlogPost, +} from '@/state/features/blogSlice'; + +const post = (id: string, user = 'alice'): BlogPost => ({ + id, + user, + title: 't', + description: 'd', + createdAt: 0, +}); + +describe('blogSlice reducers', () => { + it('addPosts replaces posts', () => { + const s = reducer(undefined, addPosts([post('1')])); + expect(s.posts.map((p) => p.id)).toEqual(['1']); + }); + + it('removePost removes by id from posts and filteredPosts', () => { + const init = reducer(undefined, addPosts([post('1'), post('2')])); + const withFiltered = { ...init, filteredPosts: [post('2'), post('3')] }; + const s = reducer(withFiltered, removePost('2')); + expect(s.posts.map((p) => p.id)).toEqual(['1']); + expect(s.filteredPosts.map((p) => p.id)).toEqual(['3']); + }); + + it('addToHashMap stores key as id-user', () => { + const s = reducer(undefined, addToHashMap(post('1', 'bob'))); + expect(Object.keys(s.hashMapPosts)).toEqual(['1-bob']); + }); + + it('upsertPosts updates or inserts', () => { + const a = reducer(undefined, addPosts([post('1'), post('2')])); + const b = reducer(a, upsertPosts([post('2', 'x'), post('3')])); + expect(b.posts.find((p) => p.id === '2')?.user).toBe('x'); + expect(b.posts.some((p) => p.id === '3')).toBe(true); + }); + + it('upsertPostsBeginning unshifts new and updates existing', () => { + const a = reducer(undefined, addPosts([post('2')])); + const b = reducer(a, upsertPostsBeginning([post('1'), post('2', 'y')])); + expect(b.posts.map((p) => p.id)).toEqual(['1', '2']); + expect(b.posts.find((p) => p.id === '2')?.user).toBe('y'); + }); + + it('populateFavorites fills favorites without duplicates', () => { + const a = reducer(undefined, populateFavorites([post('1')])); + const b = reducer(a, populateFavorites([post('1'), post('2')])); + expect(b.favorites.map((p) => p.id)).toEqual(['1', '2']); + }); + + it('setCountNewPosts sets counter', () => { + const s = reducer(undefined, setCountNewPosts(5)); + expect(s.countNewPosts).toBe(5); + }); +}); diff --git a/tests/state/globalSlice.test.ts b/tests/state/globalSlice.test.ts new file mode 100644 index 0000000..e38c2eb --- /dev/null +++ b/tests/state/globalSlice.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest'; +import reducer, { + togglePublishBlogModal, + toggleEditBlogModal, + setCurrentBlog, + setVisitingBlog, + setIsLoadingGlobal, + setAddToDownloads, + updateDownloads, + setUserAvatarHash, +} from '@/state/features/globalSlice'; + +describe('globalSlice reducers', () => { + it('toggles modals', () => { + let s = reducer(undefined, togglePublishBlogModal(true)); + expect(s.isOpenPublishBlogModal).toBe(true); + s = reducer(s, toggleEditBlogModal(true)); + expect(s.isOpenEditBlogModal).toBe(true); + }); + + it('sets current and visiting blog and clears loading flag', () => { + const blog = { createdAt: 1, blogId: 'b', title: 't', description: 'd', blogImage: '' }; + let s = reducer(undefined, setCurrentBlog(blog as any)); + expect(s.currentBlog?.blogId).toBe('b'); + expect(s.isLoadingCurrentBlog).toBe(false); + s = reducer(s, setVisitingBlog({ ...blog, name: 'alice' } as any)); + expect(s.visitingBlog?.name).toBe('alice'); + expect(s.isLoadingCurrentBlog).toBe(false); + }); + + it('sets global loading flag', () => { + const s = reducer(undefined, setIsLoadingGlobal(true)); + expect(s.isLoadingGlobal).toBe(true); + }); + + it('adds and updates downloads', () => { + let s = reducer(undefined, setAddToDownloads({ identifier: 'x', status: 'start' })); + expect(s.downloads['x'].status).toBe('start'); + s = reducer(s, updateDownloads({ identifier: 'x', progress: 50 })); + expect(s.downloads['x'].progress).toBe(50); + }); + + it('sets user avatar hash when valid', () => { + const s = reducer(undefined, setUserAvatarHash({ name: 'bob', url: 'u' })); + expect(s.userAvatarHash['bob']).toBe('u'); + }); +}); diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..e897fbb --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "baseUrl": "..", + "paths": { + "@/*": ["src/*"] + }, + "jsx": "react-jsx", + "types": ["vitest/globals", "vite/client"] + }, + "include": ["./**/*", "../src", "../vite-env.d.ts"] +} diff --git a/tests/types/expect-extensions.d.ts b/tests/types/expect-extensions.d.ts index 12dacb8..bb681ab 100644 --- a/tests/types/expect-extensions.d.ts +++ b/tests/types/expect-extensions.d.ts @@ -1,6 +1,14 @@ // tests/types/expect-extensions.d.ts import 'vitest'; -import 'jest-axe'; + +// Minimal ambient typings for jest-axe to satisfy TS in tests +declare module 'jest-axe' { + export interface AxeResults { + violations: any[]; + } + export function axe(container: HTMLElement): Promise; + export const toHaveNoViolations: any; +} declare module 'vitest' { interface Assertion { diff --git a/tests/types/jest-axe.d.ts b/tests/types/jest-axe.d.ts new file mode 100644 index 0000000..47df8c1 --- /dev/null +++ b/tests/types/jest-axe.d.ts @@ -0,0 +1,7 @@ +declare module 'jest-axe' { + export interface AxeResults { + violations: any[]; + } + export function axe(container: HTMLElement, options?: any): Promise; + export const toHaveNoViolations: any; +} diff --git a/tests/utils/blogIdformats.test.ts b/tests/utils/blogIdformats.test.ts new file mode 100644 index 0000000..dd3a651 --- /dev/null +++ b/tests/utils/blogIdformats.test.ts @@ -0,0 +1,28 @@ +import { describe, it, expect } from 'vitest'; +import { + addPrefix, + removePrefix, + extractCreateTitleIdAndId, + buildIdentifierFromCreateTitleIdAndId, +} from '@/utils/blogIdformats'; + +describe('blogIdformats', () => { + it('addPrefix adds q-blog- when missing', () => { + expect(addPrefix('abc')).toBe('q-blog-abc'); + expect(addPrefix('q-blog-abc')).toBe('q-blog-abc'); + }); + + it('removePrefix removes q-blog- when present', () => { + expect(removePrefix('q-blog-abc')).toBe('abc'); + expect(removePrefix('abc')).toBe('abc'); + }); + + it('extracts createTitleId and id part', () => { + expect(extractCreateTitleIdAndId('q-blog-foo-post-123')).toBe('123'); + expect(extractCreateTitleIdAndId('no-delimiter')).toBe(''); + }); + + it('builds identifier from parts', () => { + expect(buildIdentifierFromCreateTitleIdAndId('q-blog-foo', '123')).toBe('q-blog-foo-post-123'); + }); +}); diff --git a/tests/utils/checkStructure.test.ts b/tests/utils/checkStructure.test.ts new file mode 100644 index 0000000..dac06de --- /dev/null +++ b/tests/utils/checkStructure.test.ts @@ -0,0 +1,46 @@ +import { describe, it, expect } from 'vitest'; +import { checkStructure, checkStructureMailMessages } from '@/utils/checkStructure'; + +describe('checkStructure (blog)', () => { + const base = { + title: 't', + createdAt: Date.now(), + postContent: [], + }; + + it('invalid when required fields missing', () => { + expect(checkStructure({})).toBe(false); + expect(checkStructure({ ...base, title: '' })).toBe(false); + expect(checkStructure({ ...base, createdAt: 0 })).toBe(false); + }); + + it('valid with proper content blocks', () => { + const content = [ + { type: 'editor', version: 1, id: 'e1', content: [{ text: 'hello' }] }, + { type: 'image', version: 1, id: 'i1', content: { image: 'data:' } }, + ]; + expect(checkStructure({ ...base, postContent: content })).toBe(true); + }); + + it('invalid with unsupported type for version 1', () => { + const content = [{ type: 'unknown', version: 1, id: 'x', content: {} }]; + expect(checkStructure({ ...base, postContent: content })).toBe(false); + }); +}); + +describe('checkStructureMailMessages', () => { + it('valid minimal', () => { + const mail = { + createdAt: Date.now(), + version: 1, + attachments: [], + textContent: [], + generalData: {}, + }; + expect(checkStructureMailMessages(mail)).toBe(true); + }); + + it('invalid if arrays/objects missing', () => { + expect(checkStructureMailMessages({})).toBe(false); + }); +}); diff --git a/tests/utils/extractTextFromSlate.test.ts b/tests/utils/extractTextFromSlate.test.ts new file mode 100644 index 0000000..6101b97 --- /dev/null +++ b/tests/utils/extractTextFromSlate.test.ts @@ -0,0 +1,16 @@ +import { describe, it, expect } from 'vitest'; +import { extractTextFromSlate } from '@/utils/extractTextFromSlate'; + +describe('extractTextFromSlate', () => { + it('returns empty string for non-array', () => { + expect(extractTextFromSlate(null as unknown as any[])).toBe(''); + }); + + it('concatenates text from nested nodes', () => { + const nodes = [ + { text: 'Hello ' }, + { children: [{ text: 'world' }, { children: [{ text: '!' }] }] }, + ]; + expect(extractTextFromSlate(nodes)).toBe('Hello world!'); + }); +}); diff --git a/tests/utils/fetchMail.test.ts b/tests/utils/fetchMail.test.ts new file mode 100644 index 0000000..025f8ed --- /dev/null +++ b/tests/utils/fetchMail.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { fetchAndEvaluateMail } from '@/utils/fetchMail'; + +const original = (global as any).qortalRequest; + +beforeEach(() => { + (global as any).qortalRequest = vi.fn(); +}); + +afterEach(() => { + (global as any).qortalRequest = original; + vi.restoreAllMocks(); +}); + +const validDecrypted = { + title: 'Mail title', + createdAt: 1234, + version: 1, + attachments: [], + textContent: [], + generalData: {}, +}; + +describe('fetchAndEvaluateMail', () => { + it('returns isValid=false when prerequisites fail', async () => { + // No owner + (global as any).qortalRequest = vi + .fn() + // FETCH_QDN_RESOURCE + .mockResolvedValueOnce('encrypted') + // GET_NAME_DATA + .mockResolvedValueOnce({}); + + const res = await fetchAndEvaluateMail({ + user: 'u', + messageIdentifier: 'mid', + otherUser: 'other', + content: {}, + }); + expect(res.isValid).toBe(false); + }); + + it('returns mapped object when all checks pass', async () => { + const decryptedB64 = btoa(JSON.stringify(validDecrypted)); + (global as any).qortalRequest = vi + .fn() + // FETCH_QDN_RESOURCE + .mockResolvedValueOnce('encrypted') + // GET_NAME_DATA + .mockResolvedValueOnce({ owner: 'ADDR' }) + // GET_ACCOUNT_DATA + .mockResolvedValueOnce({ publicKey: 'PUB' }) + // DECRYPT_DATA + .mockResolvedValueOnce(decryptedB64); + + const res = await fetchAndEvaluateMail({ + user: 'u', + messageIdentifier: 'mid', + otherUser: 'other', + content: {}, + }); + expect(res.isValid).toBe(true); + expect(res.id).toBe('mid'); + expect(res.title).toBe('Mail title'); + expect(res.createdAt).toBe(1234); + }); +}); diff --git a/tests/utils/fetchPosts.test.ts b/tests/utils/fetchPosts.test.ts new file mode 100644 index 0000000..260fa83 --- /dev/null +++ b/tests/utils/fetchPosts.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect } from 'vitest'; +import { server } from '../msw/server'; +import { http, HttpResponse } from 'msw'; +import { fetchAndEvaluatePosts } from '@/utils/fetchPosts'; + +const validPost = (overrides?: Partial) => ({ + title: 'Hello', + createdAt: 123, + postContent: [ + { type: 'image', version: 1, id: 'img1', content: { image: 'img-data' } }, + { type: 'editor', version: 1, id: 'ed1', content: [{ text: 'Some text' }] }, + ], + ...overrides, +}); + +describe('fetchAndEvaluatePosts', () => { + it('returns isValid=false when structure invalid', async () => { + server.use(http.get('/arbitrary/BLOG_POST/:user/:postId', () => HttpResponse.json({}))); + const res = await fetchAndEvaluatePosts({ user: 'u', postId: 'p', content: {} }); + expect(res.isValid).toBe(false); + }); + + it('maps fields and extracts description when editor present', async () => { + server.use( + http.get('/arbitrary/BLOG_POST/:user/:postId', () => HttpResponse.json(validPost())), + ); + const content = { description: '' }; + const res = await fetchAndEvaluatePosts({ user: 'alice', postId: 'post1', content }); + expect(res.isValid).toBe(true); + expect(res.user).toBe('alice'); + expect(res.id).toBe('post1'); + expect(res.title).toBe('Hello'); + expect(res.postImage).toBe('img-data'); + expect(res.description).toBe('Some text'); + }); + + it('keeps existing description when provided', async () => { + server.use( + http.get('/arbitrary/BLOG_POST/:user/:postId', () => HttpResponse.json(validPost())), + ); + const content = { description: 'keep me' }; + const res = await fetchAndEvaluatePosts({ user: 'alice', postId: 'post1', content }); + expect(res.description).toBe('keep me'); + }); +}); diff --git a/tests/utils/qortalRequestFunctions.test.ts b/tests/utils/qortalRequestFunctions.test.ts new file mode 100644 index 0000000..b78e937 --- /dev/null +++ b/tests/utils/qortalRequestFunctions.test.ts @@ -0,0 +1,45 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { getAccountNames, getPrimaryAccountName } from '@/utils/qortalRequestFunctions'; + +const original = (global as any).qortalRequest; + +beforeEach(() => { + (global as any).qortalRequest = vi.fn(); +}); + +afterEach(() => { + (global as any).qortalRequest = original; + vi.restoreAllMocks(); +}); + +describe('qortalRequestFunctions', () => { + it('getAccountNames returns mapped list', async () => { + (global as any).qortalRequest = vi.fn().mockResolvedValueOnce([ + { name: 'alice', owner: 'ADDR1' }, + { name: 'bob', owner: 'ADDR2' }, + ]); + const list = await getAccountNames('ADDR1'); + expect(list).toEqual([ + { name: 'alice', owner: 'ADDR1' }, + { name: 'bob', owner: 'ADDR2' }, + ]); + }); + + it('getAccountNames falls back on empty/non-array/error', async () => { + (global as any).qortalRequest = vi.fn().mockResolvedValueOnce('not-an-array'); + const a = await getAccountNames('X'); + expect(a).toEqual([{ name: '', owner: 'X' }]); + (global as any).qortalRequest = vi.fn().mockRejectedValueOnce(new Error('boom')); + const b = await getAccountNames('Y'); + expect(b).toEqual([{ name: '', owner: 'Y' }]); + }); + + it('getPrimaryAccountName returns string or empty', async () => { + (global as any).qortalRequest = vi.fn().mockResolvedValueOnce('alice'); + expect(await getPrimaryAccountName('ADDR')).toBe('alice'); + (global as any).qortalRequest = vi.fn().mockResolvedValueOnce({}); + expect(await getPrimaryAccountName('ADDR')).toBe(''); + (global as any).qortalRequest = vi.fn().mockRejectedValueOnce(new Error('x')); + expect(await getPrimaryAccountName('ADDR')).toBe(''); + }); +}); diff --git a/tests/utils/time.test.ts b/tests/utils/time.test.ts new file mode 100644 index 0000000..a71d031 --- /dev/null +++ b/tests/utils/time.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import moment from 'moment'; +import { formatTimestamp, formatDate } from '@/utils/time'; + +describe('time utils', () => { + const fixed = 1700000000000; // fixed ms timestamp + + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(fixed); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('formatTimestamp: Just now for <1m', () => { + expect(formatTimestamp(fixed - 10 * 1000)).toBe('Just now'); + }); + + it('formatTimestamp: minutes for <60m', () => { + expect(formatTimestamp(fixed - 23 * 60 * 1000)).toBe('23m'); + }); + + it('formatTimestamp: hours for <24h', () => { + expect(formatTimestamp(fixed - 3 * 60 * 60 * 1000)).toBe('3h'); + }); + + it('formatTimestamp: MMM D for >=24h', () => { + const s = formatTimestamp(fixed - 3 * 24 * 60 * 60 * 1000); + expect(s).toMatch(/^[A-Z][a-z]{2} \d{1,2}$/); + }); + + it('formatDate: relative time', () => { + const ts = fixed - 2 * 60 * 60 * 1000; + // moment.fromNow() value depends on locale; check contains 'ago' + const out = formatDate(ts); + expect(typeof out).toBe('string'); + }); +}); diff --git a/tests/utils/toBase64.test.ts b/tests/utils/toBase64.test.ts new file mode 100644 index 0000000..1eae572 --- /dev/null +++ b/tests/utils/toBase64.test.ts @@ -0,0 +1,88 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { + objectToUint8Array, + uint8ArrayToObject, + base64ToUint8Array, + uint8ArrayToBase64, + objectToBase64, + toBase64, + processFileInChunks, +} from '@/utils/toBase64'; + +// Basic conversions not requiring FileReader +describe('binary utils', () => { + it('objectToUint8Array / uint8ArrayToObject roundtrip', () => { + const obj = { a: 1, b: 'x' }; + const u8 = objectToUint8Array(obj); + expect(u8).toBeInstanceOf(Uint8Array); + const back = uint8ArrayToObject(u8); + expect(back).toEqual(obj); + }); + + it('base64 <-> uint8 array', () => { + const bytes = new Uint8Array([1, 2, 3, 4]); + const b64 = uint8ArrayToBase64(bytes); + const back = base64ToUint8Array(b64); + expect(Array.from(back)).toEqual([1, 2, 3, 4]); + }); +}); + +// FileReader-based functions: mock FileReader +describe('FileReader-based utils', () => { + const original = global.FileReader; + + beforeEach(() => { + class FRMock { + public onload: ((this: FileReader, ev: ProgressEvent) => any) | null = null; + public onloadend: ((this: FileReader, ev: ProgressEvent) => any) | null = null; + public onerror: ((this: FileReader, ev: ProgressEvent) => any) | null = null; + public result: string | ArrayBuffer | null = null; + readAsDataURL(_: Blob) { + // simulate base64-encoded JSON + this.result = 'data:application/json;base64,' + btoa('{"a":1}'); + setTimeout(() => { + this.onload && this.onload.call(this as any, {} as any); + this.onloadend && this.onloadend.call(this as any, {} as any); + }, 0); + } + readAsArrayBuffer(_: Blob) { + this.result = new Uint8Array([1, 2, 3, 4]).buffer; + const ev = { target: { result: this.result } } as any; + setTimeout(() => { + this.onload && this.onload.call(this as any, ev); + this.onloadend && this.onloadend.call(this as any, ev); + }, 0); + } + } + (global as unknown as any).FileReader = FRMock as any; + (globalThis as any).FileReader = FRMock as any; + if (typeof window !== 'undefined') (window as any).FileReader = FRMock as any; + }); + + afterEach(() => { + (global as unknown as any).FileReader = original as any; + (globalThis as any).FileReader = original as any; + if (typeof window !== 'undefined') (window as any).FileReader = original as any; + }); + + it('objectToBase64 produces base64 string', async () => { + const out = await objectToBase64({ a: 1 }); + expect(typeof out).toBe('string'); + expect(out).toBe(btoa('{"a":1}')); + }); + + it('toBase64 handles blob-like file', async () => { + // Use Blob to avoid environment-specific File constructor quirks + const file = new Blob([new Uint8Array([1, 2])], { + type: 'application/octet-stream', + }) as unknown as File; + const out = await toBase64(file); + expect(typeof out === 'string' || out instanceof ArrayBuffer).toBeTruthy(); + }); + + it('processFileInChunks resolves Uint8Array', async () => { + const file = new File([new Uint8Array([1, 2, 3, 4])], 'x.bin'); + const out = await processFileInChunks(file); + expect(out).toBeInstanceOf(Uint8Array); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index e60f06c..af34832 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,8 +8,22 @@ export default defineConfig({ css: false, globals: true, coverage: { + enabled: true, provider: 'v8', reporter: ['text', 'lcov'], + include: [ + 'src/utils/blogIdformats.ts', + 'src/utils/extractTextFromSlate.ts', + 'src/utils/time.ts', + 'src/utils/toBase64.ts', + 'src/state/features/blogSlice.ts', + ], + thresholds: { + statements: 50, + branches: 40, + functions: 50, + lines: 50, + }, }, }, resolve: { -- 2.43.0 From 2d051354c9d0ac463480eabc07ddc46802930d51 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Thu, 21 Aug 2025 20:03:11 -0400 Subject: [PATCH 31/42] =?UTF-8?q?release:=20v0.1.0=20=E2=80=94=20multi-blo?= =?UTF-8?q?g=20(user=20blogs=20page,=20smart=20redirect,=20header=20dropdo?= =?UTF-8?q?wn,=20name-click=20nav);=20docs=20+=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE_NOTES_multiblog.md | 34 +++++ docs/RELEASE_NOTES_v0.1.0.md | 24 ++++ docs/USER_ANNOUNCEMENT_v0.1.0.md | 11 ++ package-lock.json | 34 +---- package.json | 4 +- src/App.tsx | 4 + src/components/layout/Navbar/Navbar.tsx | 89 ++++++++++-- .../BlogIndividualPost/BlogIndividualPost.tsx | 2 +- src/pages/BlogList/PostPreview.tsx | 34 ++++- src/pages/UserBlogs/NameRootRedirect.tsx | 33 +++++ src/pages/UserBlogs/UserBlogs.tsx | 136 ++++++++++++++++++ src/utils/blogs.ts | 55 +++++++ src/wrappers/GlobalWrapper.tsx | 34 +++++ tests/components/Navbar.multiblog.test.tsx | 51 +++++++ tests/components/PostPreview.a11y.test.tsx | 29 ++-- .../components/PostPreview.navigate.test.tsx | 47 ++++++ tests/pages/BlogIndividualPost.test.tsx | 43 ++++++ tests/pages/UserBlogs.test.tsx | 43 ++++++ tests/routes/NameRootRedirect.test.tsx | 63 ++++++++ 19 files changed, 705 insertions(+), 65 deletions(-) create mode 100644 docs/RELEASE_NOTES_multiblog.md create mode 100644 docs/RELEASE_NOTES_v0.1.0.md create mode 100644 docs/USER_ANNOUNCEMENT_v0.1.0.md create mode 100644 src/pages/UserBlogs/NameRootRedirect.tsx create mode 100644 src/pages/UserBlogs/UserBlogs.tsx create mode 100644 src/utils/blogs.ts create mode 100644 tests/components/Navbar.multiblog.test.tsx create mode 100644 tests/components/PostPreview.navigate.test.tsx create mode 100644 tests/pages/UserBlogs.test.tsx create mode 100644 tests/routes/NameRootRedirect.test.tsx diff --git a/docs/RELEASE_NOTES_multiblog.md b/docs/RELEASE_NOTES_multiblog.md new file mode 100644 index 0000000..6b92087 --- /dev/null +++ b/docs/RELEASE_NOTES_multiblog.md @@ -0,0 +1,34 @@ +# Q-Blog — Multi-Blog Feature Notes (Patch) + +Date: 2025-08-21 + +This patch implements ADR-0005 (Allow Multiple Blogs per Name) using the Plan A approach described in docs/features. + +Highlights + +- Added new route `/:user/blogs` listing all blogs for a Name. +- Added smart redirect for `/:user`: + - 1 blog → `/:user/:blog` (history replace) + - 0 or >1 → `/:user/blogs` +- Converted header action from a single “My Blog” link to a “My Blogs” dropdown with per-blog entries and a “Create new blog” action. For 0 blogs, the header shows a primary “Create Blog” button. +- Kept all existing blog-scoped routes intact. + +Code Map + +- `src/utils/blogs.ts`: list + fetch blog details helpers. +- `src/pages/UserBlogs/UserBlogs.tsx`: new list page with owner CTAs. +- `src/pages/UserBlogs/NameRootRedirect.tsx`: redirect component for `/:user`. +- `src/App.tsx`: routes added for `/:user/blogs` and `/:user`. +- `src/wrappers/GlobalWrapper.tsx`: fetches current user blogs; wires Navbar selection to set active `currentBlog` and navigate. +- `src/components/layout/Navbar/Navbar.tsx`: “My Blogs” dropdown, accessible popover menu; primary Create button when 0 blogs. + +Tests + +- `tests/routes/NameRootRedirect.test.tsx`: redirects for single vs multiple blogs. +- `tests/pages/UserBlogs.test.tsx`: renders list. +- `tests/components/Navbar.multiblog.test.tsx`: Create vs My Blogs visibility. + +Notes + +- Redirect target within Q‑Blog is `/:user/:blog` (this app’s profile/posts page), not `…/posts`. +- No data migrations; all changes are UI/routing. diff --git a/docs/RELEASE_NOTES_v0.1.0.md b/docs/RELEASE_NOTES_v0.1.0.md new file mode 100644 index 0000000..b89aa4b --- /dev/null +++ b/docs/RELEASE_NOTES_v0.1.0.md @@ -0,0 +1,24 @@ +# Q‑Blog v0.1.0 — Multiple Blogs per Name + +Release date: 2025-08-21 + +This release introduces multi‑blog support per Name with a minimal, backwards‑compatible routing update and improved navigation. + +Highlights +- User blogs page: `/:user/blogs` lists all blogs for a Name, with owner actions to Edit/Create. +- Smart redirect for `/:user`: + - Exactly 1 blog → `/:user/:blog` (history replace) + - 0 or >1 blogs → `/:user/blogs` +- Header: “My Blogs” dropdown (lists all your blogs + “Create new blog”); when 0 blogs, shows “Create Blog” button. +- Name clicks: Clicking an author name/avatar now navigates to `/:user` (which resolves to the blogs list unless a single blog exists). +- Editor flow: You can create and edit posts per blog; switching blogs via the header updates the active blog context. + +Developer notes +- New files: `src/utils/blogs.ts`, `src/pages/UserBlogs/UserBlogs.tsx`, `src/pages/UserBlogs/NameRootRedirect.tsx`. +- Updated routes in `src/App.tsx`. +- `GlobalWrapper` now fetches current user blogs and wires selection to set `currentBlog`. +- `Navbar` updated to a dropdown menu with accessible patterns (popover + list + focus return). +- Tests cover redirect behavior, page rendering, and header menu states. + +No data migrations. Deep links to `/:user/:blog/...` continue to work. + diff --git a/docs/USER_ANNOUNCEMENT_v0.1.0.md b/docs/USER_ANNOUNCEMENT_v0.1.0.md new file mode 100644 index 0000000..baecdc5 --- /dev/null +++ b/docs/USER_ANNOUNCEMENT_v0.1.0.md @@ -0,0 +1,11 @@ +# Q‑Blog v0.1.0 — What’s New + +Q‑Blog now supports multiple blogs per Name. + +- New: a “My Blogs” menu to switch between your blogs or create a new one +- New: a user blogs page at `/:user/blogs` listing all blogs for that name +- Smarter name links: clicking a user name takes you to their blogs list (or straight to their single blog if they have only one) + +Try it here: qortal://app/qblog + +If you have feedback or run into any issues, please let us know. Enjoy blogging! diff --git a/package-lock.json b/package-lock.json index a39a317..f0586cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "q-blog", - "version": "0.0.2", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "q-blog", - "version": "0.0.2", + "version": "0.1.0", "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", @@ -10563,21 +10563,6 @@ } } }, - "node_modules/vite-node/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -11240,21 +11225,6 @@ } } }, - "node_modules/vitest/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 2e0d49d..8a84773 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "q-blog", "private": true, - "version": "0.0.2", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", @@ -9,7 +9,7 @@ "preview": "vite preview", "lint": "eslint .", "format": "prettier --check .", - "format:write": "prettier --write .", + "format:fix": "prettier --write .", "test": "vitest run", "test:run": "vitest run", "typecheck": "tsc -p tsconfig.json -noEmit", diff --git a/src/App.tsx b/src/App.tsx index aabcb7e..ec0d1d0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,8 @@ import { useIframe } from './hooks/useIframe'; import { BlogIndividualPost } from './pages/BlogIndividualPost/BlogIndividualPost'; import { BlogIndividualProfile } from './pages/BlogIndividualProfile/BlogIndividualProfile'; import { BlogList } from './pages/BlogList/BlogList'; +import UserBlogs from './pages/UserBlogs/UserBlogs'; +import NameRootRedirect from './pages/UserBlogs/NameRootRedirect'; import { CreatePost } from './pages/CreatePost/CreatePost'; import { CreatEditProfile } from './pages/CreateEditProfile/CreatEditProfile'; import { ThemeProvider } from '@mui/material/styles'; @@ -37,6 +39,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 4cc28c7..68fd659 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -22,6 +22,7 @@ import { togglePublishBlogModal } from '../../../state/features/globalSlice'; import { useDispatch, useSelector } from 'react-redux'; import AutoStoriesIcon from '@mui/icons-material/AutoStories'; import { RootState } from '../../../state/store'; +import type { BlogSummary } from '../../../utils/blogs'; import { UserNavbar } from '../../common/UserNavbar/UserNavbar'; import { removePrefix } from '../../../utils/blogIdformats'; import { useLocation } from 'react-router-dom'; @@ -72,6 +73,8 @@ interface Props { hasAttemptedToFetchBlogInitial: boolean; allNames: NameRecord[]; onSwitchActiveName: (name: string) => void; + userBlogs?: BlogSummary[]; + onSelectBlog?: (blog: BlogSummary) => void; } function useQuery() { @@ -88,6 +91,8 @@ const NavBar: React.FC = ({ hasAttemptedToFetchBlogInitial, allNames, onSwitchActiveName, + userBlogs = [], + onSelectBlog, }) => { const navigate = useNavigate(); const dispatch = useDispatch(); @@ -107,6 +112,7 @@ const NavBar: React.FC = ({ const [anchorElNotification, setAnchorElNotification] = React.useState( null, ); + const [anchorElBlogs, setAnchorElBlogs] = React.useState(null); const [isOpenModal, setIsOpenModal] = React.useState(false); const [searchVal, setSearchVal] = useState(''); const searchValRef = useRef(''); @@ -130,6 +136,7 @@ const NavBar: React.FC = ({ const handleClose = () => { setAnchorEl(null); }; + const closeBlogs = () => setAnchorElBlogs(null); const onClose = () => { setIsOpenModal(false); }; @@ -137,6 +144,8 @@ const NavBar: React.FC = ({ const id = open ? 'simple-popover' : undefined; const openPopover = Boolean(anchorElNotification); const idNotification = openPopover ? 'simple-popover-notification' : undefined; + const openBlogs = Boolean(anchorElBlogs); + const idBlogs = openBlogs ? 'blogs-popover' : undefined; const [viewMode, setViewMode] = useState<'tile' | 'list'>(() => { const saved = localStorage.getItem('qblog_view'); @@ -375,17 +384,20 @@ const NavBar: React.FC = ({ > add notification */} - {isAuthenticated && userName && hasAttemptedToFetchBlogInitial && !hasBlog && ( - { - dispatch(togglePublishBlogModal(true)); - }} - > - - Create Blog - - )} - {isAuthenticated && userName && hasBlog && ( + {isAuthenticated && + userName && + hasAttemptedToFetchBlogInitial && + (userBlogs?.length || 0) === 0 && ( + { + dispatch(togglePublishBlogModal(true)); + }} + > + + Create Blog + + )} + {isAuthenticated && userName && (userBlogs?.length || 0) >= 1 && ( <> = ({ } - onClick={() => { - navigate(`/${userName}/${blog.blogId}`); + aria-haspopup="menu" + aria-controls={idBlogs} + aria-expanded={openBlogs ? 'true' : undefined} + onClick={(e: any) => { + const target = e.currentTarget as HTMLButtonElement; + setAnchorElBlogs(target); }} > - My Blog + My Blogs + + + + + {(userBlogs || []).map((b) => { + const isActive = blog?.blogId === b.blogId; + return ( + { + onSelectBlog && onSelectBlog(b); + closeBlogs(); + }} + > + + + ); + })} + + + + + + + )} diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index 304774a..0f0831c 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -312,7 +312,7 @@ export const BlogIndividualPost = () => { > { - navigate(`/${user}/${blog}`); + navigate(`/${user}`); }} sx={{ cursor: 'pointer', diff --git a/src/pages/BlogList/PostPreview.tsx b/src/pages/BlogList/PostPreview.tsx index 2d05545..3fb2081 100644 --- a/src/pages/BlogList/PostPreview.tsx +++ b/src/pages/BlogList/PostPreview.tsx @@ -46,6 +46,7 @@ import BlockIcon from '@mui/icons-material/Block'; import { CustomIcon } from '../../components/common/CustomIcon'; import ResponsiveImage from '../../components/common/ResponsiveImage'; import { formatDate } from '../../utils/time'; +import { useNavigate } from 'react-router-dom'; interface BlogPostPreviewProps { title: string; createdAt: number | string; @@ -76,6 +77,7 @@ const BlogPostPreview: React.FC = ({ const dispatch = useDispatch(); const theme = useTheme(); + const navigate = useNavigate(); const favoritesLocal = useSelector((state: RootState) => state.blog.favoritesLocal); const [isOpenAlert, setIsOpenAlert] = useState(false); const subscriptions = useSelector((state: RootState) => state.blog.subscriptions); @@ -118,7 +120,7 @@ const BlogPostPreview: React.FC = ({ }, [favoritesLocal, blogPost?.id]); const blockUserFunc = async (user: string) => { - if (user === 'Q-Blog') return; + if (user === 'Q-Blog' || user === 'qblog') return; if (subscriptions.includes(user) && username) { try { const listName = `q-blog-subscriptions-${username}`; @@ -199,7 +201,15 @@ const BlogPostPreview: React.FC = ({ }} > - + { + e.stopPropagation(); + navigate(`/${author}`); + }} + sx={{ cursor: 'pointer' }} + /> @@ -209,6 +219,11 @@ const BlogPostPreview: React.FC = ({ color={ theme.palette.mode === 'light' ? theme.palette.text.secondary : '#d6e8ff' } + onClick={(e: any) => { + e.stopPropagation(); + navigate(`/${author}`); + }} + sx={{ cursor: 'pointer' }} > {author} @@ -269,7 +284,15 @@ const BlogPostPreview: React.FC = ({ - + { + e.stopPropagation(); + navigate(`/${author}`); + }} + sx={{ cursor: 'pointer' }} + /> @@ -277,6 +300,11 @@ const BlogPostPreview: React.FC = ({ { + e.stopPropagation(); + navigate(`/${author}`); + }} + sx={{ cursor: 'pointer' }} > {author} diff --git a/src/pages/UserBlogs/NameRootRedirect.tsx b/src/pages/UserBlogs/NameRootRedirect.tsx new file mode 100644 index 0000000..bb17c31 --- /dev/null +++ b/src/pages/UserBlogs/NameRootRedirect.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Box, Typography } from '@mui/material'; +import { listBlogsByName } from '../../utils/blogs'; + +export const NameRootRedirect: React.FC = () => { + const { user } = useParams(); + const navigate = useNavigate(); + React.useEffect(() => { + let mounted = true; + (async () => { + if (!user) return; + const blogs = await listBlogsByName(user); + if (!mounted) return; + if (blogs.length === 1) { + navigate(`/${user}/${blogs[0].handle}`, { replace: true }); + } else { + navigate(`/${user}/blogs`, { replace: true }); + } + })(); + return () => { + mounted = false; + }; + }, [user, navigate]); + + return ( + + Loading blogs… + + ); +}; + +export default NameRootRedirect; diff --git a/src/pages/UserBlogs/UserBlogs.tsx b/src/pages/UserBlogs/UserBlogs.tsx new file mode 100644 index 0000000..cb25652 --- /dev/null +++ b/src/pages/UserBlogs/UserBlogs.tsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; +import { Box, Button, Divider, List, ListItem, ListItemText, Typography } from '@mui/material'; +import { RootState } from '../../state/store'; +import { BlogSummary, fetchBlogDetails, listBlogsByName } from '../../utils/blogs'; +import { + setCurrentBlog, + toggleEditBlogModal, + togglePublishBlogModal, +} from '../../state/features/globalSlice'; +import { removePrefix } from '../../utils/blogIdformats'; + +export const UserBlogs: React.FC = () => { + const { user: routeUser } = useParams(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const authUser = useSelector((s: RootState) => s.auth.user); + const isOwner = !!authUser?.name && authUser?.name === routeUser; + + const [blogs, setBlogs] = React.useState([]); + const [loading, setLoading] = React.useState(true); + + const load = React.useCallback(async () => { + if (!routeUser) return; + setLoading(true); + try { + const res = await listBlogsByName(routeUser); + setBlogs(res); + } finally { + setLoading(false); + } + }, [routeUser]); + + React.useEffect(() => { + load(); + }, [load]); + + const onView = (b: BlogSummary) => navigate(`/${b.name}/${b.handle}`); + const onEdit = async (b: BlogSummary) => { + if (!isOwner) return; + const details = await fetchBlogDetails(b.name, b.blogId); + if (!details) return; + dispatch( + setCurrentBlog({ + createdAt: details?.createdAt || Date.now(), + blogId: b.blogId, + title: details?.title || '', + description: details?.description || '', + blogImage: details?.blogImage || '', + category: details?.category, + tags: details?.tags || [], + }), + ); + dispatch(toggleEditBlogModal(true)); + }; + + const onCreate = () => dispatch(togglePublishBlogModal(true)); + + if (loading) { + return ( + + Loading blogs… + + ); + } + + return ( + + + {`${routeUser}'s Blogs`} + + + {blogs.length === 0 ? ( + + + {isOwner ? "You don't have any blogs yet." : 'No public blogs yet.'} + + {isOwner && ( + + )} + + ) : ( + <> + {isOwner && ( + + + + )} + + {blogs.map((b, idx) => ( + + + + {isOwner && ( + + )} + + } + > + + + {idx < blogs.length - 1 && } + + ))} + + + )} + + ); +}; + +export default UserBlogs; diff --git a/src/utils/blogs.ts b/src/utils/blogs.ts new file mode 100644 index 0000000..49c9187 --- /dev/null +++ b/src/utils/blogs.ts @@ -0,0 +1,55 @@ +import { addPrefix, removePrefix } from './blogIdformats'; + +export interface BlogSummary { + name: string; + blogId: string; // full identifier e.g., q-blog-xxxxx + handle: string; // short handle without prefix + title: string; + createdAt?: number; +} + +// Lists all BLOG resources for an exact name whose identifier starts with q-blog- +export async function listBlogsByName(name: string): Promise { + if (!name) return []; + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG&identifier=q-blog-&exactmatchnames=true&name=${encodeURIComponent( + name, + )}&prefix=true&limit=100&includemetadata=true`; + + const resp = await fetch(url, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + if (!resp.ok) return []; + const data = await resp.json(); + const items = (Array.isArray(data) ? data : []).filter( + (b: any) => typeof b?.identifier === 'string' && b.identifier.startsWith('q-blog-'), + ); + return items.map((b: any) => ({ + name: b.name, + blogId: b.identifier, + handle: removePrefix(b.identifier), + title: b?.metadata?.title || '', + createdAt: b?.created, + })); +} + +export interface BlogDetails { + title: string; + description: string; + blogImage: string; + createdAt: number; + category?: string; + tags?: string[]; +} + +// Fetch a specific blog JSON payload +export async function fetchBlogDetails(name: string, blogId: string): Promise { + if (!name || !blogId) return null; + const url = `/arbitrary/BLOG/${encodeURIComponent(name)}/${encodeURIComponent(blogId)}`; + const resp = await fetch(url, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + if (!resp.ok) return null; + return await resp.json(); +} diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index 54129b6..2544dc1 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -39,6 +39,7 @@ import { NameRecord, } from '../utils/qortalRequestFunctions'; import { removePrefix } from '../utils/blogIdformats'; +import { BlogSummary, fetchBlogDetails, listBlogsByName } from '../utils/blogs'; interface Props { children: React.ReactNode; @@ -56,6 +57,7 @@ const GlobalWrapper: React.FC = ({ children }) => { const [userAvatar, setUserAvatar] = useState(''); const [allNames, setAllNames] = useState([]); + const [userBlogs, setUserBlogs] = useState([]); const interval = useRef(null); const { user } = useSelector((state: RootState) => state.auth); const { audios, currAudio } = useSelector((state: RootState) => state.global); @@ -90,6 +92,11 @@ const GlobalWrapper: React.FC = ({ children }) => { getFavorites(); getSubscriptions(); getAvatar(); + // fetch all blogs for current user + (async () => { + const blogs = await listBlogsByName(user.name!); + setUserBlogs(blogs); + })(); }, [user?.name]); const getAvatar = async () => { @@ -441,6 +448,9 @@ const GlobalWrapper: React.FC = ({ children }) => { dispatch(addUser({ ...user, name: newName })); const blogId = await getBlog(newName); setHasAttemptedToFetchBlogInitial(true); + // refresh user blogs for new name + const blogs = await listBlogsByName(newName); + setUserBlogs(blogs); if (blogId) { navigate(`/${newName}/${removePrefix(blogId)}`); } else { @@ -450,6 +460,28 @@ const GlobalWrapper: React.FC = ({ children }) => { [user, navigate], ); + const onSelectBlog = React.useCallback( + async (blog: BlogSummary) => { + if (!user?.name) return; + const details = await fetchBlogDetails(user.name, blog.blogId); + if (details) { + dispatch( + setCurrentBlog({ + createdAt: details?.createdAt || Date.now(), + blogId: blog.blogId, + title: details?.title || '', + description: details?.description || '', + blogImage: details?.blogImage || '', + category: details?.category, + tags: details?.tags || [], + }), + ); + } + navigate(`/${user.name}/${blog.handle}`); + }, + [user?.name], + ); + const onClosePublishBlogModal = React.useCallback(() => { dispatch(togglePublishBlogModal(false)); }, []); @@ -650,6 +682,8 @@ const GlobalWrapper: React.FC = ({ children }) => { hasAttemptedToFetchBlogInitial={hasAttemptedToFetchBlogInitial} allNames={allNames} onSwitchActiveName={switchActiveName} + userBlogs={userBlogs} + onSelectBlog={onSelectBlog} /> {children} diff --git a/tests/components/Navbar.multiblog.test.tsx b/tests/components/Navbar.multiblog.test.tsx new file mode 100644 index 0000000..03a247d --- /dev/null +++ b/tests/components/Navbar.multiblog.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import NavBar from '@/components/layout/Navbar/Navbar'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; + +const baseProps = { + isAuthenticated: true, + hasBlog: true, + userName: 'alice', + userAvatar: '', + blog: { blogId: 'q-blog-a' }, + authenticate: vi.fn(), + hasAttemptedToFetchBlogInitial: true, + allNames: [], + onSwitchActiveName: vi.fn(), +}; + +describe('Navbar multiblog', () => { + it('shows Create Blog when user has 0 blogs', () => { + render( + + + + + + + , + ); + expect(screen.getByText('Create Blog')).toBeInTheDocument(); + }); + + it('shows My Blogs dropdown when user has >=1 blogs', () => { + render( + + + + + + + , + ); + expect(screen.getByText('My Blogs')).toBeInTheDocument(); + }); +}); diff --git a/tests/components/PostPreview.a11y.test.tsx b/tests/components/PostPreview.a11y.test.tsx index 648e1b0..0fc4fef 100644 --- a/tests/components/PostPreview.a11y.test.tsx +++ b/tests/components/PostPreview.a11y.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { Provider } from 'react-redux'; import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import { axe } from 'jest-axe'; import PostPreview from '@/pages/BlogList/PostPreview'; @@ -22,19 +23,21 @@ describe('PostPreview a11y', () => { const { container } = render( - + + + , ); diff --git a/tests/components/PostPreview.navigate.test.tsx b/tests/components/PostPreview.navigate.test.tsx new file mode 100644 index 0000000..ca19044 --- /dev/null +++ b/tests/components/PostPreview.navigate.test.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import PostPreview from '@/pages/BlogList/PostPreview'; +import { store } from '@/state/store'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; + +describe('PostPreview author navigation', () => { + it('clicking author navigates to /:user', async () => { + render( + + + + + + } + /> + User Root} /> + + + + , + ); + + // click the author text + const author = await screen.findByText('alice'); + author.click(); + expect(await screen.findByText('User Root')).toBeInTheDocument(); + }); +}); diff --git a/tests/pages/BlogIndividualPost.test.tsx b/tests/pages/BlogIndividualPost.test.tsx index 0d4ec57..184d531 100644 --- a/tests/pages/BlogIndividualPost.test.tsx +++ b/tests/pages/BlogIndividualPost.test.tsx @@ -49,6 +49,7 @@ describe('BlogIndividualPost', () => { } /> + User Root} /> @@ -57,4 +58,46 @@ describe('BlogIndividualPost', () => { expect(await screen.findByText('Unit Test Post')).toBeInTheDocument(); }); + + it('clicking author area navigates to /:user', async () => { + server.use( + http.get('/arbitrary/BLOG/:user/:blog', ({ params }) => + HttpResponse.json({ + title: 'My Blog', + createdAt: 1, + blogId: params.blog, + description: 'd', + blogImage: '', + }), + ), + http.get('/arbitrary/BLOG_POST/:user/:fullId', ({ params }) => { + const { fullId, user } = params as any; + if (typeof fullId === 'string' && fullId.includes('-post-') && user) { + return HttpResponse.json(postResponse); + } + return HttpResponse.json({}, { status: 404 }); + }), + http.get('/arbitrary/resources/search', () => HttpResponse.json([])), + ); + + render( + + + + + } /> + User Root} /> + + + + , + ); + + // Wait for load + expect(await screen.findByText('Unit Test Post')).toBeInTheDocument(); + // Click on the username text in the CardHeader + const username = await screen.findByText(/alice/); + username.click(); + expect(await screen.findByTestId('user-root')).toBeInTheDocument(); + }); }); diff --git a/tests/pages/UserBlogs.test.tsx b/tests/pages/UserBlogs.test.tsx new file mode 100644 index 0000000..98d4e38 --- /dev/null +++ b/tests/pages/UserBlogs.test.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import { store } from '@/state/store'; +import UserBlogs from '@/pages/UserBlogs/UserBlogs'; +import { server } from '../msw/server'; +import { http, HttpResponse } from 'msw'; + +describe('UserBlogs page', () => { + it('renders list of blogs for user', async () => { + server.use( + http.get('/arbitrary/resources/search', ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get('service') === 'BLOG') { + return HttpResponse.json([ + { name: 'alice', identifier: 'q-blog-a', created: 1, metadata: { title: 'A' } }, + { name: 'alice', identifier: 'q-blog-b', created: 2, metadata: { title: 'B' } }, + ]); + } + return HttpResponse.json([]); + }), + ); + + render( + + + + + } /> + + + + , + ); + + expect(await screen.findByText("alice's Blogs")).toBeInTheDocument(); + expect(await screen.findByText('A')).toBeInTheDocument(); + expect(await screen.findByText('B')).toBeInTheDocument(); + }); +}); diff --git a/tests/routes/NameRootRedirect.test.tsx b/tests/routes/NameRootRedirect.test.tsx new file mode 100644 index 0000000..d17eb74 --- /dev/null +++ b/tests/routes/NameRootRedirect.test.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen } from '@testing-library/react'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import NameRootRedirect from '@/pages/UserBlogs/NameRootRedirect'; +import { server } from '../msw/server'; +import { http, HttpResponse } from 'msw'; + +describe('NameRootRedirect', () => { + it('redirects to single blog when only one exists', async () => { + server.use( + http.get('/arbitrary/resources/search', ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get('service') === 'BLOG') { + return HttpResponse.json([ + { name: 'alice', identifier: 'q-blog-alpha', created: 1, metadata: { title: 'Alpha' } }, + ]); + } + return HttpResponse.json([]); + }), + ); + + render( + + + + } /> + Blog Page} /> + + + , + ); + expect(await screen.findByText('Blog Page')).toBeInTheDocument(); + }); + + it('redirects to /:user/blogs when zero or multiple blogs', async () => { + server.use( + http.get('/arbitrary/resources/search', ({ request }) => { + const url = new URL(request.url); + if (url.searchParams.get('service') === 'BLOG') { + return HttpResponse.json([ + { name: 'alice', identifier: 'q-blog-a', created: 1, metadata: { title: 'A' } }, + { name: 'alice', identifier: 'q-blog-b', created: 2, metadata: { title: 'B' } }, + ]); + } + return HttpResponse.json([]); + }), + ); + + render( + + + + } /> + User Blogs} /> + + + , + ); + expect(await screen.findByText('User Blogs')).toBeInTheDocument(); + }); +}); -- 2.43.0 From ba6de53d0554a6d7e4869bac85909ab51ab997b0 Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Thu, 21 Aug 2025 20:08:04 -0400 Subject: [PATCH 32/42] chore(format): prettier write for CI --- docs/RELEASE_NOTES_v0.1.0.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/RELEASE_NOTES_v0.1.0.md b/docs/RELEASE_NOTES_v0.1.0.md index b89aa4b..c05b6a3 100644 --- a/docs/RELEASE_NOTES_v0.1.0.md +++ b/docs/RELEASE_NOTES_v0.1.0.md @@ -5,6 +5,7 @@ Release date: 2025-08-21 This release introduces multi‑blog support per Name with a minimal, backwards‑compatible routing update and improved navigation. Highlights + - User blogs page: `/:user/blogs` lists all blogs for a Name, with owner actions to Edit/Create. - Smart redirect for `/:user`: - Exactly 1 blog → `/:user/:blog` (history replace) @@ -14,6 +15,7 @@ Highlights - Editor flow: You can create and edit posts per blog; switching blogs via the header updates the active blog context. Developer notes + - New files: `src/utils/blogs.ts`, `src/pages/UserBlogs/UserBlogs.tsx`, `src/pages/UserBlogs/NameRootRedirect.tsx`. - Updated routes in `src/App.tsx`. - `GlobalWrapper` now fetches current user blogs and wires selection to set `currentBlog`. @@ -21,4 +23,3 @@ Developer notes - Tests cover redirect behavior, page rendering, and header menu states. No data migrations. Deep links to `/:user/:blog/...` continue to work. - -- 2.43.0 From 8b48a8e31384de864dd5daa46bc578a5a39fc2fe Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Thu, 21 Aug 2025 20:33:38 -0400 Subject: [PATCH 33/42] security: remove tracked .gitea.env; add .gitea.env.example; doc note for local release env --- .gitea.env.example | 11 +++++++++++ docs/CI_GITEA.md | 9 +++++++++ package-lock.json | 30 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 .gitea.env.example diff --git a/.gitea.env.example b/.gitea.env.example new file mode 100644 index 0000000..8bc0d78 --- /dev/null +++ b/.gitea.env.example @@ -0,0 +1,11 @@ +# Copy this file to .gitea.env and fill real values. +# Keep .gitea.env untracked (it is listed in .gitignore). + +GITEA_BASE_URL=https://gitea.qortal.link +# Personal access token with releases:write permission +GITEA_TOKEN= + +# Repository owner and name +OWNER= +REPO= + diff --git a/docs/CI_GITEA.md b/docs/CI_GITEA.md index 4c70625..db9cd33 100644 --- a/docs/CI_GITEA.md +++ b/docs/CI_GITEA.md @@ -40,3 +40,12 @@ When `package-lock.json` exists, CI runs **npm ci** (fast, reproducible). Withou npm run lint:phase0 npm test -- --run ``` + +## Release environment + +For local release scripts (e.g., creating a Gitea release), place secrets in a local `.gitea.env` file at the repo root and source it when running scripts. Do not commit this file. + +- Use `.gitea.env.example` as a template. +- Ensure `.gitea.env` remains ignored via `.gitignore`. + +If a token was ever committed, rotate it in Gitea and consider cleaning it from history. diff --git a/package-lock.json b/package-lock.json index f0586cc..11cddbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10563,6 +10563,21 @@ } } }, + "node_modules/vite-node/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -11225,6 +11240,21 @@ } } }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", -- 2.43.0 From 34b3a55299cc7a223cab09917ab2541858cddadb Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Fri, 22 Aug 2025 01:01:49 -0400 Subject: [PATCH 34/42] docs: v0.2.0 release notes and wiki docs; cleanup dead code; add missing types; ensure tests green --- .../ADR-0006-wiki-mode-multi-editor.md | 26 ++ docs/Q-Blog_1.0.0_ROADMAP_implementation.md | 5 + docs/RELEASE_NOTES_v0.1.1.md | 36 +++ docs/RELEASE_NOTES_v0.2.0.md | 38 +++ docs/TESTING.md | 5 + docs/USER_ANNOUNCEMENT_v0.2.0.md | 19 ++ docs/features/FEATURE_WIKI_MODE_OVERVIEW.md | 37 +++ docs/features/SPEC_WIKI_RESOLVER.md | 36 +++ docs/features/TECH_IMPL_WIKI_MODE.md | 66 +++++ package-lock.json | 44 +--- package.json | 4 +- src/components/modals/EditBlogModal.tsx | 61 ++++- src/components/modals/PublishBlogModal.tsx | 48 +++- src/global.d.ts | 4 + src/hooks/useFetchPosts.tsx | 241 +++++++++++++++--- src/index.d.ts | 5 - .../BlogIndividualPost/BlogIndividualPost.tsx | 88 ++++++- .../BlogIndividualProfile.tsx | 146 +++++++++-- src/pages/BlogList/PostPreview.tsx | 14 - src/pages/UserBlogs/UserBlogs.tsx | 3 + src/state/features/globalSlice.ts | 6 + src/utils/blogs.ts | 4 + src/utils/wiki.ts | 135 ++++++++++ src/utils/wikiSettingsCache.ts | 66 +++++ src/webworkers/decodeBase64.js | 62 ----- src/webworkers/getBlogWorker.js | 2 +- src/wrappers/GlobalWrapper.tsx | 49 +++- .../useFetchPosts.favorites.wiki.test.tsx | 92 +++++++ tests/msw/handlers.ts | 4 + tests/pages/BlogIndividualPost.wiki.test.tsx | 172 +++++++++++++ .../pages/BlogIndividualProfile.wiki.test.tsx | 90 +++++++ tests/utils/wiki.test.ts | 101 ++++++++ tests/utils/wikiSettingsCache.test.ts | 37 +++ 33 files changed, 1553 insertions(+), 193 deletions(-) create mode 100644 docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md create mode 100644 docs/RELEASE_NOTES_v0.1.1.md create mode 100644 docs/RELEASE_NOTES_v0.2.0.md create mode 100644 docs/USER_ANNOUNCEMENT_v0.2.0.md create mode 100644 docs/features/FEATURE_WIKI_MODE_OVERVIEW.md create mode 100644 docs/features/SPEC_WIKI_RESOLVER.md create mode 100644 docs/features/TECH_IMPL_WIKI_MODE.md create mode 100644 src/utils/wiki.ts create mode 100644 src/utils/wikiSettingsCache.ts delete mode 100644 src/webworkers/decodeBase64.js create mode 100644 tests/hooks/useFetchPosts.favorites.wiki.test.tsx create mode 100644 tests/pages/BlogIndividualPost.wiki.test.tsx create mode 100644 tests/pages/BlogIndividualProfile.wiki.test.tsx create mode 100644 tests/utils/wiki.test.ts create mode 100644 tests/utils/wikiSettingsCache.test.ts diff --git a/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md b/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md new file mode 100644 index 0000000..b2c5a60 --- /dev/null +++ b/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md @@ -0,0 +1,26 @@ +# ADR 0006 — Wiki Mode (Multi‑Editor via Visible Revisions) + +Date: 2025-08-22 +Status: Accepted + +## Context + +Clone‑on‑edit bug revealed a path to collaborative editing. We want to formalize this as Wiki mode. + +## Decision + +- Add per‑blog wikiEnabled toggle. +- Add Name‑based whitelist/blacklist with precedence: blacklist > whitelist > global. +- Posts remain immutable; revisions link via originPostId/parentPostId/lineageBlogId. +- Canonical revision chosen client‑side by resolver. + +## Consequences + +- Multiple editors may contribute visible revisions. +- Owner can revoke visibility instantly by changing lists. +- Legacy posts unaffected (wikiEnabled missing = false). + +## Alternatives + +- Server‑side canonical logic (not possible in Qortal). +- Per‑revision ACLs (heavier; unnecessary for v1). diff --git a/docs/Q-Blog_1.0.0_ROADMAP_implementation.md b/docs/Q-Blog_1.0.0_ROADMAP_implementation.md index e4eeb11..689756f 100644 --- a/docs/Q-Blog_1.0.0_ROADMAP_implementation.md +++ b/docs/Q-Blog_1.0.0_ROADMAP_implementation.md @@ -201,6 +201,11 @@ --- +## Milestone Updates + +- v0.1.x — Multiblog foundations and quality baselines delivered. +- v0.2.0 — Wiki Mode canonical selection shipped (owner/whitelist/blacklist; canonical dedupe across Names in feed, favorites, subscriptions, and post view). Settings cache added for efficient owner/settings resolution. + ## Phase 8 — Performance & Resilience **Libraries** diff --git a/docs/RELEASE_NOTES_v0.1.1.md b/docs/RELEASE_NOTES_v0.1.1.md new file mode 100644 index 0000000..d7852f5 --- /dev/null +++ b/docs/RELEASE_NOTES_v0.1.1.md @@ -0,0 +1,36 @@ +# Q-Blog v0.1.1 — Wiki Mode Integration, Canonicalization, and Perf + +Date: 2025-08-22 + +## Highlights + +- Wiki Mode settings in Create + Edit Blog modals (checkbox + whitelist/blacklist). +- Canonical resolver wired into Post, Blog page, Global feed, Subscriptions, and Favorites. +- Attribution on Post page: “Latest by · ”. +- Header “My Blogs” switcher seeds blog context for immediate list refresh. +- Performance: settings read from BLOG metadata when available; per‑page parallel prefetch for duplicate identifiers; singletons skip canonicalization. + +## Details + +- Data model: BLOG JSON and metadata now include `wikiEnabled`, `editorWhitelist`, `editorBlacklist`. +- Resolver logic (`src/utils/wiki.ts`): Owner always allowed; blacklist > whitelist > global allow; newest wins with owner/id tiebreakers. +- Caching (`src/utils/wikiSettingsCache.ts`): Reads from metadata; fetches BLOG JSON only when fields are missing. +- Lists (`src/hooks/useFetchPosts.tsx`): + - Global feed and Subscriptions canonicalize per identifier across Names. + - Favorites canonicalizes per identifier across Names. + - Prefetches settings for blogs with duplicates in parallel; singles are fast‑pathed. + +## Fixes + +- Edit modal now correctly pre-fills the wiki checkbox and lists. +- Blog page refreshes the posts list reliably when switching blogs in the header. + +## Testing + +- New tests for resolver, cache, blog/post pages, and Favorites canonicalization. +- MSW defaults prevent unhandled network calls in tests. + +## Notes + +- Backwards compatible: legacy blogs behave unchanged until wiki is enabled. +- Further perf options: optimistic list render and concurrency caps are available if desired. diff --git a/docs/RELEASE_NOTES_v0.2.0.md b/docs/RELEASE_NOTES_v0.2.0.md new file mode 100644 index 0000000..42e5e7d --- /dev/null +++ b/docs/RELEASE_NOTES_v0.2.0.md @@ -0,0 +1,38 @@ +Q‑Blog v0.2.0 — Wiki Mode Canonical Selection + +Date: 2025-08-22 + +Highlights + +- Wiki Mode canonical selection: When multiple authors publish the same `BLOG_POST` identifier, the app now selects a single canonical revision to display based on blog settings. +- Per‑blog settings cache: Efficiently resolves each blog’s owner and wiki settings (`wikiEnabled`, `editorWhitelist`, `editorBlacklist`). +- Favorites and Subscriptions respect wiki mode: Lists deduplicate by identifier across names and pick the canonical author. +- Individual post view resolves canonical author when wiki is enabled before fetching content JSON. +- UI: Navbar search/filter and persistent Tile/List view toggle. Notifications consolidated in one place. +- Quality: Accessibility tests and stronger utility/unit coverage; minor code cleanup and worker URL fix. + +Canonical selection rules + +- Owner is always allowed (cannot be blocked by blacklist). +- Blacklist disallows others even if whitelisted; whitelist gates non‑owners if non‑empty. +- Among authorized authors: pick latest by `updatedAt`/`qdnUpdated`; owner wins ties; otherwise lowest id as last tiebreaker. + +Implementation notes + +- `utils/wiki.ts` implements `isAuthorized`, `canEdit`, and `selectCanonical`. +- `utils/wikiSettingsCache.ts` caches per‑blog owner/settings, preferring metadata when available. +- `hooks/useFetchPosts.tsx` canonicalizes search results for feed, favorites, and subscriptions. +- `pages/BlogIndividualPost/BlogIndividualPost.tsx` selects the canonical author for the requested post. + +Breaking changes + +- None. Existing identifiers and endpoints are unchanged. + +Fixes & cleanup + +- Removed an unused web worker and fixed a BLOG_POST worker URL. +- Removed redundant local helper where a shared util exists. + +Upgrade notes + +- No migration steps required. Deploy as usual; v0.2.0 is compatible with prior data. diff --git a/docs/TESTING.md b/docs/TESTING.md index ee91f9a..24f3e00 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -34,8 +34,13 @@ Additional tests added: - `tests/utils/fetchPosts.test.ts`: maps BLOG_POST fetch via MSW. - `tests/utils/qortalRequestFunctions.test.ts`: qortalRequest wrappers behavior. - `tests/utils/fetchMail.test.ts`: mail decrypt/mapping flow with mocked qortalRequest. +- `tests/utils/wiki.test.ts`: wiki authorization and canonical resolver. - `tests/pages/BlogList.test.tsx`: renders posts and new-post banner (MUI ThemeProvider). - `tests/pages/BlogIndividualProfile.test.tsx`: loads blog title from API (MSW + ThemeProvider). +- `tests/pages/BlogIndividualPost.wiki.test.tsx`: canonical across Names + edit visibility. +- `tests/pages/BlogIndividualProfile.wiki.test.tsx`: canonical grouping on blog page. +- `tests/utils/wikiSettingsCache.test.ts`: cache fetch and settings mapping. +- `tests/hooks/useFetchPosts.favorites.wiki.test.tsx`: Favorites canonicalization across Names. --- diff --git a/docs/USER_ANNOUNCEMENT_v0.2.0.md b/docs/USER_ANNOUNCEMENT_v0.2.0.md new file mode 100644 index 0000000..be3e697 --- /dev/null +++ b/docs/USER_ANNOUNCEMENT_v0.2.0.md @@ -0,0 +1,19 @@ +Q‑Blog v0.2.0 — Wiki Mode is here + +We’ve shipped a smarter, collaborative blogging experience. When multiple people post updates to the same article, Q‑Blog now shows a single, “canonical” version based on the blog owner’s wiki settings. + +What’s new + +- Canonical posts: If several authors publish a revision of the same post, Q‑Blog picks one to display. The owner’s settings define who can contribute. The most up‑to‑date allowed revision wins; the owner’s revision wins ties. +- Works everywhere: The main feed, Favorites, Subscriptions, and the post page all use canonical selection when wiki mode is enabled. +- Quality of life: Search/filter from the navbar and switch between Tile/List views (your choice is remembered). + +No action needed +Just update to v0.2.0. Your posts and favorites continue to work; collaborative updates will show consistently. + +Tips + +- Owners can enable wiki mode and manage who can edit via blog settings (whitelist/blacklist). +- Use the List view to scan more details at a glance; switch back to Tiles any time. + +Thanks for using Q‑Blog! diff --git a/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md b/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md new file mode 100644 index 0000000..ae8ee13 --- /dev/null +++ b/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md @@ -0,0 +1,37 @@ +# Wiki Mode / Multi‑Editor — Product Overview + +_Generated 2025-08-22_ + +## Summary + +Q‑Blog gains a new **Wiki mode**, allowing multiple **Names** to publish **visible revisions** of posts under a blog. + +- **Names** are the identity key (not account addresses). +- Authorization is computed **client‑side**; Qortal has no server scripting. +- Precedence: **blacklist > whitelist > global allow**. +- Backward‑compatible: missing fields mean wiki is off. + +## UX + +- **Blog Settings:** toggle Wiki Mode; configure whitelist/blacklist of Names. +- **Post page:** shows the canonical (latest visible) revision with author/date attribution. + - Implemented: canonical chosen across Names by exact BLOG_POST identifier; attribution shown when author differs from owner. +- **Edit button:** shown only to Owner and authorized editors. +- **Blog list:** groups posts by lineage and shows canonical revision per group. + - Implemented for Blog page (`/{name}/{blog}`) using identifier-grouping. + - Global feed, Subscriptions, and Favorites canonicalize duplicates via a cached lookup of per‑blog wiki settings. + - Header “My Blogs” switcher updates both title and posts by seeding blog context immediately. + +## Canonical Selection (v0.2.0) + +The app selects one canonical revision when multiple Names publish the same `BLOG_POST` identifier: + +- Authorization first: filter out unauthorized Names using blog settings. The Owner is always allowed (cannot be blocked). +- Freshest wins: compare `updatedAt` or `qdnUpdated`; pick the most recent. +- Tie‑breakers: if timestamps tie, prefer the Owner; otherwise, pick the lowest id for stability. + +This logic is applied consistently in: + +- Main feed pages and “Load new posts”. +- Favorites and Subscriptions lists. +- Individual post view before fetching content. diff --git a/docs/features/SPEC_WIKI_RESOLVER.md b/docs/features/SPEC_WIKI_RESOLVER.md new file mode 100644 index 0000000..149d9b1 --- /dev/null +++ b/docs/features/SPEC_WIKI_RESOLVER.md @@ -0,0 +1,36 @@ +# Wiki Mode — Resolver & Authorization Spec + +_Generated 2025-08-22_ + +## Identity + +- **Name** (string) is the identity key. +- One account may own multiple Names. Names may be transferred; rules apply to current Name string. + +## Authorization Precedence + +1. Owner always allowed. +2. If Name ∈ blacklist → blocked. +3. If whitelist non‑empty → allowed iff Name ∈ whitelist (and not blacklisted). +4. If whitelist empty → allowed (unless blacklisted). + +If Name in both lists → blacklisted. + +## Canonical Selection + +- Group posts by originPostId (or id if missing). +- Candidates: published posts in lineage blog id. +- Filter: authorized authors only. +- Choose newest by updatedAt. Tie: Owner wins; then lowest id. + +## Edit Visibility + +Shown if: (viewer == Owner) or (wikiEnabled && viewer not blacklisted && (whitelist empty or viewer ∈ whitelist)). + +## Backward Compatibility + +- Missing wikiEnabled → false +- Missing lists → empty +- Missing originPostId → id +- Missing lineageBlogId → current blogId +- Missing updatedAt → QDN timestamp diff --git a/docs/features/TECH_IMPL_WIKI_MODE.md b/docs/features/TECH_IMPL_WIKI_MODE.md new file mode 100644 index 0000000..b46ec7b --- /dev/null +++ b/docs/features/TECH_IMPL_WIKI_MODE.md @@ -0,0 +1,66 @@ +# Wiki Mode / Multi‑Editor — Technical Implementation + +_Generated 2025-08-22_ + +## Data Model + +**BlogSettings** + +- `wikiEnabled: boolean` (optional; missing = false) +- `editorWhitelist: Name[]` (optional; empty = global allow) +- `editorBlacklist: Name[]` (optional) + +**Post (revision)** + +- `originPostId: Id` (first ancestor; fallback to id if missing) +- `parentPostId?: Id` +- `lineageBlogId: BlogId` (owner’s blog id; fallback to route blog id) +- `authorName: Name` (registered name) +- `updatedAt: ISO timestamp` (fallback to QDN timestamp if missing) +- `published: boolean` + +## Authorization (by Name) + +1. Owner always allowed. +2. Blacklist blocks regardless. +3. Whitelist non‑empty → only listed Names (minus blacklist). +4. Whitelist empty → all Names allowed (minus blacklist). + +## canEdit(viewerName, settings, ownerName) + +- Returns true if viewerName is Owner, or if wiki enabled and viewerName passes the whitelist/blacklist rules. + +## Canonical Resolver (client) + +1. Resolve origin id. +2. Collect revisions with same origin + blog lineage, published = true. +3. Filter by authorization (authorName vs blog settings). +4. Pick newest by updatedAt; tiebreak: Owner wins; then lowest id. + +Reference implementation in code: + +- `src/utils/wiki.ts` provides `isAuthorized`, `canEdit`, and `selectCanonical` used across UI. +- `src/utils/wikiSettingsCache.ts` caches per‑blog (ownerName, settings) using `/arbitrary/resources?service=BLOG&identifier=...` and `/arbitrary/BLOG//`. +- `src/hooks/useFetchPosts.tsx` groups search results by identifier and applies canonical selection in feed, favorites, and subscriptions. +- `src/pages/BlogIndividualPost/BlogIndividualPost.tsx` resolves canonical author before fetching BLOG_POST JSON when wiki mode is enabled. + +## UI + +- Blog Settings: toggle, Name pickers for whitelist/blacklist. + - Implemented in `Edit Blog` modal (checkbox + comma-separated Name inputs). + - Also available in `Create Blog` modal so new blogs can enable wiki from the start. +- Post page: “Latest by Name on Date” subheader if revision not by Owner. +- Edit: shown only if canEdit true. +- Blog list: use resolver to show canonical per lineage. + - Global feed, Subscriptions, and Favorites use a lightweight cache of per-blog settings to canonicalize duplicates by identifier. + - Header blog switcher seeds blog context to ensure the posts list refreshes immediately on change. + +## Backward Compatibility + +- Missing fields → defaults (wikiEnabled=false, lists empty, origin=id, lineage=blogId, updatedAt=QDN ts). + +## Performance Notes + +- Settings Resolution: reads wiki settings from BLOG resource metadata when available; otherwise fetches BLOG JSON as fallback. +- Prefetch: for each page of results, settings for blogs with duplicate identifiers are prefetched in parallel (singletons skip resolution). +- Canonicalization happens only when necessary; otherwise owner or newest item is used. diff --git a/package-lock.json b/package-lock.json index 11cddbe..a972866 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "q-blog", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "q-blog", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", @@ -45,6 +45,8 @@ "@testing-library/jest-dom": "^6.7.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@types/node": "^24.3.0", "@types/react": "^18.3.23", "@types/react-copy-to-clipboard": "^5.0.4", @@ -7259,13 +7261,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.18", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", + "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { @@ -10563,21 +10565,6 @@ } } }, - "node_modules/vite-node/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -11240,21 +11227,6 @@ } } }, - "node_modules/vitest/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 8a84773..43f7ed4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "q-blog", "private": true, - "version": "0.1.0", + "version": "0.2.0", "type": "module", "scripts": { "dev": "vite", @@ -57,6 +57,8 @@ "@testing-library/jest-dom": "^6.7.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@types/node": "^24.3.0", "@types/react": "^18.3.23", "@types/react-copy-to-clipboard": "^5.0.4", diff --git a/src/components/modals/EditBlogModal.tsx b/src/components/modals/EditBlogModal.tsx index 9223e38..ae90aa5 100644 --- a/src/components/modals/EditBlogModal.tsx +++ b/src/components/modals/EditBlogModal.tsx @@ -31,6 +31,9 @@ interface MyModalProps { description: string, category: string, tags: string[], + wikiEnabled: boolean, + editorWhitelist: string[], + editorBlacklist: string[], ) => Promise; currentBlog: any; } @@ -52,23 +55,37 @@ const MyModal: React.FC = ({ open, onClose, onPublish, currentBlog const [selectedOption, setSelectedOption] = useState(null); const [inputValue, setInputValue] = useState(''); const [chips, setChips] = useState([]); + const [wikiEnabled, setWikiEnabled] = useState(false); + const [whitelist, setWhitelist] = useState(''); + const [blacklist, setBlacklist] = useState(''); const [options, setOptions] = useState([]); React.useEffect(() => { - if (currentBlog) { - setTitle(currentBlog?.title || ''); - setDescription(currentBlog?.description || ''); - const findCategory = options.find((option) => option.id === currentBlog?.category); - if (!findCategory) return; - setSelectedOption(findCategory); - if (!currentBlog?.tags || !Array.isArray(currentBlog.tags)) return; - setChips(currentBlog.tags); - } + if (!currentBlog) return; + setTitle(currentBlog?.title || ''); + setDescription(currentBlog?.description || ''); + // Always set wiki fields regardless of category availability + setWikiEnabled(!!currentBlog?.wikiEnabled); + setWhitelist((currentBlog?.editorWhitelist || []).join(', ')); + setBlacklist((currentBlog?.editorBlacklist || []).join(', ')); + + // Category and tags are optional; handle them if present + const findCategory = options.find((option) => option.id === currentBlog?.category); + if (findCategory) setSelectedOption(findCategory); + if (Array.isArray(currentBlog?.tags)) setChips(currentBlog.tags); }, [currentBlog, options]); const handlePublish = async (): Promise => { try { - await onPublish(title, description, selectedOption?.id || '', chips); + const wl = whitelist + .split(',') + .map((n) => n.trim()) + .filter(Boolean); + const bl = blacklist + .split(',') + .map((n) => n.trim()) + .filter(Boolean); + await onPublish(title, description, selectedOption?.id || '', chips, wikiEnabled, wl, bl); handleClose(); } catch (error: any) { setErrorMessage(error.message); @@ -192,6 +209,30 @@ const MyModal: React.FC = ({ open, onClose, onPublish, currentBlog )} + + + setWikiEnabled(e.target.checked)} + /> + + + setWhitelist(e.target.value)} + fullWidth + /> + setBlacklist(e.target.value)} + fullWidth + /> + + Promise; username: string; } @@ -55,9 +58,29 @@ const MyModal: React.FC = ({ open, onClose, onPublish, username }) const [chips, setChips] = useState([]); const [blogIdentifier, setBlogIdentifier] = useState(username || ''); const [options, setOptions] = useState([]); + const [wikiEnabled, setWikiEnabled] = useState(false); + const [whitelist, setWhitelist] = useState(''); + const [blacklist, setBlacklist] = useState(''); const handlePublish = async (): Promise => { try { - await onPublish(title, description, selectedOption?.id || '', chips, blogIdentifier); + const wl = whitelist + .split(',') + .map((n) => n.trim()) + .filter(Boolean); + const bl = blacklist + .split(',') + .map((n) => n.trim()) + .filter(Boolean); + await onPublish( + title, + description, + selectedOption?.id || '', + chips, + blogIdentifier, + wikiEnabled, + wl, + bl, + ); handleClose(); } catch (error: any) { setErrorMessage(error.message); @@ -222,6 +245,29 @@ const MyModal: React.FC = ({ open, onClose, onPublish, username }) )} + + + setWikiEnabled(e.target.checked)} + /> + + + setWhitelist(e.target.value)} + fullWidth + /> + setBlacklist(e.target.value)} + fullWidth + /> + { const dispatch = useDispatch(); @@ -100,9 +102,65 @@ export const useFetchPosts = () => { willFetchAll = false; fetchAll = responseData.slice(0, findPost); } + // Canonicalize by identifier using per-blog settings cache + const grouped = new Map(); + for (const it of fetchAll) { + const id = it.identifier; + const arr = grouped.get(id) || []; + arr.push(it); + grouped.set(id, arr); + } + const selectedItems: any[] = []; + const entries = Array.from(grouped.entries()); + // Preload settings for blog ids that have duplicate authors + const toPrefetch = Array.from( + new Set( + entries + .filter(([_, arr]) => arr.length > 1) + .map(([id]) => id.split('-post-')[0] as string), + ), + ); + const preloaded = await Promise.all( + toPrefetch.map((blogFull) => + getCachedBlogSettings(blogFull).then((v) => [blogFull, v] as const), + ), + ); + const settingsMap = new Map(preloaded); - const structureData = fetchAll.map((post: any): BlogPost => { - return { + for (const [id, arr] of entries) { + const [blogFull] = id.split('-post-'); + if (arr.length === 1) { + selectedItems.push(arr[0]); + continue; + } + const preload = settingsMap.get(blogFull) || (await getCachedBlogSettings(blogFull)); + const ownerName = (preload as any).ownerName as string; + const settings = (preload as any).settings as BlogSettings; + if (settings?.wikiEnabled) { + const revisions = arr.map((it: any) => ({ + id: it.identifier, + originPostId: it.identifier, + lineageBlogId: blogFull, + authorName: it.name, + updatedAt: it.updated, + qdnUpdated: it.updated, + published: true, + })); + const chosen = selectCanonical(revisions, settings as BlogSettings, ownerName, { + expectedLineageBlogId: blogFull, + }); + const item = chosen + ? arr.find((it: any) => it.name === chosen.authorName) || arr[0] + : arr[0]; + selectedItems.push(item); + } else { + const ownerItem = arr.find((it: any) => it.name === ownerName); + if (ownerItem) selectedItems.push(ownerItem); + else selectedItems.push(arr.sort((a: any, b: any) => b.updated - a.updated)[0]); + } + } + const structureData = selectedItems.map( + (post: any): BlogPost => ({ title: post?.metadata?.title, category: post?.metadata?.category, categoryName: post?.metadata?.categoryName, @@ -113,8 +171,8 @@ export const useFetchPosts = () => { user: post.name, postImage: '', id: post.identifier, - }; - }); + }), + ); if (!willFetchAll) { dispatch(upsertPostsBeginning(structureData)); } @@ -149,8 +207,64 @@ export const useFetchPosts = () => { }, }); const responseData = await response.json(); - const structureData = responseData.map((post: any): BlogPost => { - return { + // Canonicalize by identifier using per-blog settings cache + const grouped = new Map(); + for (const it of responseData) { + const id = it.identifier; + const arr = grouped.get(id) || []; + arr.push(it); + grouped.set(id, arr); + } + const selectedItems: any[] = []; + const entries = Array.from(grouped.entries()); + const toPrefetch = Array.from( + new Set( + entries + .filter(([_, arr]) => arr.length > 1) + .map(([id]) => id.split('-post-')[0] as string), + ), + ); + const preloaded = await Promise.all( + toPrefetch.map((blogFull) => + getCachedBlogSettings(blogFull).then((v) => [blogFull, v] as const), + ), + ); + const settingsMap = new Map(preloaded); + + for (const [id, arr] of entries) { + const [blogFull] = id.split('-post-'); + if (arr.length === 1) { + selectedItems.push(arr[0]); + continue; + } + const preload = settingsMap.get(blogFull) || (await getCachedBlogSettings(blogFull)); + const ownerName = (preload as any).ownerName as string; + const settings = (preload as any).settings as BlogSettings; + if (settings?.wikiEnabled) { + const revisions = arr.map((it: any) => ({ + id: it.identifier, + originPostId: it.identifier, + lineageBlogId: blogFull, + authorName: it.name, + updatedAt: it.updated, + qdnUpdated: it.updated, + published: true, + })); + const chosen = selectCanonical(revisions, settings as BlogSettings, ownerName, { + expectedLineageBlogId: blogFull, + }); + const item = chosen + ? arr.find((it: any) => it.name === chosen.authorName) || arr[0] + : arr[0]; + selectedItems.push(item); + } else { + const ownerItem = arr.find((it: any) => it.name === ownerName); + if (ownerItem) selectedItems.push(ownerItem); + else selectedItems.push(arr.sort((a: any, b: any) => b.updated - a.updated)[0]); + } + } + const structureData = selectedItems.map( + (post: any): BlogPost => ({ title: post?.metadata?.title, category: post?.metadata?.category, categoryName: post?.metadata?.categoryName, @@ -161,8 +275,8 @@ export const useFetchPosts = () => { user: post.name, postImage: '', id: post.identifier, - }; - }); + }), + ); dispatch(upsertPosts(structureData)); for (const content of structureData) { @@ -237,8 +351,66 @@ export const useFetchPosts = () => { }, }); const responseData = await response.json(); - const structureData = responseData.map((post: any): BlogPost => { - return { + // Canonicalize duplicates by identifier using settings cache + const grouped = new Map(); + for (const it of responseData) { + const id = it.identifier; + const arr = grouped.get(id) || []; + arr.push(it); + grouped.set(id, arr); + } + const selectedItems: any[] = []; + const entries = Array.from(grouped.entries()); + const toPrefetch = Array.from( + new Set( + entries + .filter(([_, arr]) => arr.length > 1) + .map(([id]) => id.split('-post-')[0] as string), + ), + ); + const preloaded = await Promise.all( + toPrefetch.map((blogFull) => + getCachedBlogSettings(blogFull).then((v) => [blogFull, v] as const), + ), + ); + const settingsMap = new Map( + preloaded, + ); + + for (const [id, arr] of entries) { + const [blogFull] = id.split('-post-'); + if (arr.length === 1) { + selectedItems.push(arr[0]); + continue; + } + const preload = settingsMap.get(blogFull) || (await getCachedBlogSettings(blogFull)); + const ownerName = (preload as any).ownerName as string; + const settings = (preload as any).settings as BlogSettings; + if (settings?.wikiEnabled) { + const revisions = arr.map((it: any) => ({ + id: it.identifier, + originPostId: it.identifier, + lineageBlogId: blogFull, + authorName: it.name, + updatedAt: it.updated, + qdnUpdated: it.updated, + published: true, + })); + const chosen = selectCanonical(revisions, settings as BlogSettings, ownerName, { + expectedLineageBlogId: blogFull, + }); + const item = chosen + ? arr.find((it: any) => it.name === chosen.authorName) || arr[0] + : arr[0]; + selectedItems.push(item); + } else { + const ownerItem = arr.find((it: any) => it.name === ownerName); + if (ownerItem) selectedItems.push(ownerItem); + else selectedItems.push(arr.sort((a: any, b: any) => b.updated - a.updated)[0]); + } + } + const structureData = selectedItems.map( + (post: any): BlogPost => ({ title: post?.metadata?.title, category: post?.metadata?.category, categoryName: post?.metadata?.categoryName, @@ -248,8 +420,8 @@ export const useFetchPosts = () => { user: post.name, postImage: '', id: post.identifier, - }; - }); + }), + ); dispatch(upsertSubscriptionPosts(structureData)); for (const content of structureData) { @@ -275,22 +447,8 @@ export const useFetchPosts = () => { let favs = []; for (const item of favSlice) { try { - // await qortalRequest({ - // action: "SEARCH_QDN_RESOURCES", - // service: "THUMBNAIL", - // query: "search query goes here", // Optional - searches both "identifier" and "name" fields - // identifier: "search query goes here", // Optional - searches only the "identifier" field - // name: "search query goes here", // Optional - searches only the "name" field - // prefix: false, // Optional - if true, only the beginning of fields are matched in all of the above filters - // default: false, // Optional - if true, only resources without identifiers are returned - // includeStatus: false, // Optional - will take time to respond, so only request if necessary - // includeMetadata: false, // Optional - will take time to respond, so only request if necessary - // limit: 100, - // offset: 0, - // reverse: true - // }); - //TODO - NAME SHOULD BE EXACT - const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${item.id}&exactmatchnames=true&name=${item.user}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`; + // Fetch across Names by identifier and select canonical per wiki settings + const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${item.id}&limit=50&includemetadata=true&reverse=true&excludeblocked=true`; const response = await fetch(url, { method: 'GET', headers: { @@ -298,9 +456,30 @@ export const useFetchPosts = () => { }, }); const data = await response.json(); - // - if (data.length > 0) { - favs.push(data[0]); + if (Array.isArray(data) && data.length > 0) { + const [blogFull] = (item.id as string).split('-post-'); + const { ownerName, settings } = await getCachedBlogSettings(blogFull); + if (settings?.wikiEnabled) { + const revisions = data.map((it: any) => ({ + id: it.identifier, + originPostId: it.identifier, + lineageBlogId: blogFull, + authorName: it.name, + updatedAt: it.updated, + qdnUpdated: it.updated, + published: true, + })); + const chosen = selectCanonical(revisions, settings as BlogSettings, ownerName, { + expectedLineageBlogId: blogFull, + }); + const itemChosen = chosen + ? data.find((it: any) => it.name === chosen.authorName) || data[0] + : data[0]; + favs.push(itemChosen); + } else { + const ownerItem = data.find((it: any) => it.name === ownerName); + favs.push(ownerItem || data.sort((a: any, b: any) => b.updated - a.updated)[0]); + } } } catch (error) {} } diff --git a/src/index.d.ts b/src/index.d.ts index e31bd28..b49b8b8 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -2,8 +2,3 @@ declare module 'webworker:getBlogWorker' { const value: new () => Worker; export default value; } - -declare module 'webworker:decodeBase64' { - const value: new () => Worker; - export default value; -} diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index 0f0831c..bfacad4 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -32,12 +32,14 @@ import { ReusableModal } from '../../components/modals/ReusableModal'; import AudioElement from '../../components/AudioElement'; import ErrorBoundary from '../../components/common/ErrorBoundary'; import { CommentSection } from '../../components/common/Comments/CommentSection'; +import { canEdit, BlogSettings, selectCanonical } from '../../utils/wiki'; import { Tipping } from '../../components/common/Tipping/Tipping'; import FileElement from '../../components/FileElement'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import { setNotification } from '../../state/features/notificationsSlice'; import ContextMenuResource from '../../components/common/ContextMenu/ContextMenuResource'; import { PollWidget } from '../../components/common/PollWidget'; +import { formatDate } from '../../utils/time'; const ResponsiveGridLayout = WidthProvider(Responsive); const initialMinHeight = 2; // Define an initial minimum height for grid items @@ -90,7 +92,7 @@ export const BlogIndividualPost = () => { return addPrefix(blog); }, [blog]); const { user: userState } = useSelector((state: RootState) => state.auth); - const { audios, audioPostId } = useSelector((state: RootState) => state.global); + const { audios, audioPostId, visitingBlog } = useSelector((state: RootState) => state.global); const [avatarUrl, setAvatarUrl] = React.useState(''); const dispatch = useDispatch(); @@ -108,6 +110,8 @@ export const BlogIndividualPost = () => { // saveLayoutsToLocalStorage(layoutss) }; const [blogContent, setBlogContent] = React.useState(null); + const [canonicalAuthor, setCanonicalAuthor] = React.useState(''); + const [canonicalUpdated, setCanonicalUpdated] = React.useState(null); const [isOpenSwitchPlaylistModal, setisOpenSwitchPlaylistModal] = useState(false); const tempSaveAudio = useRef(null); const saveAudio = React.useRef(null); @@ -125,7 +129,58 @@ export const BlogIndividualPost = () => { dispatch(setIsLoadingGlobal(true)); const formBlogId = addPrefix(blog); const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId); - const url = `/arbitrary/BLOG_POST/${user}/${formPostId}`; + // Resolve canonical across names if wiki is enabled + const settings: BlogSettings = { + wikiEnabled: (visitingBlog as any)?.wikiEnabled ?? false, + editorWhitelist: (visitingBlog as any)?.editorWhitelist || [], + editorBlacklist: (visitingBlog as any)?.editorBlacklist || [], + }; + + let contentName = user || ''; + if (settings.wikiEnabled) { + try { + const searchUrl = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${encodeURIComponent( + formPostId, + )}&limit=50&includemetadata=true&reverse=true&excludeblocked=true`; + const searchResp = await fetch(searchUrl, { + headers: { 'Content-Type': 'application/json' }, + }); + const items = (await searchResp.json()) || []; + const revisions = items.map((it: any) => ({ + id: it.identifier, + originPostId: it.identifier, // grouping by id for v1 + lineageBlogId: formBlogId, + authorName: it.name, + updatedAt: it.updated, + qdnUpdated: it.updated, + published: true, + })); + const selected = selectCanonical( + revisions, + settings, + (visitingBlog as any)?.name || user || '', + { + expectedLineageBlogId: formBlogId, + }, + ); + if (selected && selected.authorName) { + contentName = selected.authorName; + setCanonicalAuthor(selected.authorName); + const tsNum = + typeof (selected as any).updatedAt === 'number' + ? (selected as any).updatedAt + : typeof (selected as any).qdnUpdated === 'number' + ? (selected as any).qdnUpdated + : null; + setCanonicalUpdated(tsNum); + } else { + setCanonicalAuthor(user || ''); + setCanonicalUpdated(null); + } + } catch {} + } + + const url = `/arbitrary/BLOG_POST/${contentName}/${formPostId}`; const response = await fetch(url, { method: 'GET', headers: { @@ -168,10 +223,10 @@ export const BlogIndividualPost = () => { } finally { dispatch(setIsLoadingGlobal(false)); } - }, [user, postId, blog]); + }, [user, postId, blog, visitingBlog]); React.useEffect(() => { getBlogPost(); - }, [postId]); + }, [postId, visitingBlog]); const switchPlayList = () => { const filteredAudios = (blogContent?.postContent || []).filter( @@ -293,7 +348,16 @@ export const BlogIndividualPost = () => { paddingBottom: '50px', }} > - {user === userState?.name && ( + {(() => { + const viewer = userState?.name; + const owner = (visitingBlog as any)?.name || user || ''; + const settings: BlogSettings = { + wikiEnabled: (visitingBlog as any)?.wikiEnabled ?? false, + editorWhitelist: (visitingBlog as any)?.editorWhitelist || [], + editorBlacklist: (visitingBlog as any)?.editorBlacklist || [], + }; + return canEdit(viewer, settings, owner); + })() && ( @@ -100,21 +100,11 @@ export const UserBlogs: React.FC = () => { - {isOwner && ( - )} -- 2.43.0 From cd79cdf6adcac0f65be24b7145b4f50673dd48ed Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Fri, 22 Aug 2025 01:37:50 -0400 Subject: [PATCH 37/42] test: add wiki editor route canonical-load test and feed edit-pencil visibility test; run prettier --- src/pages/EditPost/EditPost.tsx | 4 +- src/pages/UserBlogs/UserBlogs.tsx | 23 +++++++- tests/pages/BlogList.wiki-edit.test.tsx | 67 +++++++++++++++++++++ tests/pages/EditPost.wiki.test.tsx | 77 +++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 tests/pages/BlogList.wiki-edit.test.tsx create mode 100644 tests/pages/EditPost.wiki.test.tsx diff --git a/src/pages/EditPost/EditPost.tsx b/src/pages/EditPost/EditPost.tsx index 512329c..e8588d6 100644 --- a/src/pages/EditPost/EditPost.tsx +++ b/src/pages/EditPost/EditPost.tsx @@ -210,7 +210,9 @@ export const EditPost = () => { const searchUrl = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${encodeURIComponent( fullId, )}&limit=50&includemetadata=true&reverse=true&excludeblocked=true`; - const searchResp = await fetch(searchUrl, { headers: { 'Content-Type': 'application/json' } }); + const searchResp = await fetch(searchUrl, { + headers: { 'Content-Type': 'application/json' }, + }); const items = (await searchResp.json()) || []; const revisions = items.map((it: any) => ({ id: it.identifier, diff --git a/src/pages/UserBlogs/UserBlogs.tsx b/src/pages/UserBlogs/UserBlogs.tsx index 9ce3e42..4150001 100644 --- a/src/pages/UserBlogs/UserBlogs.tsx +++ b/src/pages/UserBlogs/UserBlogs.tsx @@ -89,7 +89,12 @@ export const UserBlogs: React.FC = () => { <> {isOwner && ( - @@ -100,11 +105,23 @@ export const UserBlogs: React.FC = () => { - {isOwner && ( - )} diff --git a/tests/pages/BlogList.wiki-edit.test.tsx b/tests/pages/BlogList.wiki-edit.test.tsx new file mode 100644 index 0000000..3a70899 --- /dev/null +++ b/tests/pages/BlogList.wiki-edit.test.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter } from 'react-router-dom'; +import { render, waitFor } from '@testing-library/react'; +import { http, HttpResponse } from 'msw'; +import { server } from '../msw/server'; +import { BlogList } from '@/pages/BlogList/BlogList'; +import { addPosts } from '@/state/features/blogSlice'; +import { addUser } from '@/state/features/authSlice'; + +describe('BlogList — wiki edit pencil visibility', () => { + it('shows edit pencil for authorized non-owner editor', async () => { + // Configure auth user as bob + store.dispatch(addUser({ address: 'A', publicKey: 'P', name: 'bob' })); + // Seed one post from another user + store.dispatch( + addPosts([ + { + id: 'q-blog-myblog-post-123', + user: 'carol', + title: 'T', + description: 'd', + createdAt: 1, + } as any, + ]), + ); + + // Owner + settings (whitelist bob) + server.use( + http.get('/arbitrary/resources', ({ request }) => { + const url = new URL(request.url); + if ( + url.searchParams.get('service') === 'BLOG' && + url.searchParams.get('identifier') === 'q-blog-myblog' + ) { + return HttpResponse.json([{ name: 'alice', identifier: 'q-blog-myblog' }]); + } + return HttpResponse.json([]); + }), + http.get('/arbitrary/BLOG/:name/:id', ({ params }) => { + const { name, id } = params as any; + if (name === 'alice' && id === 'q-blog-myblog') { + return HttpResponse.json({ wikiEnabled: true, editorWhitelist: ['bob'] }); + } + return HttpResponse.json({}, { status: 404 }); + }), + ); + + const { container } = render( + + + + + + + , + ); + + await waitFor(() => { + const pencil = container.querySelector('.edit-btn'); + expect(pencil).toBeTruthy(); + }); + }); +}); diff --git a/tests/pages/EditPost.wiki.test.tsx b/tests/pages/EditPost.wiki.test.tsx new file mode 100644 index 0000000..439b495 --- /dev/null +++ b/tests/pages/EditPost.wiki.test.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen, waitFor } from '@testing-library/react'; +import { http, HttpResponse } from 'msw'; +import { server } from '../msw/server'; +import { EditPost } from '@/pages/EditPost/EditPost'; + +describe('EditPost — wiki canonical loading', () => { + it('loads canonical content by identifier across names', async () => { + // BLOG owner + settings + server.use( + http.get('/arbitrary/resources', ({ request }) => { + const url = new URL(request.url); + if ( + url.searchParams.get('service') === 'BLOG' && + url.searchParams.get('identifier') === 'q-blog-myblog' + ) { + return HttpResponse.json([{ name: 'alice', identifier: 'q-blog-myblog' }]); + } + return HttpResponse.json([]); + }), + http.get('/arbitrary/BLOG/:name/:id', ({ params }) => { + const { name, id } = params as any; + if (name === 'alice' && id === 'q-blog-myblog') { + return HttpResponse.json({ wikiEnabled: true }); + } + return HttpResponse.json({}, { status: 404 }); + }), + // Search across names by full identifier + http.get('/arbitrary/resources/search', ({ request }) => { + const url = new URL(request.url); + if ( + url.searchParams.get('service') === 'BLOG_POST' && + url.searchParams.get('identifier') === 'q-blog-myblog-post-123' + ) { + return HttpResponse.json([ + { name: 'alice', identifier: 'q-blog-myblog-post-123', updated: 1000 }, + { name: 'carol', identifier: 'q-blog-myblog-post-123', updated: 2000 }, + ]); + } + return HttpResponse.json([]); + }), + // Canonical chosen should be carol (newer) + http.get('/arbitrary/BLOG_POST/:name/:fullId', ({ params }) => { + const { name, fullId } = params as any; + if (name === 'carol' && fullId === 'q-blog-myblog-post-123') { + return HttpResponse.json({ + title: 'Newer Title', + createdAt: 1, + postContent: [{ type: 'editor', version: 1, id: 'e1', content: [{ text: 'Hello' }] }], + }); + } + return HttpResponse.json({}, { status: 404 }); + }), + ); + + render( + + + + + } /> + + + + , + ); + + // Title input should pick canonical content's title + const titleInput = await screen.findByDisplayValue('Newer Title'); + expect(titleInput).toBeInTheDocument(); + }); +}); -- 2.43.0 From 2dc843a857b3c8f8527b8edebc4c621e4be5f88a Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Fri, 22 Aug 2025 01:46:51 -0400 Subject: [PATCH 38/42] docs: v0.2.1 notes and announcement; update release docs to require Prettier before push; bump version to 0.2.1 --- docs/RELEASE_FLOW.md | 11 ++++++++++- docs/RELEASE_NOTES_v0.2.1.md | 18 ++++++++++++++++++ docs/RELEASING.md | 1 + docs/USER_ANNOUNCEMENT_v0.2.1.md | 14 ++++++++++++++ package.json | 2 +- 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 docs/RELEASE_NOTES_v0.2.1.md create mode 100644 docs/USER_ANNOUNCEMENT_v0.2.1.md diff --git a/docs/RELEASE_FLOW.md b/docs/RELEASE_FLOW.md index 5360669..92396bd 100644 --- a/docs/RELEASE_FLOW.md +++ b/docs/RELEASE_FLOW.md @@ -28,7 +28,16 @@ bash scripts/release/build-archive.sh # bash scripts/release/build-archive.sh --with-build ``` -3. Create/Update Gitea release and upload zips for the current version only: +3. Before pushing: format and checks + +```bash +npm run format:fix +npm run typecheck +npm run lint:full +npm run test:run +``` + +4. Create/Update Gitea release and upload zips for the current version only: ```bash bash scripts/release/create-gitea-release.sh 0.0.1 \ diff --git a/docs/RELEASE_NOTES_v0.2.1.md b/docs/RELEASE_NOTES_v0.2.1.md new file mode 100644 index 0000000..742317c --- /dev/null +++ b/docs/RELEASE_NOTES_v0.2.1.md @@ -0,0 +1,18 @@ +Q‑Blog v0.2.1 — Wiki Editing UX Fixes + +Date: 2025-08-22 + +Fixes + +- Edit pencil in feed: Shows for authorized wiki editors (not only the original author). Uses blog wiki settings and `canEdit()` to compute visibility. +- Editor loading under wiki mode: The Edit page now builds the full BLOG_POST identifier (`q-blog--post-`), resolves the canonical author if wiki is enabled, and loads their content. Publishing uses the full identifier so editors without their own blog can contribute when allowed. +- Button contrast: “Create Blog” and blog list action buttons (View, Edit) now use themed variants with better contrast in dark/light modes. + +Quality + +- Tests added for edit-route canonical loading and feed pencil visibility under wiki permissions. +- Prettier formatting step documented before pushes to keep CI green. + +Upgrade notes + +- No migration steps. This is a behavioral and UX fix release on top of v0.2.0. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 4fc2c05..c1b151f 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -6,6 +6,7 @@ This summarizes the practical steps we use today to publish a release via Gitea - Ensure CI is green on the branch to release. - Verify locally: + - Format first: `npm run format:fix` - `npm run typecheck && npm run lint:full && npm run test:run` - Optional production build: `npm run build` diff --git a/docs/USER_ANNOUNCEMENT_v0.2.1.md b/docs/USER_ANNOUNCEMENT_v0.2.1.md new file mode 100644 index 0000000..6ded685 --- /dev/null +++ b/docs/USER_ANNOUNCEMENT_v0.2.1.md @@ -0,0 +1,14 @@ +Q‑Blog v0.2.1 — Smoother Wiki Editing + +We’ve polished the wiki experience based on feedback from v0.2.0. + +What’s improved + +- Edit access in the feed: If a blog enables wiki mode and you’re allowed to edit, the edit pencil now appears for you (even if you aren’t the original author). +- Reliable editing: The Edit page correctly loads the latest canonical content and publishes updates under the proper identifier so contributions “just work”. +- Better readability: “Create Blog” and blog list action buttons are clearer in both dark and light themes. + +No action needed +Update to v0.2.1 — your workflows and posts remain the same, just smoother. + +Thanks for the quick feedback and keep it coming! diff --git a/package.json b/package.json index 43f7ed4..3682122 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "q-blog", "private": true, - "version": "0.2.0", + "version": "0.2.1", "type": "module", "scripts": { "dev": "vite", -- 2.43.0 From 0b100af6869d25d39857408e6b6d20cdddad36cf Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Fri, 22 Aug 2025 07:28:42 -0400 Subject: [PATCH 39/42] Release v0.2.2 --- .gitignore | 2 +- docs/A11Y_MANUAL_CHECKLIST.md | 6 +- docs/AUDIT_REPORT_v0.0.1.md | 4 +- docs/CHANGELOG_addendum_v0.2.2.md | 6 + .../ADR-0006-wiki-mode-multi-editor.md | 14 +- .../DECISIONS/ADR-0007-wiki-edit-auth-flow.md | 19 + docs/DEVELOPER_GUIDE_MULTIBLOG.md | 2 +- docs/GLOSSARY_DOMAIN.md | 10 +- docs/PHASE0_STATUS.md | 6 +- docs/Q-Blog_Project_Instructions.md | 4 +- docs/RELEASE_FLOW.md | 6 +- docs/RELEASE_NOTES_multiblog.md | 2 +- docs/RELEASE_NOTES_v0.0.1.md | 8 +- docs/RELEASE_NOTES_v0.1.0.md | 4 +- docs/RELEASE_NOTES_v0.1.1.md | 4 +- docs/RELEASE_NOTES_v0.2.0.md | 8 +- docs/RELEASE_NOTES_v0.2.1.md | 2 +- docs/RELEASE_NOTES_v0.2.2.md | 41 + docs/RELEASING.md | 6 +- docs/RISKS_ASSUMPTIONS.md | 6 +- docs/ROADMAP_DEPENDENCIES.md | 8 +- docs/SECURITY_PRIVACY_POSTURE.md | 12 +- docs/TESTING.md | 2 +- docs/USER_ANNOUNCEMENT_v0.1.0.md | 4 +- docs/USER_ANNOUNCEMENT_v0.2.0.md | 12 +- docs/USER_ANNOUNCEMENT_v0.2.1.md | 2 +- docs/USER_JOURNEYS.md | 6 +- docs/VISION_PRFAQ.md | 20 +- docs/features/FEATURE_MULTIBLOG_OVERVIEW.md | 4 + docs/features/FEATURE_WIKI_MODE_OVERVIEW.md | 14 +- docs/features/SPEC_WIKI_RESOLVER.md | 2 +- docs/features/TECH_IMPL_MULTIBLOG.md | 2 +- docs/features/TECH_IMPL_WIKI_MODE.md | 12 +- package-lock.json | 34 +- package.json | 2 +- scripts/dev/patch-gitignore-phase1.sh | 2 +- src/components/layout/Navbar/Navbar.tsx | 281 +++--- .../BlogIndividualPost/BlogIndividualPost.tsx | 7 +- src/pages/BlogList/BlogList.tsx | 22 +- src/pages/BlogList/PostPreview.tsx | 4 + src/pages/CreatePost/CreatePost.tsx | 214 +++-- src/pages/CreatePost/CreatePostBuilder.tsx | 51 +- src/pages/CreatePost/CreatePostMinimal.tsx | 46 +- src/pages/UserBlogs/UserBlogs.tsx | 2 +- src/wrappers/GlobalWrapper.tsx | 809 +++--------------- .../features/auth/AuthenticateButton.test.tsx | 43 + tests/features/names/SwitchNameMenu.test.tsx | 40 + .../pages/CreatePost.wiki.editNoBlog.test.tsx | 115 +++ 48 files changed, 855 insertions(+), 1077 deletions(-) create mode 100644 docs/CHANGELOG_addendum_v0.2.2.md create mode 100644 docs/DECISIONS/ADR-0007-wiki-edit-auth-flow.md create mode 100644 docs/RELEASE_NOTES_v0.2.2.md create mode 100644 tests/features/auth/AuthenticateButton.test.tsx create mode 100644 tests/features/names/SwitchNameMenu.test.tsx create mode 100644 tests/pages/CreatePost.wiki.editNoBlog.test.tsx diff --git a/.gitignore b/.gitignore index 82661c2..d862aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# --- Q‑Blog canonical ignore (Phase 1) --- +# --- Q-Blog canonical ignore (Phase 1) --- # Logs logs *.log diff --git a/docs/A11Y_MANUAL_CHECKLIST.md b/docs/A11Y_MANUAL_CHECKLIST.md index df36635..e944c6b 100644 --- a/docs/A11Y_MANUAL_CHECKLIST.md +++ b/docs/A11Y_MANUAL_CHECKLIST.md @@ -1,10 +1,10 @@ -# Q‑Blog — Accessibility Manual Test Checklist +# Q-Blog — Accessibility Manual Test Checklist _Generated 2025-08-16 23:27Z_ ## Global -- Tab through top‑level navigation, header, main, footer; **skip link** focuses main. +- Tab through top-level navigation, header, main, footer; **skip link** focuses main. - Focus is always visible; Escape closes modals/popovers and returns focus to invoker. ## Read Post @@ -20,7 +20,7 @@ _Generated 2025-08-16 23:27Z_ ## Manage Blogs -- Switcher is keyboard‑reachable; selection updates page content and title. +- Switcher is keyboard-reachable; selection updates page content and title. ## Collaboration diff --git a/docs/AUDIT_REPORT_v0.0.1.md b/docs/AUDIT_REPORT_v0.0.1.md index 29d963a..8321cf6 100644 --- a/docs/AUDIT_REPORT_v0.0.1.md +++ b/docs/AUDIT_REPORT_v0.0.1.md @@ -1,4 +1,4 @@ -# Q‑Blog v0.0.1 — Source Audit +# Q-Blog v0.0.1 — Source Audit _Generated: 2025-08-21T19:40:03_ @@ -442,7 +442,7 @@ echo " - ${SRC_ZIP}" 1. Content sanitization (DOMPurify) not detected in source scan. Verify sanitize-on-save and sanitize-on-render are implemented. 2. Zod validation not detected; confirm validation at form and API boundaries or update docs. 3. Testing stack may be incomplete (MSW/jest-axe). Ensure test coverage for autosave, a11y smoke, and error paths. -4. RTK Query not detected; if used, ensure endpoints are defined with blog‑scoped cache keys. +4. RTK Query not detected; if used, ensure endpoints are defined with blog-scoped cache keys. ## Next Steps (thin vertical slice) diff --git a/docs/CHANGELOG_addendum_v0.2.2.md b/docs/CHANGELOG_addendum_v0.2.2.md new file mode 100644 index 0000000..7377cbe --- /dev/null +++ b/docs/CHANGELOG_addendum_v0.2.2.md @@ -0,0 +1,6 @@ +## v0.2.2 — 2025-08-22 + +- Wiki edit flow: no blog required for editors; publish BLOG_POST only +- Auth: auto-run on mount; button shows loading; deduped dispatch +- Names: loading/error/empty states in popover +- Tests: coverage for edit flow, auth UI, names UI diff --git a/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md b/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md index b2c5a60..f0bdfde 100644 --- a/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md +++ b/docs/DECISIONS/ADR-0006-wiki-mode-multi-editor.md @@ -1,18 +1,18 @@ -# ADR 0006 — Wiki Mode (Multi‑Editor via Visible Revisions) +# ADR 0006 — Wiki Mode (Multi-Editor via Visible Revisions) Date: 2025-08-22 Status: Accepted ## Context -Clone‑on‑edit bug revealed a path to collaborative editing. We want to formalize this as Wiki mode. +Clone-on-edit bug revealed a path to collaborative editing. We want to formalize this as Wiki mode. ## Decision -- Add per‑blog wikiEnabled toggle. -- Add Name‑based whitelist/blacklist with precedence: blacklist > whitelist > global. +- Add per-blog wikiEnabled toggle. +- Add Name-based whitelist/blacklist with precedence: blacklist > whitelist > global. - Posts remain immutable; revisions link via originPostId/parentPostId/lineageBlogId. -- Canonical revision chosen client‑side by resolver. +- Canonical revision chosen client-side by resolver. ## Consequences @@ -22,5 +22,5 @@ Clone‑on‑edit bug revealed a path to collaborative editing. We want to forma ## Alternatives -- Server‑side canonical logic (not possible in Qortal). -- Per‑revision ACLs (heavier; unnecessary for v1). +- Server-side canonical logic (not possible in Qortal). +- Per-revision ACLs (heavier; unnecessary for v1). diff --git a/docs/DECISIONS/ADR-0007-wiki-edit-auth-flow.md b/docs/DECISIONS/ADR-0007-wiki-edit-auth-flow.md new file mode 100644 index 0000000..efcc0ae --- /dev/null +++ b/docs/DECISIONS/ADR-0007-wiki-edit-auth-flow.md @@ -0,0 +1,19 @@ +# ADR-0007 — Wiki Edit Flow & Auth Adjustments (v0.2.2) + +**Status:** Accepted +**Date:** 2025-08-22 + +## Context + +Users could not edit others’ posts on wiki-enabled blogs unless they owned a blog. Auth feedback was unclear; initial auth no longer triggered on first load. + +## Decision + +1. **Edit flow:** In edit mode, do not require `currentBlog`. Publish only `service: BLOG_POST` under the editor’s Name using the original identifier. Never publish `service: BLOG` from edit screens. +2. **Auth:** Trigger `askForAccountInformation()` on initial load (idempotent). Show loading states for Authenticate button and names popover. + +## Consequences + +- Allows true wiki behavior; avoids accidental “create a blog first” blocker. +- Clear, accessible feedback during auth; fewer duplicate requests. +- Tests added to prevent regressions. diff --git a/docs/DEVELOPER_GUIDE_MULTIBLOG.md b/docs/DEVELOPER_GUIDE_MULTIBLOG.md index a3c1760..a2376b5 100644 --- a/docs/DEVELOPER_GUIDE_MULTIBLOG.md +++ b/docs/DEVELOPER_GUIDE_MULTIBLOG.md @@ -2,7 +2,7 @@ _Generated 2025-08-21_ -**Start here** to implement the multi‑blog feature. This guide links to the overview, technical plan, and ADR; then gives a fast manual test checklist. +**Start here** to implement the multi-blog feature. This guide links to the overview, technical plan, and ADR; then gives a fast manual test checklist. ## Read these first diff --git a/docs/GLOSSARY_DOMAIN.md b/docs/GLOSSARY_DOMAIN.md index ee837d3..2ec0106 100644 --- a/docs/GLOSSARY_DOMAIN.md +++ b/docs/GLOSSARY_DOMAIN.md @@ -1,23 +1,23 @@ -# Q‑Blog — Glossary & Domain Canon +# Q-Blog — Glossary & Domain Canon _Generated 2025-08-16 23:27Z_ **Name** — The account identity under which blogs are created. **Blog** — A container for posts; owned by a Name; has **handle**, **title**, **visibility**. -**Handle** — Human‑friendly unique identifier per Name (slug rules). +**Handle** — Human-friendly unique identifier per Name (slug rules). **Post** — Content item; belongs to exactly one Blog (immutable link). **Role** — `owner | editor | author`; defines allowed operations. **Membership** — Name ↔ Blog relationship with role. **Revision** — Monotonically increasing number used for concurrency control. -**Invite** — Time‑limited token that assigns a role on acceptance. +**Invite** — Time-limited token that assigns a role on acceptance. ### Invariants - A Post’s `blogId` does not change after creation. - (`nameId`, `blogHandle`) is unique. -- All write operations require a role check (server‑enforced). +- All write operations require a role check (server-enforced). ### Identifier & URL Guidance - Canonical blog URL: `/{nameHandle}/{blogHandle}/…` (conceptual). -- Slugs are normalized to lowercase, ASCII, hyphen‑separated; collisions rejected with a helpful message. +- Slugs are normalized to lowercase, ASCII, hyphen-separated; collisions rejected with a helpful message. diff --git a/docs/PHASE0_STATUS.md b/docs/PHASE0_STATUS.md index 1df567f..7213362 100644 --- a/docs/PHASE0_STATUS.md +++ b/docs/PHASE0_STATUS.md @@ -5,9 +5,9 @@ ## What we delivered - **Tracker hygiene:** canonical labels; duplicate cleanup helpers; milestone created; seed issues for Phase 1–3. -- **CI (self‑hosted):** single workflow (`.gitea/workflows/ci.yml`) using only built‑in steps; verified green. +- **CI (self-hosted):** single workflow (`.gitea/workflows/ci.yml`) using only built-in steps; verified green. - **Dev harness:** Vitest + @testing-library/react + jest-axe; a11y smoke `tests/axe-smoke.test.tsx` passing. -- **Lint posture:** ESLint flat config + phase‑scoped lint (Phase 0 ignores `src/**`); scripts to narrow/expand scope. +- **Lint posture:** ESLint flat config + phase-scoped lint (Phase 0 ignores `src/**`); scripts to narrow/expand scope. - **Docs & instructions:** project instructions, CONTRIBUTING, RELEASING, CI runner notes, Phase plans. - **Utility scripts:** tracker bootstrap/verify; label dedupe; PR helpers. @@ -22,7 +22,7 @@ - Remove remaining `@ts-nocheck` (scripted) and start type hygiene sweeps. - Fix MUI v4 imports, a11y `` gaps, and hooks-in-non-components issues. -- Begin file‑scoped lint expansion behind small PRs. +- Begin file-scoped lint expansion behind small PRs. --- diff --git a/docs/Q-Blog_Project_Instructions.md b/docs/Q-Blog_Project_Instructions.md index a4911f0..6425603 100644 --- a/docs/Q-Blog_Project_Instructions.md +++ b/docs/Q-Blog_Project_Instructions.md @@ -2,7 +2,7 @@ _Generated 2025-08-16 23:37Z_ -> Default instructions for all Q-Blog chats. Optimize for **correctness, accessibility, collaboration, and a focused writing UX**. Keep answers decisive and artifact‑oriented. +> Default instructions for all Q-Blog chats. Optimize for **correctness, accessibility, collaboration, and a focused writing UX**. Keep answers decisive and artifact-oriented. --- @@ -182,7 +182,7 @@ Centralize shared constants and import everywhere: roles, statuses, limits (uplo ## 15) Versioning & releases -SemVer with human changelogs; each release includes notes, migration steps, and quick verify. CI gates: typecheck, lint (jsx‑a11y), unit/component tests, axe smoke, coverage, build. +SemVer with human changelogs; each release includes notes, migration steps, and quick verify. CI gates: typecheck, lint (jsx-a11y), unit/component tests, axe smoke, coverage, build. --- diff --git a/docs/RELEASE_FLOW.md b/docs/RELEASE_FLOW.md index 92396bd..786346e 100644 --- a/docs/RELEASE_FLOW.md +++ b/docs/RELEASE_FLOW.md @@ -1,8 +1,8 @@ # Release Flow (Gitea + local scripts) -This repo uses local scripts to cut releases and create uploads in Gitea. CI stays marketplace‑free and works with your self‑hosted runner. +This repo uses local scripts to cut releases and create uploads in Gitea. CI stays marketplace-free and works with your self-hosted runner. -## One‑time +## One-time - Ensure env vars are exported in your shell (or place them in `.gitea.env` and `source` it): ```bash @@ -61,5 +61,5 @@ The script will: ### Tips -- To override which files are uploaded (e.g., re‑upload a specific dist), pass `--assets 'release/*-v0.2.0-dist.zip'`. +- To override which files are uploaded (e.g., re-upload a specific dist), pass `--assets 'release/*-v0.2.0-dist.zip'`. - If you have older zips in `release/`, the default upload pattern avoids attaching them. diff --git a/docs/RELEASE_NOTES_multiblog.md b/docs/RELEASE_NOTES_multiblog.md index 6b92087..fdd114e 100644 --- a/docs/RELEASE_NOTES_multiblog.md +++ b/docs/RELEASE_NOTES_multiblog.md @@ -30,5 +30,5 @@ Tests Notes -- Redirect target within Q‑Blog is `/:user/:blog` (this app’s profile/posts page), not `…/posts`. +- Redirect target within Q-Blog is `/:user/:blog` (this app’s profile/posts page), not `…/posts`. - No data migrations; all changes are UI/routing. diff --git a/docs/RELEASE_NOTES_v0.0.1.md b/docs/RELEASE_NOTES_v0.0.1.md index 1147528..78989df 100644 --- a/docs/RELEASE_NOTES_v0.0.1.md +++ b/docs/RELEASE_NOTES_v0.0.1.md @@ -1,10 +1,10 @@ -# Q‑Blog v0.0.1 — Phase 0 wrap +# Q-Blog v0.0.1 — Phase 0 wrap **Highlights** -- Self‑hosted CI workflow (no marketplace actions); verified green. -- Test harness online (Vitest + RTL + jest‑axe) with a11y smoke test. -- ESLint flat config with Phase‑scoped lint; IDE noise reduced. +- Self-hosted CI workflow (no marketplace actions); verified green. +- Test harness online (Vitest + RTL + jest-axe) with a11y smoke test. +- ESLint flat config with Phase-scoped lint; IDE noise reduced. - Tracker primed: labels, milestone, seed issues, maintenance scripts. - Contributor docs and PR templates in place. diff --git a/docs/RELEASE_NOTES_v0.1.0.md b/docs/RELEASE_NOTES_v0.1.0.md index c05b6a3..d53d003 100644 --- a/docs/RELEASE_NOTES_v0.1.0.md +++ b/docs/RELEASE_NOTES_v0.1.0.md @@ -1,8 +1,8 @@ -# Q‑Blog v0.1.0 — Multiple Blogs per Name +# Q-Blog v0.1.0 — Multiple Blogs per Name Release date: 2025-08-21 -This release introduces multi‑blog support per Name with a minimal, backwards‑compatible routing update and improved navigation. +This release introduces multi-blog support per Name with a minimal, backwards-compatible routing update and improved navigation. Highlights diff --git a/docs/RELEASE_NOTES_v0.1.1.md b/docs/RELEASE_NOTES_v0.1.1.md index d7852f5..c024700 100644 --- a/docs/RELEASE_NOTES_v0.1.1.md +++ b/docs/RELEASE_NOTES_v0.1.1.md @@ -8,7 +8,7 @@ Date: 2025-08-22 - Canonical resolver wired into Post, Blog page, Global feed, Subscriptions, and Favorites. - Attribution on Post page: “Latest by · ”. - Header “My Blogs” switcher seeds blog context for immediate list refresh. -- Performance: settings read from BLOG metadata when available; per‑page parallel prefetch for duplicate identifiers; singletons skip canonicalization. +- Performance: settings read from BLOG metadata when available; per-page parallel prefetch for duplicate identifiers; singletons skip canonicalization. ## Details @@ -18,7 +18,7 @@ Date: 2025-08-22 - Lists (`src/hooks/useFetchPosts.tsx`): - Global feed and Subscriptions canonicalize per identifier across Names. - Favorites canonicalizes per identifier across Names. - - Prefetches settings for blogs with duplicates in parallel; singles are fast‑pathed. + - Prefetches settings for blogs with duplicates in parallel; singles are fast-pathed. ## Fixes diff --git a/docs/RELEASE_NOTES_v0.2.0.md b/docs/RELEASE_NOTES_v0.2.0.md index 42e5e7d..c91c883 100644 --- a/docs/RELEASE_NOTES_v0.2.0.md +++ b/docs/RELEASE_NOTES_v0.2.0.md @@ -1,11 +1,11 @@ -Q‑Blog v0.2.0 — Wiki Mode Canonical Selection +Q-Blog v0.2.0 — Wiki Mode Canonical Selection Date: 2025-08-22 Highlights - Wiki Mode canonical selection: When multiple authors publish the same `BLOG_POST` identifier, the app now selects a single canonical revision to display based on blog settings. -- Per‑blog settings cache: Efficiently resolves each blog’s owner and wiki settings (`wikiEnabled`, `editorWhitelist`, `editorBlacklist`). +- Per-blog settings cache: Efficiently resolves each blog’s owner and wiki settings (`wikiEnabled`, `editorWhitelist`, `editorBlacklist`). - Favorites and Subscriptions respect wiki mode: Lists deduplicate by identifier across names and pick the canonical author. - Individual post view resolves canonical author when wiki is enabled before fetching content JSON. - UI: Navbar search/filter and persistent Tile/List view toggle. Notifications consolidated in one place. @@ -14,13 +14,13 @@ Highlights Canonical selection rules - Owner is always allowed (cannot be blocked by blacklist). -- Blacklist disallows others even if whitelisted; whitelist gates non‑owners if non‑empty. +- Blacklist disallows others even if whitelisted; whitelist gates non-owners if non-empty. - Among authorized authors: pick latest by `updatedAt`/`qdnUpdated`; owner wins ties; otherwise lowest id as last tiebreaker. Implementation notes - `utils/wiki.ts` implements `isAuthorized`, `canEdit`, and `selectCanonical`. -- `utils/wikiSettingsCache.ts` caches per‑blog owner/settings, preferring metadata when available. +- `utils/wikiSettingsCache.ts` caches per-blog owner/settings, preferring metadata when available. - `hooks/useFetchPosts.tsx` canonicalizes search results for feed, favorites, and subscriptions. - `pages/BlogIndividualPost/BlogIndividualPost.tsx` selects the canonical author for the requested post. diff --git a/docs/RELEASE_NOTES_v0.2.1.md b/docs/RELEASE_NOTES_v0.2.1.md index 742317c..6b01cfb 100644 --- a/docs/RELEASE_NOTES_v0.2.1.md +++ b/docs/RELEASE_NOTES_v0.2.1.md @@ -1,4 +1,4 @@ -Q‑Blog v0.2.1 — Wiki Editing UX Fixes +Q-Blog v0.2.1 — Wiki Editing UX Fixes Date: 2025-08-22 diff --git a/docs/RELEASE_NOTES_v0.2.2.md b/docs/RELEASE_NOTES_v0.2.2.md new file mode 100644 index 0000000..3d91e7d --- /dev/null +++ b/docs/RELEASE_NOTES_v0.2.2.md @@ -0,0 +1,41 @@ +# Q-Blog — Release Notes (v0.2.2) + +## Highlights + +- **Wiki edits without owning a blog** — editors can publish revisions to wiki-enabled blogs even if they don’t have their own blog. +- **Authenticate UX** — auto-auth on first load; Authenticate button shows spinner and prevents double-clicks. +- **Names popover** — clear states for Loading / Error / Empty; prevents “empty menu” confusion. +- **Stability** — tests added for wiki edits, auth button, and names popover. + +## Changes + +### Code + +- `CreatePostMinimal.tsx` — remove `currentBlog` guard in **edit** publish path; prefill title from canonical content. +- `CreatePostBuilder.tsx` — same guard removal in edit path. +- `CreatePost.tsx` — wiki edit gating; canonical selection; passes content to editors. +- `GlobalWrapper.tsx` — thin, idempotent auth; auto-auth on mount; parallel names fetch (`namesStatus`). +- `Navbar.tsx` — Authenticate spinner/disabled; names popover states; “My Blogs” popover. + +### Docs + +- `FEATURE_WIKI_MODE_OVERVIEW.md` — editors don’t need their own blog for edits. +- `TECH_IMPL_WIKI_MODE.md` — edit flow clarified; BLOG vs BLOG_POST responsibilities. +- `FEATURE_MULTIBLOG_OVERVIEW.md` — header Blog Switcher out-of-scope for v0.2.2. + +### Tests + +- `CreatePost.wiki.editNoBlog.test.tsx` — publishes edit without `currentBlog`. +- `AuthenticateButton.test.tsx` — loading state disables click. +- `SwitchNameMenu.test.tsx` — Loading state visible in popover. + +## Upgrade notes + +- No migrations. Backward-compatible: missing wiki fields → wiki disabled. +- If you vendor custom Navbar, integrate the names popover states to match tests and UX. + +## Verify + +- Open a wiki-enabled blog (empty whitelist). Authenticate → edit a post as a different Name with no blog → Publish succeeds. +- Authenticate button shows spinner; double-clicking is ignored. +- Switch-name popover shows Loading/Error/Empty correctly. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index c1b151f..ef0ee4f 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -1,8 +1,8 @@ -# Releasing — Q‑Blog +# Releasing — Q-Blog This summarizes the practical steps we use today to publish a release via Gitea and attach versioned artifacts. -## Pre‑flight +## Pre-flight - Ensure CI is green on the branch to release. - Verify locally: @@ -60,4 +60,4 @@ By default, only the current version’s zips are uploaded (`release/*-vX.Y.Z-*. ## Notes - The release scripts avoid attaching older zip files by default. -- For re‑runs where artifacts already exist, the API calls are idempotent: release is patched and assets re‑uploaded as needed. +- For re-runs where artifacts already exist, the API calls are idempotent: release is patched and assets re-uploaded as needed. diff --git a/docs/RISKS_ASSUMPTIONS.md b/docs/RISKS_ASSUMPTIONS.md index ab99490..b98938d 100644 --- a/docs/RISKS_ASSUMPTIONS.md +++ b/docs/RISKS_ASSUMPTIONS.md @@ -1,13 +1,13 @@ -# Q‑Blog — Risks, Assumptions & Mitigations +# Q-Blog — Risks, Assumptions & Mitigations _Generated 2025-08-16 23:27Z_ | ID | Area | Risk/Assumption | Phase | Impact | Mitigation | | --: | ------- | ----------------------------------------------------------------------- | :---: | ------ | ----------------------------------------------------- | | R1 | Data | Legacy content migration to default blogs may fail on malformed records | 6 | High | Idempotent migrator, dry run, backup + rollback notes | -| R2 | Editor | Rich‑text sanitization strips needed formatting | 10 | Medium | Allowlist tuned with tests; sample content goldens | +| R2 | Editor | Rich-text sanitization strips needed formatting | 10 | Medium | Allowlist tuned with tests; sample content goldens | | R3 | A11y | Keyboard traps in complex modals/popovers | 4–5 | Medium | Component audits; focus tests; Esc/restore policies | -| R4 | Collab | Permission gaps lead to privilege escalation | 7 | High | Server‑side checks; matrix tests; deny‑by‑default | +| R4 | Collab | Permission gaps lead to privilege escalation | 7 | High | Server-side checks; matrix tests; deny-by-default | | R5 | Perf | Large lists regress INP/LCP | 8 | Medium | Virtualization, prefetch, memoization; vitals budgets | | A1 | API | We can evolve/extend server contracts | 0 | — | If not, draft shims and versioned adapters | | A2 | Tooling | CI runners can execute headless browsers for axe/e2e | 2 | — | If flaky, move some checks to nightly | diff --git a/docs/ROADMAP_DEPENDENCIES.md b/docs/ROADMAP_DEPENDENCIES.md index 0b87e7d..231a452 100644 --- a/docs/ROADMAP_DEPENDENCIES.md +++ b/docs/ROADMAP_DEPENDENCIES.md @@ -1,4 +1,4 @@ -# Q‑Blog — Roadmap Dependencies & Milestones +# Q-Blog — Roadmap Dependencies & Milestones _Generated 2025-08-16 23:27Z_ @@ -7,16 +7,16 @@ _Generated 2025-08-16 23:27Z_ - Phase 2 depends on Phase 1 docs (testing/a11y standards referenced). - Phase 3 depends on Phase 2 harness (to verify fixes). - Phase 4 (a11y) builds on Phase 3 stability. -- Phase 6 (multi‑blog) depends on Phase 5 UX patterns (switcher placement, routing clarity). +- Phase 6 (multi-blog) depends on Phase 5 UX patterns (switcher placement, routing clarity). - Phase 7 (shared blogs) depends on Phase 6 data model and routing. - Phase 10 (release) depends on all prior phases plus observability wiring. ## Milestone Gates - **M1 — Baseline Ready (P0–P2):** Docs present; typecheck/lint/tests green; coverage report generated. -- **M2 — Stable & Accessible (P3–P4):** No crashers; keyboard‑only journeys pass; axe critical=0. +- **M2 — Stable & Accessible (P3–P4):** No crashers; keyboard-only journeys pass; axe critical=0. - **M3 — UX Solid (P5):** Consistent patterns, no dead ends; editor ergonomics audited. -- **M4 — Multi‑Blog (P6):** Create/switch blogs with migrated legacy content. +- **M4 — Multi-Blog (P6):** Create/switch blogs with migrated legacy content. - **M5 — Shared Blogs (P7):** Role matrix enforced; invites work. - **M6 — Resilient & Performant (P8–P9):** Vitals within budget; themes/i18n consistent. - **M7 — Release Ready (P10):** Telemetry, security checks, notes/guides done. diff --git a/docs/SECURITY_PRIVACY_POSTURE.md b/docs/SECURITY_PRIVACY_POSTURE.md index 11572d3..a39721b 100644 --- a/docs/SECURITY_PRIVACY_POSTURE.md +++ b/docs/SECURITY_PRIVACY_POSTURE.md @@ -1,4 +1,4 @@ -# Q‑Blog — Security & Privacy Posture (1.0) +# Q-Blog — Security & Privacy Posture (1.0) _Generated 2025-08-16 23:27Z_ @@ -7,17 +7,17 @@ _Generated 2025-08-16 23:27Z_ - **Least privilege** — Roles restrict actions; tokens scoped; client never authoritative. - **Sanitize everywhere** — Rich text sanitized on save and render (allowlist). - **Fail safe** — On doubt, deny writes; surface clear errors with next actions. -- **Minimal telemetry** — Only crash/quality signals; no PII; user‑visible policy. +- **Minimal telemetry** — Only crash/quality signals; no PII; user-visible policy. - **Defense in depth** — CSP, input validation, dependency hygiene, error boundaries. -## Non‑Goals (1.0) +## Non-Goals (1.0) -- End‑to‑end encryption for content. -- Fine‑grained per‑post ACLs (roles are per blog). +- End-to-end encryption for content. +- Fine-grained per-post ACLs (roles are per blog). ## Checklist (Dev) -- Inputs validated client‑side; re‑validated server‑side. +- Inputs validated client-side; re-validated server-side. - All writes include role checks and revision/ETag for concurrency. - Sanitization unit tests cover common XSS vectors. - Dependencies audited; pinned versions for determinism. diff --git a/docs/TESTING.md b/docs/TESTING.md index 24f3e00..0db7a69 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -51,7 +51,7 @@ Additional tests added: - Test env adds a minimal `IntersectionObserver` polyfill in `tests/setup.ts` for components using `react-intersection-observer`. - UI tests wrap components in MUI `ThemeProvider` with a default theme to satisfy styled theme usage. -## Test Scenarios — Multi‑Blog (future) +## Test Scenarios — Multi-Blog (future) - Header: 0 vs 1 vs N blogs; menu opens/closes; selection navigates and sets active blog. - Name root redirect: single blog → posts; else → `/{{name}}/blogs`; polite loading text in `aria-live`. diff --git a/docs/USER_ANNOUNCEMENT_v0.1.0.md b/docs/USER_ANNOUNCEMENT_v0.1.0.md index baecdc5..0bfcf5d 100644 --- a/docs/USER_ANNOUNCEMENT_v0.1.0.md +++ b/docs/USER_ANNOUNCEMENT_v0.1.0.md @@ -1,6 +1,6 @@ -# Q‑Blog v0.1.0 — What’s New +# Q-Blog v0.1.0 — What’s New -Q‑Blog now supports multiple blogs per Name. +Q-Blog now supports multiple blogs per Name. - New: a “My Blogs” menu to switch between your blogs or create a new one - New: a user blogs page at `/:user/blogs` listing all blogs for that name diff --git a/docs/USER_ANNOUNCEMENT_v0.2.0.md b/docs/USER_ANNOUNCEMENT_v0.2.0.md index be3e697..90194c1 100644 --- a/docs/USER_ANNOUNCEMENT_v0.2.0.md +++ b/docs/USER_ANNOUNCEMENT_v0.2.0.md @@ -1,19 +1,21 @@ -Q‑Blog v0.2.0 — Wiki Mode is here +Q-Blog v0.2.0 — Wiki Mode is here -We’ve shipped a smarter, collaborative blogging experience. When multiple people post updates to the same article, Q‑Blog now shows a single, “canonical” version based on the blog owner’s wiki settings. +We’ve shipped a smarter, collaborative blogging experience. When multiple people post updates to the same article, Q-Blog now shows a single, “canonical” version based on the blog owner’s wiki settings. What’s new -- Canonical posts: If several authors publish a revision of the same post, Q‑Blog picks one to display. The owner’s settings define who can contribute. The most up‑to‑date allowed revision wins; the owner’s revision wins ties. +- Canonical posts: If several authors publish a revision of the same post, Q-Blog picks one to display. The owner’s settings define who can contribute. The most up-to-date allowed revision wins; the owner’s revision wins ties. - Works everywhere: The main feed, Favorites, Subscriptions, and the post page all use canonical selection when wiki mode is enabled. - Quality of life: Search/filter from the navbar and switch between Tile/List views (your choice is remembered). +- (v0.2.1) Better readability: “Create Blog” and blog list action buttons are clearer in both dark and light themes. +- (v0.2.2) Wiki editing bugfix: Editing a post without having a blog no longer crashes the editor. No action needed -Just update to v0.2.0. Your posts and favorites continue to work; collaborative updates will show consistently. +Just update to the current release. Your posts and favorites continue to work; collaborative updates will show consistently. Tips - Owners can enable wiki mode and manage who can edit via blog settings (whitelist/blacklist). - Use the List view to scan more details at a glance; switch back to Tiles any time. -Thanks for using Q‑Blog! +Thanks for using Q-Blog! diff --git a/docs/USER_ANNOUNCEMENT_v0.2.1.md b/docs/USER_ANNOUNCEMENT_v0.2.1.md index 6ded685..ca211d1 100644 --- a/docs/USER_ANNOUNCEMENT_v0.2.1.md +++ b/docs/USER_ANNOUNCEMENT_v0.2.1.md @@ -1,4 +1,4 @@ -Q‑Blog v0.2.1 — Smoother Wiki Editing +Q-Blog v0.2.1 — Smoother Wiki Editing We’ve polished the wiki experience based on feedback from v0.2.0. diff --git a/docs/USER_JOURNEYS.md b/docs/USER_JOURNEYS.md index 234c91e..e3d23d5 100644 --- a/docs/USER_JOURNEYS.md +++ b/docs/USER_JOURNEYS.md @@ -1,4 +1,4 @@ -# Q‑Blog 1.0 — Personas & Key Journeys +# Q-Blog 1.0 — Personas & Key Journeys _Generated 2025-08-16 23:27Z_ @@ -30,7 +30,7 @@ _Generated 2025-08-16 23:27Z_ - ⚠️ Note: PostEditor autosave + live region announcements need verification in code; docs may overstate completeness. -## Journeys — Multi‑Blog +## Journeys — Multi-Blog ### 0 blogs (self) @@ -46,4 +46,4 @@ _Generated 2025-08-16 23:27Z_ ### Public view -- Viewing someone else’s `/{{name}}/blogs` shows a read‑only list; no edit/create controls. +- Viewing someone else’s `/{{name}}/blogs` shows a read-only list; no edit/create controls. diff --git a/docs/VISION_PRFAQ.md b/docs/VISION_PRFAQ.md index 9cd129a..7a73a42 100644 --- a/docs/VISION_PRFAQ.md +++ b/docs/VISION_PRFAQ.md @@ -1,32 +1,32 @@ -# Q‑Blog 1.0 — PRFAQ +# Q-Blog 1.0 — PRFAQ _Generated 2025-08-16 23:27Z_ ## Press Release (Narrative) -Today we announce **Q‑Blog 1.0**, a modern, accessible writing workspace that makes it effortless to publish under multiple blogs with a single name, and to collaborate safely through **Shared Blogs**. Q‑Blog pairs a focused editor with strong correctness, accessibility, and testability so writers can publish confidently, teams can coordinate, and readers can trust what they see. +Today we announce **Q-Blog 1.0**, a modern, accessible writing workspace that makes it effortless to publish under multiple blogs with a single name, and to collaborate safely through **Shared Blogs**. Q-Blog pairs a focused editor with strong correctness, accessibility, and testability so writers can publish confidently, teams can coordinate, and readers can trust what they see. Key highlights: - **Multiple blogs per name** — Organize topics cleanly without creating new accounts. -- **Shared blogs** — Invite editors and authors with role‑based permissions. -- **Inclusive by design** — Keyboard‑ready, screen‑reader friendly, respectful of motion/contrast preferences. +- **Shared blogs** — Invite editors and authors with role-based permissions. +- **Inclusive by design** — Keyboard-ready, screen-reader friendly, respectful of motion/contrast preferences. - **Resilient** — Deterministic saves, offline tolerance for drafts, clear recoveries on failure. -- **Transparent** — Human‑readable release notes and an upgrade guide. +- **Transparent** — Human-readable release notes and an upgrade guide. ## FAQ -**What is Q‑Blog? Who is it for?** +**What is Q-Blog? Who is it for?** A content creation app for individuals and teams who want a fast, accessible, and reliable blogging workflow. **How is it different?** -Strong quality bar (tests, a11y), multi‑blog under one name, and safe collaboration via shared blogs—all while keeping the UI simple. +Strong quality bar (tests, a11y), multi-blog under one name, and safe collaboration via shared blogs—all while keeping the UI simple. **Why multiple blogs per name?** Writers often publish in distinct domains (e.g., dev notes vs. essays). Separate blogs keep audiences clear without fragmenting identity. **How do Shared Blogs work?** -Blog owners can invite collaborators and assign roles (**Owner**, **Editor**, **Author**). Permissions are enforced server‑side; the UI mirrors capabilities but never trusts the client alone. +Blog owners can invite collaborators and assign roles (**Owner**, **Editor**, **Author**). Permissions are enforced server-side; the UI mirrors capabilities but never trusts the client alone. **What about accessibility?** We commit to keyboard-only journeys, semantic landmarks, focus management, descriptive names/labels, and honoring user preferences for motion and contrast. @@ -35,7 +35,7 @@ We commit to keyboard-only journeys, semantic landmarks, focus management, descr Rich text is sanitized on save and render (allowlist). Permissions gate every write. Observability captures issues without leaking personal data. **How do you measure success?** -Reliability (save success rate), accessibility (keyboard checks + automated audits), performance (Web Vitals), and usability (zero “dead‑end” flows). +Reliability (save success rate), accessibility (keyboard checks + automated audits), performance (Web Vitals), and usability (zero “dead-end” flows). **What’s in 1.0?** -Multi‑blog per name, Shared Blogs, resilient editor workflows, accessibility baseline, docs, and a clear release process. +Multi-blog per name, Shared Blogs, resilient editor workflows, accessibility baseline, docs, and a clear release process. diff --git a/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md b/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md index ed82fda..447cdf3 100644 --- a/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md +++ b/docs/features/FEATURE_MULTIBLOG_OVERVIEW.md @@ -31,3 +31,7 @@ Allow a single **Name** to own **multiple Blogs**, while preserving existing blo - Aligns with entity contract (**Name 1..N Blog**). - Minimizes refactors; relies on existing blog-scoped routing and components. - Backward-compatible for deep links. + +## Notes + +- The **Header Blog Switcher** dropdown is **out of scope for v0.2.2** (list + redirect are implemented). diff --git a/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md b/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md index ae8ee13..3b9f8f9 100644 --- a/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md +++ b/docs/features/FEATURE_WIKI_MODE_OVERVIEW.md @@ -1,25 +1,27 @@ -# Wiki Mode / Multi‑Editor — Product Overview +# Wiki Mode / Multi-Editor — Product Overview _Generated 2025-08-22_ ## Summary -Q‑Blog gains a new **Wiki mode**, allowing multiple **Names** to publish **visible revisions** of posts under a blog. +Q-Blog gains a new **Wiki mode**, allowing multiple **Names** to publish **visible revisions** of posts under a blog. - **Names** are the identity key (not account addresses). -- Authorization is computed **client‑side**; Qortal has no server scripting. +- Authorization is computed **client-side**; Qortal has no server scripting. - Precedence: **blacklist > whitelist > global allow**. -- Backward‑compatible: missing fields mean wiki is off. +- Backward-compatible: missing fields mean wiki is off. ## UX +> Editors **do not need their own blog** to publish a revision on a wiki-enabled blog. + - **Blog Settings:** toggle Wiki Mode; configure whitelist/blacklist of Names. - **Post page:** shows the canonical (latest visible) revision with author/date attribution. - Implemented: canonical chosen across Names by exact BLOG_POST identifier; attribution shown when author differs from owner. - **Edit button:** shown only to Owner and authorized editors. - **Blog list:** groups posts by lineage and shows canonical revision per group. - Implemented for Blog page (`/{name}/{blog}`) using identifier-grouping. - - Global feed, Subscriptions, and Favorites canonicalize duplicates via a cached lookup of per‑blog wiki settings. + - Global feed, Subscriptions, and Favorites canonicalize duplicates via a cached lookup of per-blog wiki settings. - Header “My Blogs” switcher updates both title and posts by seeding blog context immediately. ## Canonical Selection (v0.2.0) @@ -28,7 +30,7 @@ The app selects one canonical revision when multiple Names publish the same `BLO - Authorization first: filter out unauthorized Names using blog settings. The Owner is always allowed (cannot be blocked). - Freshest wins: compare `updatedAt` or `qdnUpdated`; pick the most recent. -- Tie‑breakers: if timestamps tie, prefer the Owner; otherwise, pick the lowest id for stability. +- Tie-breakers: if timestamps tie, prefer the Owner; otherwise, pick the lowest id for stability. This logic is applied consistently in: diff --git a/docs/features/SPEC_WIKI_RESOLVER.md b/docs/features/SPEC_WIKI_RESOLVER.md index 149d9b1..a757582 100644 --- a/docs/features/SPEC_WIKI_RESOLVER.md +++ b/docs/features/SPEC_WIKI_RESOLVER.md @@ -11,7 +11,7 @@ _Generated 2025-08-22_ 1. Owner always allowed. 2. If Name ∈ blacklist → blocked. -3. If whitelist non‑empty → allowed iff Name ∈ whitelist (and not blacklisted). +3. If whitelist non-empty → allowed iff Name ∈ whitelist (and not blacklisted). 4. If whitelist empty → allowed (unless blacklisted). If Name in both lists → blacklisted. diff --git a/docs/features/TECH_IMPL_MULTIBLOG.md b/docs/features/TECH_IMPL_MULTIBLOG.md index a5fe4b3..debceba 100644 --- a/docs/features/TECH_IMPL_MULTIBLOG.md +++ b/docs/features/TECH_IMPL_MULTIBLOG.md @@ -86,7 +86,7 @@ _Generated 2025-08-21_ - Header dropdown fetches `listBlogsByName(currentUser.nameId)` when authenticated. - `UserBlogs` page fetches `listBlogsByName(params.nameId)`. -> If an endpoint already exists, reuse it. Otherwise add it in `src/store/api.ts` with blog-scoped cache keys documented in Q‑Blog. +> If an endpoint already exists, reuse it. Otherwise add it in `src/store/api.ts` with blog-scoped cache keys documented in Q-Blog. --- diff --git a/docs/features/TECH_IMPL_WIKI_MODE.md b/docs/features/TECH_IMPL_WIKI_MODE.md index b46ec7b..03508d4 100644 --- a/docs/features/TECH_IMPL_WIKI_MODE.md +++ b/docs/features/TECH_IMPL_WIKI_MODE.md @@ -1,4 +1,4 @@ -# Wiki Mode / Multi‑Editor — Technical Implementation +# Wiki Mode / Multi-Editor — Technical Implementation _Generated 2025-08-22_ @@ -23,7 +23,7 @@ _Generated 2025-08-22_ 1. Owner always allowed. 2. Blacklist blocks regardless. -3. Whitelist non‑empty → only listed Names (minus blacklist). +3. Whitelist non-empty → only listed Names (minus blacklist). 4. Whitelist empty → all Names allowed (minus blacklist). ## canEdit(viewerName, settings, ownerName) @@ -40,7 +40,7 @@ _Generated 2025-08-22_ Reference implementation in code: - `src/utils/wiki.ts` provides `isAuthorized`, `canEdit`, and `selectCanonical` used across UI. -- `src/utils/wikiSettingsCache.ts` caches per‑blog (ownerName, settings) using `/arbitrary/resources?service=BLOG&identifier=...` and `/arbitrary/BLOG//`. +- `src/utils/wikiSettingsCache.ts` caches per-blog (ownerName, settings) using `/arbitrary/resources?service=BLOG&identifier=...` and `/arbitrary/BLOG//`. - `src/hooks/useFetchPosts.tsx` groups search results by identifier and applies canonical selection in feed, favorites, and subscriptions. - `src/pages/BlogIndividualPost/BlogIndividualPost.tsx` resolves canonical author before fetching BLOG_POST JSON when wiki mode is enabled. @@ -64,3 +64,9 @@ Reference implementation in code: - Settings Resolution: reads wiki settings from BLOG resource metadata when available; otherwise fetches BLOG JSON as fallback. - Prefetch: for each page of results, settings for blogs with duplicate identifiers are prefetched in parallel (singletons skip resolution). - Canonicalization happens only when necessary; otherwise owner or newest item is used. + +## Edit Flow (Wiki) + +- **Editors do not need their own blog** to publish a revision of an existing post when the target blog has Wiki Mode enabled. +- Edits publish a new `BLOG_POST` under the editor's **Name** using the **original BLOG_POST identifier**; the canonical resolver selects the visible revision. +- Blog (`service: BLOG`) metadata is **not** republished during edits. diff --git a/package-lock.json b/package-lock.json index 0fbb5c8..0222177 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "q-blog", - "version": "0.2.0", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "q-blog", - "version": "0.2.0", + "version": "0.2.2", "dependencies": { "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", @@ -10565,21 +10565,6 @@ } } }, - "node_modules/vite-node/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -11242,21 +11227,6 @@ } } }, - "node_modules/vitest/node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 3682122..f4efc7c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "q-blog", "private": true, - "version": "0.2.1", + "version": "0.2.2", "type": "module", "scripts": { "dev": "vite", diff --git a/scripts/dev/patch-gitignore-phase1.sh b/scripts/dev/patch-gitignore-phase1.sh index e825f3d..f86ca85 100755 --- a/scripts/dev/patch-gitignore-phase1.sh +++ b/scripts/dev/patch-gitignore-phase1.sh @@ -11,7 +11,7 @@ if [[ -f "$TARGET" ]]; then fi cat > "$TARGET" <<'IGN' -# --- Q‑Blog canonical ignore (Phase 1) --- +# --- Q-Blog canonical ignore (Phase 1) --- # Logs logs *.log diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index 40287f5..2f9cdcb 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -17,7 +17,7 @@ import AddBoxIcon from '@mui/icons-material/AddBox'; import Badge from '@mui/material/Badge'; import NotificationsIcon from '@mui/icons-material/Notifications'; import ExitToAppIcon from '@mui/icons-material/ExitToApp'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { togglePublishBlogModal } from '../../../state/features/globalSlice'; import { useDispatch, useSelector } from 'react-redux'; import AutoStoriesIcon from '@mui/icons-material/AutoStories'; @@ -25,16 +25,14 @@ import { RootState } from '../../../state/store'; import type { BlogSummary } from '../../../utils/blogs'; import { UserNavbar } from '../../common/UserNavbar/UserNavbar'; import { removePrefix } from '../../../utils/blogIdformats'; -import { useLocation } from 'react-router-dom'; import BookmarkIcon from '@mui/icons-material/Bookmark'; import SubscriptionsIcon from '@mui/icons-material/Subscriptions'; import { BlockedNamesModal } from '../../common/BlockedNamesModal/BlockedNamesModal'; import SearchIcon from '@mui/icons-material/Search'; import EmailIcon from '@mui/icons-material/Email'; import localforage from 'localforage'; -const notification = localforage.createInstance({ - name: 'notification', -}); +import type { NameRecord } from '../../../utils/qortalRequestFunctions'; +const notification = localforage.createInstance({ name: 'notification' }); import BackspaceIcon from '@mui/icons-material/Backspace'; import { @@ -53,6 +51,7 @@ import { AccountCircleSVG } from '../../../assets/svgs/AccountCircleSVG'; import QblogLogo from '../../../assets/img/qBlogLogo.png'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import PersonOffIcon from '@mui/icons-material/PersonOff'; +import CircularProgress from '@mui/material/CircularProgress'; import { NewWindowSVG } from '../../../assets/svgs/NewWindowSVG'; import { addFilteredPosts, @@ -62,7 +61,7 @@ import { import { Item } from '../../common/Comments/CommentEditor'; import { formatDate } from '../../../utils/time'; import CheckIcon from '@mui/icons-material/Check'; -import type { NameRecord } from '../../../utils/qortalRequestFunctions'; + interface Props { isAuthenticated: boolean; hasBlog: boolean; @@ -75,6 +74,8 @@ interface Props { onSwitchActiveName: (name: string) => void; userBlogs?: BlogSummary[]; onSelectBlog?: (blog: BlogSummary) => void; + authStatus?: 'idle' | 'loading' | 'succeeded' | 'failed'; + namesStatus?: 'idle' | 'loading' | 'succeeded' | 'failed'; } function useQuery() { @@ -93,6 +94,8 @@ const NavBar: React.FC = ({ onSwitchActiveName, userBlogs = [], onSelectBlog, + authStatus = 'idle', + namesStatus = 'idle', }) => { const navigate = useNavigate(); const dispatch = useDispatch(); @@ -114,7 +117,6 @@ const NavBar: React.FC = ({ ); const [anchorElBlogs, setAnchorElBlogs] = React.useState(null); const [isOpenModal, setIsOpenModal] = React.useState(false); - const [searchVal, setSearchVal] = useState(''); const searchValRef = useRef(''); const inputRef = useRef(null); const stripBlogId = removePrefix(visitingBlog?.blogId || ''); @@ -196,7 +198,7 @@ const NavBar: React.FC = ({ searchValRef.current = e.target.value; }} onKeyDown={(event) => { - if (event.key === 'Enter' || event.keyCode === 13) { + if (event.key === 'Enter' || (event as any).keyCode === 13) { if (!searchValRef.current) { dispatch(setIsFiltering(false)); dispatch(setFilterValue('')); @@ -214,29 +216,17 @@ const NavBar: React.FC = ({ }} placeholder="Filter by name" sx={{ - '&&:before': { - borderBottom: 'none', - }, - '&&:after': { - borderBottom: 'none', - }, - '&&:hover:before': { - borderBottom: 'none', - }, - '&&.Mui-focused:before': { - borderBottom: 'none', - }, - '&&.Mui-focused': { - outline: 'none', - }, + '&&:before': { borderBottom: 'none' }, + '&&:after': { borderBottom: 'none' }, + '&&:hover:before': { borderBottom: 'none' }, + '&&.Mui-focused:before': { borderBottom: 'none' }, + '&&.Mui-focused': { outline: 'none' }, fontSize: '18px', }} /> { if (!searchValRef.current) { dispatch(setIsFiltering(false)); @@ -254,9 +244,7 @@ const NavBar: React.FC = ({ }} /> { dispatch(setIsFiltering(false)); dispatch(setFilterValue('')); @@ -286,19 +274,24 @@ const NavBar: React.FC = ({ alignItems: 'center', }} > - {/* Add isAuthenticated && before username and wrap StyledButton in this condition*/} {!isAuthenticated && ( - - - Authenticate + + {authStatus === 'loading' ? ( + + ) : ( + + )} + {authStatus === 'loading' ? 'Authenticating…' : 'Authenticate'} )} */} + {isAuthenticated && userName && hasAttemptedToFetchBlogInitial && (userBlogs?.length || 0) === 0 && ( - { - dispatch(togglePublishBlogModal(true)); - }} - > + dispatch(togglePublishBlogModal(true))}> Create Blog )} + {isAuthenticated && userName && (userBlogs?.length || 0) >= 1 && ( <> = ({ alt="User Avatar" width="32" height="32" - style={{ - borderRadius: '50%', - }} + style={{ borderRadius: '50%' }} /> )} )} + - {!!(allNames && allNames.length) && ( - - - Switch name - - - {allNames - .filter((n) => !!n.name) - .map((n) => { - const isActive = n.name === userName; - return ( - { - onSwitchActiveName(n.name); - handleClose(); - }} - > - {isActive && ( - - - - )} - {!isActive && } - - - ); - })} + + + Switch name + + + {namesStatus === 'loading' && ( + + + + + + + - - - )} - navigate('/favorites')}> - - Favorites - - navigate('/subscriptions')}> - - Subscriptions - - { - setIsOpenModal(true); - handleClose(); - }} - > - - Blocked Names - - - + + + + + + + + )} + + {namesStatus === 'succeeded' && (!allNames || allNames.length === 0) && ( + + + + + + )} + + {(!namesStatus || namesStatus === 'succeeded') && allNames && allNames.length > 0 && ( + + {allNames.map((n) => { + const isActive = userName === n.name; + return ( + { + onSwitchActiveName(n.name); + handleClose(); + }} + > + {isActive && ( + + + + )} + {!isActive && } + + + ); + })} + + )} + + + + navigate('/favorites')}> + + Favorites + + + navigate('/subscriptions')}> + + Subscriptions + + + { + setIsOpenModal(true); + handleClose(); }} > - + + Blocked Names + - Q-Mail - - + + + + Q-Mail + + + + {isOpenModal && } diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index bfacad4..d3f6105 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -34,6 +34,8 @@ import ErrorBoundary from '../../components/common/ErrorBoundary'; import { CommentSection } from '../../components/common/Comments/CommentSection'; import { canEdit, BlogSettings, selectCanonical } from '../../utils/wiki'; import { Tipping } from '../../components/common/Tipping/Tipping'; +import { getCachedBlogSettings } from '../../utils/wikiSettingsCache'; +import { BlogSettings as BlogSettingsType } from '../../utils/wiki'; import FileElement from '../../components/FileElement'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import { setNotification } from '../../state/features/notificationsSlice'; @@ -111,6 +113,7 @@ export const BlogIndividualPost = () => { }; const [blogContent, setBlogContent] = React.useState(null); const [canonicalAuthor, setCanonicalAuthor] = React.useState(''); + const [originOwner, setOriginOwner] = React.useState(''); const [canonicalUpdated, setCanonicalUpdated] = React.useState(null); const [isOpenSwitchPlaylistModal, setisOpenSwitchPlaylistModal] = useState(false); const tempSaveAudio = useRef(null); @@ -376,7 +379,7 @@ export const BlogIndividualPost = () => { > { - navigate(`/${user}`); + navigate(`/${originOwner || user}`); }} sx={{ cursor: 'pointer', @@ -390,7 +393,7 @@ export const BlogIndividualPost = () => { {` ${user}`} + >{` ${originOwner || user}`} } /> {user && ( diff --git a/src/pages/BlogList/BlogList.tsx b/src/pages/BlogList/BlogList.tsx index b82f8d9..dd06d48 100644 --- a/src/pages/BlogList/BlogList.tsx +++ b/src/pages/BlogList/BlogList.tsx @@ -214,17 +214,23 @@ export const BlogList = ({ mode }: BlogListProps) => { }} > navigate(`/${blogPost.user}/${blogId}/${str2}`)} + onClick={() => + navigate( + `/${ + (blogSettingsMap.get(str1) as any)?.ownerName || blogPost.user + }/${blogId}/${str2}`, + ) + } description={blogPost?.description} title={blogPost?.title} createdAt={blogPost?.createdAt} - author={blogPost.user} + author={(blogSettingsMap.get(str1) as any)?.ownerName || blogPost.user} postImage={blogPost?.postImage} blogPost={blogPost} isValid={blogPost?.isValid} @@ -280,19 +286,23 @@ export const BlogList = ({ mode }: BlogListProps) => { key={blogPost.id} > { - navigate(`/${blogPost.user}/${blogId}/${str2}`); + navigate( + `/${ + (blogSettingsMap.get(str1) as any)?.ownerName || blogPost.user + }/${blogId}/${str2}`, + ); }} description={blogPost?.description} title={blogPost?.title} createdAt={blogPost?.createdAt} - author={blogPost.user} + author={(blogSettingsMap.get(str1) as any)?.ownerName || blogPost.user} postImage={blogPost?.postImage} blogPost={blogPost} isValid={blogPost?.isValid} diff --git a/src/pages/BlogList/PostPreview.tsx b/src/pages/BlogList/PostPreview.tsx index e75ee8b..e9b391c 100644 --- a/src/pages/BlogList/PostPreview.tsx +++ b/src/pages/BlogList/PostPreview.tsx @@ -51,6 +51,8 @@ interface BlogPostPreviewProps { title: string; createdAt: number | string; author: string; + originOwner?: string; + originHandle?: string; postImage?: string; description: any; blogPost: BlogPost; @@ -64,6 +66,8 @@ const BlogPostPreview: React.FC = ({ title, createdAt, author, + originOwner, + originHandle, postImage, description, onClick, diff --git a/src/pages/CreatePost/CreatePost.tsx b/src/pages/CreatePost/CreatePost.tsx index 97655bb..17e6d62 100644 --- a/src/pages/CreatePost/CreatePost.tsx +++ b/src/pages/CreatePost/CreatePost.tsx @@ -5,130 +5,175 @@ import { CreatePostBuilder } from './CreatePostBuilder'; import { CreatePostMinimal } from './CreatePostMinimal'; import HandymanRoundedIcon from '@mui/icons-material/HandymanRounded'; import HourglassFullRoundedIcon from '@mui/icons-material/HourglassFullRounded'; -import { display } from '@mui/system'; import { useDispatch, useSelector } from 'react-redux'; import { setIsLoadingGlobal } from '../../state/features/globalSlice'; import { useNavigate, useParams } from 'react-router-dom'; import { checkStructure } from '../../utils/checkStructure'; import { RootState } from '../../state/store'; -import { addPrefix, buildIdentifierFromCreateTitleIdAndId } from '../../utils/blogIdformats'; -import { Tipping } from '../../components/common/Tipping/Tipping'; +import { addPrefix } from '../../utils/blogIdformats'; +import { getCachedBlogSettings } from '../../utils/wikiSettingsCache'; +import { BlogSettings, canEdit, selectCanonical } from '../../utils/wiki'; + type EditorType = 'minimal' | 'builder'; + interface CreatePostProps { mode?: string; } + export const CreatePost = ({ mode }: CreatePostProps) => { const { user: username, postId, blog } = useParams(); - const fullPostId = useMemo(() => { - if (!blog || !postId || mode !== 'edit') return ''; - const formBlogId = addPrefix(blog); - const formPostId = buildIdentifierFromCreateTitleIdAndId(formBlogId, postId); - return formPostId; - }, [blog, postId, mode]); const user = useSelector((state: RootState) => state.auth?.user); + const dispatch = useDispatch(); + const navigate = useNavigate(); + + // IMPORTANT: in edit mode, :postId is already the full identifier (q-blog-*-post-*) + const fullPostId = useMemo(() => (mode === 'edit' && postId ? postId : ''), [postId, mode]); const [toggleEditorType, setToggleEditorType] = useState(null); const [blogContentForEdit, setBlogContentForEdit] = useState(null); const [blogMetadataForEdit, setBlogMetadataForEdit] = useState(null); const [editType, setEditType] = useState(null); const [isOpen, setIsOpen] = useState(false); - const dispatch = useDispatch(); - const navigate = useNavigate(); - React.useEffect(() => { + // Creation flow: open editor type chooser + useEffect(() => { if (!toggleEditorType && mode !== 'edit') { setIsOpen(true); } - }, [setIsOpen, toggleEditorType]); + }, [setIsOpen, toggleEditorType, mode]); - const switchType = () => { - setIsOpen(true); - }; + const switchType = () => setIsOpen(true); + // Permission check for edit useEffect(() => { - if (username && user?.name && mode === 'edit') { - if (username !== user?.name) { - navigate('/'); + let cancelled = false; + (async () => { + if (mode !== 'edit' || !blog) return; + const viewer = user?.name; + if (!viewer) { + // Defer permission decision until viewer is known (prevents redirect race in tests) + return; } - } - }, [user, username, mode]); + const blogFull = addPrefix(blog); + try { + const { ownerName, settings } = await getCachedBlogSettings(blogFull); + const allowed = canEdit(viewer, settings as BlogSettings, ownerName); + if (!allowed && !cancelled) navigate('/'); + } catch { + if (!cancelled) navigate('/'); + } + })(); + return () => { + cancelled = true; + }; + }, [user?.name, blog, mode, navigate]); + // Load post content for edit const getBlogPost = React.useCallback(async () => { + if (!blog || !postId) return; + const blogFull = addPrefix(blog); + const fullId = fullPostId; try { dispatch(setIsLoadingGlobal(true)); - const url = `/arbitrary/BLOG_POST/${username}/${fullPostId}`; - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - const responseData = await response.json(); - if (checkStructure(responseData)) { - // setNewPostContent(responseData.postContent) - // setTitle(responseData?.title || '') - // setBlogInfo(responseData) - const blogType = responseData?.layoutGeneralSettings?.blogPostType; - - if (blogType) { - setEditType(blogType); - setBlogContentForEdit(responseData); - } - //TODO - NAME SHOULD BE EXACT - // const url2 = `/arbitrary/resources/search?mode=ALL&service=BLOG_POST&identifier=${fullPostId}&exactMatchNames=${username}&limit=1&includemetadata=true` - const url2 = `/arbitrary/resources?service=BLOG_POST&identifier=${fullPostId}&name=${username}&limit=1&includemetadata=true`; - - const responseBlogs = await fetch(url2, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, + // 1) Fast path: fetch content immediately using the route ":user" param + const initialName = username || ''; + try { + const contentUrl = `/arbitrary/BLOG_POST/${initialName}/${fullId}`; + const contentResp = await fetch(contentUrl, { + headers: { 'Content-Type': 'application/json' }, }); - - const dataMetadata = await responseBlogs.json(); - if (dataMetadata && dataMetadata.length > 0) { - setBlogMetadataForEdit(dataMetadata[0]); + const contentJson = await contentResp.json(); + if (checkStructure(contentJson)) { + const blogType = contentJson?.layoutGeneralSettings?.blogPostType; + setEditType(blogType || 'minimal'); + setBlogContentForEdit(contentJson); + } else { + // Backward-compat fallback for legacy posts + const fallbackLayouts = + contentJson?.layouts && + typeof contentJson.layouts === 'object' && + Array.isArray(contentJson.layouts.rows) + ? contentJson.layouts + : { rows: [] }; + const fallback = { + title: contentJson?.title ?? '', + createdAt: Date.now(), + postContent: Array.isArray(contentJson?.postContent) ? contentJson.postContent : [], + layouts: fallbackLayouts, + layoutGeneralSettings: contentJson?.layoutGeneralSettings ?? { + blogPostType: 'minimal', + }, + } as any; + setEditType('minimal'); + setBlogContentForEdit(fallback); } + } catch { + // ignore; canonical resolution may still populate metadata } - } catch (error) { + + // 2) Resolve canonical author + revisions metadata in the background + (async () => { + try { + const { ownerName, settings } = await getCachedBlogSettings(blogFull); + let contentName = initialName; + + // Search revisions for this identifier + let items: any[] = []; + try { + const searchUrl = `/arbitrary/resources/search?service=BLOG_POST&identifier=${encodeURIComponent( + fullId, + )}&includemetadata=true&reverse=true`; + const resp = await fetch(searchUrl, { + headers: { 'Content-Type': 'application/json' }, + }); + items = (await resp.json()) || []; + } catch { + items = []; + } + + if (settings?.wikiEnabled && Array.isArray(items) && items.length > 0) { + const revisions = items.map((it: any) => ({ + id: it.identifier, + originPostId: it.identifier, + lineageBlogId: blogFull, + authorName: it.name, + updatedAt: it.updated, + qdnUpdated: it.updated, + published: true, + })); + const sel = selectCanonical(revisions, settings as BlogSettings, ownerName, { + expectedLineageBlogId: blogFull, + }); + if (sel?.authorName) contentName = sel.authorName; + } + + const chosenMeta = (items || []).find((it: any) => it.name === contentName) || items?.[0]; + if (chosenMeta) setBlogMetadataForEdit(chosenMeta); + } catch { + // ignore metadata issues; editor remains usable + } + })(); } finally { dispatch(setIsLoadingGlobal(false)); } - }, [username, fullPostId]); - React.useEffect(() => { + }, [username, fullPostId, blog, postId, dispatch]); + + useEffect(() => { if (mode === 'edit') { - getBlogPost(); + void getBlogPost(); } - }, [mode]); + }, [mode, getBlogPost]); return ( <> - {/* {toggleEditorType === 'minimal' && ( - - )} - {toggleEditorType === 'builder' && ( - - )} */} - {isOpen && ( - + {/* Create mode type chooser */} + {isOpen && mode !== 'edit' && ( + {toggleEditorType && ( Switching editor type will delete your current progress )} - - + { setToggleEditorType('minimal'); @@ -172,8 +217,15 @@ export const CreatePost = ({ mode }: CreatePostProps) => { )} - {toggleEditorType === 'minimal' && } - {toggleEditorType === 'builder' && } + {/* Create mode editors */} + {toggleEditorType === 'minimal' && mode !== 'edit' && ( + + )} + {toggleEditorType === 'builder' && mode !== 'edit' && ( + + )} + + {/* Edit mode editors */} {mode === 'edit' && editType === 'minimal' && ( { + if ( + blogMetadataForEdit && + typeof blogMetadataForEdit.blogId === 'string' && + blogMetadataForEdit.blogId + ) { + return blogMetadataForEdit.blogId; + } + if (currentBlog && typeof currentBlog.blogId === 'string') return currentBlog.blogId; + return null; +}; import React, { useCallback, useEffect } from 'react'; import BlogEditor from '../../components/editor/BlogEditor'; @@ -125,6 +137,14 @@ export const CreatePostBuilder = ({ const { currentBlog } = useSelector((state: RootState) => state.global); const [editingSection, setEditingSection] = React.useState(null); const [layouts, setLayouts] = React.useState({ md, sm, xs }); + // Normalize incoming layouts for Builder editor + const toBuilderLayouts = (incoming: any) => { + if (incoming && typeof incoming === 'object' && incoming.md && incoming.sm && incoming.xs) { + return incoming; + } + return { md, sm, xs }; + }; + const [currentBreakpoint, setCurrentBreakpoint] = React.useState(); const handleLayoutChange = (layout: any, layoutss: any) => { setLayouts(layoutss); @@ -214,17 +234,13 @@ export const CreatePostBuilder = ({ } useEffect(() => { - if (blogContentForEdit && postIdForEdit && blogMetadataForEdit) { + if (blogContentForEdit && postIdForEdit) { setTitle(blogContentForEdit?.title || ''); - setLayouts( - blogContentForEdit?.layouts || { - rows: [], - }, - ); + setLayouts(toBuilderLayouts(blogContentForEdit?.layouts)); setNewPostContent(blogContentForEdit?.postContent || []); - onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding || 5); + onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding ?? 5); } - }, [blogContentForEdit, postIdForEdit, blogMetadataForEdit]); + }, [blogContentForEdit, postIdForEdit]); const editBlog = React.useCallback( async (navbarConfig: any) => { @@ -409,10 +425,6 @@ export const CreatePostBuilder = ({ errorMsg = 'Your post has no content'; } - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.'; - } - if (errorMsg) { dispatch( setNotification({ @@ -458,7 +470,10 @@ export const CreatePostBuilder = ({ createTitleId = createTitleId.slice(1); } createTitleId = createTitleId.slice(0, 24); - const identifier = `${currentBlog.blogId}-post-${createTitleId}-${id}`; + const blogIdForPublish = computeBlogIdForPublish(currentBlog, blogMetadataForEdit); + const identifier = postIdForEdit + ? `${blogIdForPublish ?? ''}-post-${postIdForEdit}` + : `${blogIdForPublish ?? currentBlog?.blogId ?? ''}-post-${createTitleId}-${id}`; const blogPostToBase64 = await objectToBase64(postObject); let description = ''; const findText = newPostContent.find((data) => data?.type === 'editor'); @@ -571,7 +586,7 @@ export const CreatePostBuilder = ({ } async function updateQDNResource(params: any) { - if (!blogContentForEdit || !postIdForEdit || !blogMetadataForEdit) return; + if (!blogContentForEdit || !postIdForEdit) return; let address; let name; let errorMsg = ''; @@ -596,10 +611,6 @@ export const CreatePostBuilder = ({ errorMsg = 'Your post has no content'; } - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.'; - } - if (errorMsg) { dispatch( setNotification({ @@ -623,8 +634,6 @@ export const CreatePostBuilder = ({ layoutGeneralSettings, }; try { - if (!currentBlog) return; - const identifier = postIdForEdit; const blogPostToBase64 = await objectToBase64(postObject); let description = ''; @@ -1408,7 +1417,7 @@ export const CreatePostBuilder = ({ /> )} - {blogContentForEdit && blogMetadataForEdit?.metadata && ( + {blogContentForEdit && ( { setIsOpenPostModal(false); diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index fd18bd2..86b78b1 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -1,3 +1,15 @@ +// Helper: derive blogId for publish (supports edit without currentBlog) +const computeBlogIdForPublish = (currentBlog: any, blogMetadataForEdit?: any): string | null => { + if ( + blogMetadataForEdit && + typeof blogMetadataForEdit.blogId === 'string' && + blogMetadataForEdit.blogId + ) { + return blogMetadataForEdit.blogId; + } + if (currentBlog && typeof currentBlog.blogId === 'string') return currentBlog.blogId; + return null; +}; import React, { useEffect } from 'react'; import BlogEditor from '../../components/editor/BlogEditor'; import ShortUniqueId from 'short-unique-id'; @@ -125,6 +137,14 @@ export const CreatePostMinimal = ({ const [layouts, setLayouts] = React.useState({ rows: [], }); + // Normalize incoming layouts for Minimal editor + const toMinimalLayouts = (incoming: any) => { + if (incoming && typeof incoming === 'object' && Array.isArray(incoming.rows)) { + return incoming; + } + return { rows: [] }; + }; + const [currentBreakpoint, setCurrentBreakpoint] = React.useState(); const [newPostContent, setNewPostContent] = React.useState([]); @@ -214,7 +234,7 @@ export const CreatePostMinimal = ({ }, []); useEffect(() => { - if (blogContentForEdit && postIdForEdit && blogMetadataForEdit) { + if (blogContentForEdit && postIdForEdit) { setTitle(blogContentForEdit?.title || ''); setLayouts( blogContentForEdit?.layouts || { @@ -222,9 +242,9 @@ export const CreatePostMinimal = ({ }, ); setNewPostContent(blogContentForEdit?.postContent || []); - onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding || 5); + onChangePadding(blogContentForEdit?.layoutGeneralSettings?.padding ?? 5); } - }, [blogContentForEdit, postIdForEdit, blogMetadataForEdit]); + }, [blogContentForEdit, postIdForEdit]); function objectToBase64(obj: any) { // Step 1: Convert the object to a JSON string @@ -293,10 +313,6 @@ export const CreatePostMinimal = ({ errorMsg = 'Your post has no content'; } - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.'; - } - if (errorMsg) { dispatch( setNotification({ @@ -342,7 +358,10 @@ export const CreatePostMinimal = ({ createTitleId = createTitleId.slice(1); } createTitleId = createTitleId.slice(0, 24); - const identifier = `${currentBlog.blogId}-post-${createTitleId}-${id}`; + const blogIdForPublish = computeBlogIdForPublish(currentBlog, blogMetadataForEdit); + const identifier = postIdForEdit + ? `${blogIdForPublish ?? ''}-post-${postIdForEdit}` + : `${blogIdForPublish ?? currentBlog?.blogId ?? ''}-post-${createTitleId}-${id}`; const blogPostToBase64 = await objectToBase64(postObject); let description = ''; const findText = newPostContent.find((data) => data?.type === 'editor'); @@ -454,7 +473,7 @@ export const CreatePostMinimal = ({ } } async function updateQDNResource(params: any) { - if (!blogContentForEdit || !postIdForEdit || !blogMetadataForEdit) return; + if (!blogContentForEdit || !postIdForEdit) return; let address; let name; let errorMsg = ''; @@ -479,10 +498,6 @@ export const CreatePostMinimal = ({ errorMsg = 'Your post has no content'; } - if (!currentBlog) { - errorMsg = 'Cannot publish without first creating a blog.'; - } - if (errorMsg) { dispatch( setNotification({ @@ -506,8 +521,6 @@ export const CreatePostMinimal = ({ layoutGeneralSettings, }; try { - if (!currentBlog) return; - const identifier = postIdForEdit; const blogPostToBase64 = await objectToBase64(postObject); let description = ''; @@ -1365,6 +1378,7 @@ export const CreatePostMinimal = ({ > + {/* View toggle moved into Main Menu */} = ({ alignItems: 'center', }} > - {!isAuthenticated && ( - - {authStatus === 'loading' ? ( - - ) : ( - - )} - {authStatus === 'loading' ? 'Authenticating…' : 'Authenticate'} - - )} + {/* Authenticate button moved to the right of Menu for layout stability */} = ({ - {isAuthenticated && - userName && - hasAttemptedToFetchBlogInitial && - (userBlogs?.length || 0) === 0 && ( - dispatch(togglePublishBlogModal(true))}> - - Create Blog - - )} + {/* Main Menu button */} + } + aria-haspopup="menu" + aria-controls={idMain} + aria-expanded={openMain ? 'true' : undefined} + onClick={(e: any) => setAnchorElMain(e.currentTarget as HTMLButtonElement)} + sx={{ mr: 1 }} + > + Menu + - {isAuthenticated && userName && (userBlogs?.length || 0) >= 1 && ( - <> - } - onClick={() => { - navigate(`/post/new`); - }} - > - Create Post - - - } - aria-haspopup="menu" - aria-controls={idBlogs} - aria-expanded={openBlogs ? 'true' : undefined} - onClick={(e: any) => { - const target = e.currentTarget as HTMLButtonElement; - setAnchorElBlogs(target); - }} - > - My Blogs - - - - - - {(userBlogs || []).map((b) => { - const isActive = blog?.blogId === b.blogId; - return ( - { - onSelectBlog && onSelectBlog(b); - closeBlogs(); - }} - > - - - ); - })} - - - - - - - - + {!isAuthenticated && ( + + {authStatus === 'loading' ? ( + + ) : ( + + )} + {authStatus === 'loading' ? 'Authenticating…' : 'Authenticate'} + )} {isAuthenticated && userName && ( @@ -460,6 +384,95 @@ const NavBar: React.FC = ({ )} + + + {(isAuthenticated && userName && (userBlogs?.length || 0) === 0) && ( + { dispatch(togglePublishBlogModal(true)); closeMain(); }}> + + Create Blog + + )} + {(isAuthenticated && userName && (userBlogs?.length || 0) >= 1) && ( + <> + { navigate('/post/new'); closeMain(); }}> + + Create Post + + setMyBlogsOpen((v) => !v)} + aria-expanded={myBlogsOpen ? 'true' : 'false'} + aria-controls="menu-myblogs" + role="button" + > + + My Blogs + + + + {(userBlogs || []).map((b) => { + const isActive = blog?.blogId === b.blogId; + return ( + { onSelectBlog && onSelectBlog(b); closeMain(); }} + > + + + ); + })} + + + + { dispatch(togglePublishBlogModal(true)); closeMain(); }}> + + Create new blog + + + )} + + + + { toggleViewMode(); closeMain(); }}> + {viewMode === 'tile' ? 'Switch to List View' : 'Switch to Tile View'} + + + { navigate('/subscriptions'); closeMain(); }}> + + Subscriptions + + + { navigate('/bookmarks'); closeMain(); }}> + + Bookmarks + + + { setIsOpenModal(true); closeMain(); }}> + + Blocked Names + + + + + + Q-Mail + + + + + = ({ )} - - - navigate('/favorites')}> - - Favorites - - - navigate('/subscriptions')}> - - Subscriptions - - - { - setIsOpenModal(true); - handleClose(); - }} - > - - Blocked Names - - - - - - Q-Mail - - + {/* Name menu only shows name switching now */} diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index d3f6105..0c5263c 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -10,6 +10,8 @@ import { RootState } from '../../state/store'; import { checkStructure } from '../../utils/checkStructure'; import { BlogContent } from '../../interfaces/interfaces'; import ShareIcon from '@mui/icons-material/Share'; +import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; +import BookmarkIcon from '@mui/icons-material/Bookmark'; import { setAudio, setCurrAudio, @@ -31,6 +33,7 @@ import { DynamicHeightItemMinimal } from '../../components/DynamicHeightItemMini import { ReusableModal } from '../../components/modals/ReusableModal'; import AudioElement from '../../components/AudioElement'; import ErrorBoundary from '../../components/common/ErrorBoundary'; +import ImageLightbox from '../../components/common/ImageLightbox'; import { CommentSection } from '../../components/common/Comments/CommentSection'; import { canEdit, BlogSettings, selectCanonical } from '../../utils/wiki'; import { Tipping } from '../../components/common/Tipping/Tipping'; @@ -42,6 +45,7 @@ import { setNotification } from '../../state/features/notificationsSlice'; import ContextMenuResource from '../../components/common/ContextMenu/ContextMenuResource'; import { PollWidget } from '../../components/common/PollWidget'; import { formatDate } from '../../utils/time'; +import { addBookmark, removeBookmark } from '../../state/features/bookmarksSlice'; const ResponsiveGridLayout = WidthProvider(Responsive); const initialMinHeight = 2; // Define an initial minimum height for grid items @@ -94,6 +98,7 @@ export const BlogIndividualPost = () => { return addPrefix(blog); }, [blog]); const { user: userState } = useSelector((state: RootState) => state.auth); + const bookmarksLocal = useSelector((state: RootState) => state.bookmarks.bookmarksLocal); const { audios, audioPostId, visitingBlog } = useSelector((state: RootState) => state.global); const [avatarUrl, setAvatarUrl] = React.useState(''); @@ -118,6 +123,13 @@ export const BlogIndividualPost = () => { const [isOpenSwitchPlaylistModal, setisOpenSwitchPlaylistModal] = useState(false); const tempSaveAudio = useRef(null); const saveAudio = React.useRef(null); + const [lightboxSrc, setLightboxSrc] = useState(null); + const [lightboxOpen, setLightboxOpen] = useState(false); + const openLightbox = (src: string) => { + setLightboxSrc(src); + setLightboxOpen(true); + }; + const closeLightbox = () => setLightboxOpen(false); const fullPostId = useMemo(() => { if (!blog || !postId) return ''; @@ -460,6 +472,46 @@ export const BlogIndividualPost = () => { + {(() => { + const username = userState?.name || null; + const href = `/${user}/${blog}/${postId}`; + const isBookmarked = bookmarksLocal.some((b) => b.href === href); + return isBookmarked ? ( + + + + dispatch( + removeBookmark({ username, href }), + ) + } + /> + + + ) : ( + + + + dispatch( + addBookmark({ + username, + item: { + href, + title: blogContent?.title || href, + created: Date.now(), + type: 'post', + }, + }), + ) + } + /> + + + ); + })()} @@ -507,7 +559,13 @@ export const BlogIndividualPost = () => { count={count} padding={layoutGeneralSettings?.padding} > - + {blogContent?.title openLightbox(section.content.image)} + style={{ cursor: 'zoom-in' }} + /> @@ -748,10 +806,13 @@ export const BlogIndividualPost = () => { {blogContent?.title openLightbox(section.content.image)} /> @@ -950,6 +1011,7 @@ export const BlogIndividualPost = () => { + ); }; diff --git a/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx b/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx index 3138d34..d39134d 100644 --- a/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx +++ b/src/pages/BlogIndividualProfile/BlogIndividualProfile.tsx @@ -13,6 +13,10 @@ import { togglePublishBlogModal, } from '../../state/features/globalSlice'; import { addSubscription, BlogPost, removeSubscription } from '../../state/features/blogSlice'; +import { addBookmark, removeBookmark } from '../../state/features/bookmarksSlice'; +import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; +import BookmarkIcon from '@mui/icons-material/Bookmark'; +import { Tooltip, IconButton } from '@mui/material'; import { useFetchPosts } from '../../hooks/useFetchPosts'; import LazyLoad from '../../components/common/LazyLoad'; import { addPrefix, removePrefix } from '../../utils/blogIdformats'; @@ -34,6 +38,7 @@ export const BlogIndividualProfile = () => { const { user } = useSelector((state: RootState) => state.auth); const { currentBlog, visitingBlog } = useSelector((state: RootState) => state.global); const subscriptions = useSelector((state: RootState) => state.blog.subscriptions); + const bookmarksLocal = useSelector((state: RootState) => state.bookmarks.bookmarksLocal); const { blog: blogShortVersion, user: username } = useParams(); const blog = React.useMemo(() => { @@ -347,6 +352,41 @@ export const BlogIndividualProfile = () => { Subscribe )} + {(() => { + const viewer = user?.name || null; + const shortBlog = blogShortVersion || ''; + const href = `/${username}/${shortBlog}`; + const title = currentBlog?.blogId === blog ? currentBlog?.title : userBlog.title; + const isBookmarked = bookmarksLocal.some((b) => b.href === href); + return isBookmarked ? ( + + dispatch(removeBookmark({ username: viewer, href }))} + size="small" + > + + + + ) : ( + + + dispatch( + addBookmark({ + username: viewer, + item: { href, title, created: Date.now(), type: 'blog' }, + }), + ) + } + size="small" + > + + + + ); + })()} {isListView ? ( @@ -382,6 +422,7 @@ export const BlogIndividualProfile = () => { > navigate(`/${blogPost.user}/${blogId}/${str2}`)} + postHref={`/${blogPost.user}/${blogId}/${str2}`} description={blogPost?.description} title={blogPost?.title} createdAt={blogPost?.createdAt} @@ -466,6 +507,7 @@ export const BlogIndividualProfile = () => { onClick={() => { navigate(`/${blogPost.user}/${blogId}/${str2}`); }} + postHref={`/${blogPost.user}/${blogId}/${str2}`} description={blogPost?.description} title={blogPost?.title} createdAt={blogPost?.createdAt} diff --git a/src/pages/BlogList/BlogList.tsx b/src/pages/BlogList/BlogList.tsx index dd06d48..9f63bb7 100644 --- a/src/pages/BlogList/BlogList.tsx +++ b/src/pages/BlogList/BlogList.tsx @@ -227,6 +227,9 @@ export const BlogList = ({ mode }: BlogListProps) => { }/${blogId}/${str2}`, ) } + postHref={`/${ + (blogSettingsMap.get(str1) as any)?.ownerName || blogPost.user + }/${blogId}/${str2}`} description={blogPost?.description} title={blogPost?.title} createdAt={blogPost?.createdAt} @@ -299,6 +302,9 @@ export const BlogList = ({ mode }: BlogListProps) => { }/${blogId}/${str2}`, ); }} + postHref={`/${ + (blogSettingsMap.get(str1) as any)?.ownerName || blogPost.user + }/${blogId}/${str2}`} description={blogPost?.description} title={blogPost?.title} createdAt={blogPost?.createdAt} diff --git a/src/pages/BlogList/PostPreview.tsx b/src/pages/BlogList/PostPreview.tsx index e9b391c..9ce2a4d 100644 --- a/src/pages/BlogList/PostPreview.tsx +++ b/src/pages/BlogList/PostPreview.tsx @@ -31,13 +31,7 @@ import { BookmarkIconContainer, } from './PostPreview-styles'; import moment from 'moment'; -import { - blockUser, - BlogPost, - removeFavorites, - removeSubscription, - upsertFavorites, -} from '../../state/features/blogSlice'; +import { blockUser, BlogPost, removeSubscription } from '../../state/features/blogSlice'; import { useDispatch, useSelector } from 'react-redux'; import BookmarkBorderIcon from '@mui/icons-material/BookmarkBorder'; import BookmarkIcon from '@mui/icons-material/Bookmark'; @@ -47,6 +41,8 @@ import { CustomIcon } from '../../components/common/CustomIcon'; import ResponsiveImage from '../../components/common/ResponsiveImage'; import { formatDate } from '../../utils/time'; import { useNavigate } from 'react-router-dom'; +import { addBookmark, removeBookmark } from '../../state/features/bookmarksSlice'; +import { removePrefix } from '../../utils/blogIdformats'; interface BlogPostPreviewProps { title: string; createdAt: number | string; @@ -60,6 +56,7 @@ interface BlogPostPreviewProps { isValid?: boolean; tags?: string[]; fullWidth?: boolean; + postHref?: string; // canonical href used for navigation/bookmarks } const BlogPostPreview: React.FC = ({ @@ -75,6 +72,7 @@ const BlogPostPreview: React.FC = ({ isValid, tags, fullWidth = false, + postHref, }) => { const [avatarUrl, setAvatarUrl] = React.useState(''); const [showIcons, setShowIcons] = React.useState(false); @@ -82,7 +80,7 @@ const BlogPostPreview: React.FC = ({ const dispatch = useDispatch(); const theme = useTheme(); const navigate = useNavigate(); - const favoritesLocal = useSelector((state: RootState) => state.blog.favoritesLocal); + const bookmarksLocal = useSelector((state: RootState) => state.bookmarks.bookmarksLocal); const [isOpenAlert, setIsOpenAlert] = useState(false); const subscriptions = useSelector((state: RootState) => state.blog.subscriptions); const username = useSelector((state: RootState) => state.auth?.user?.name); @@ -104,10 +102,21 @@ const BlogPostPreview: React.FC = ({ getAvatar(); }, []); - const isFavorite = useMemo(() => { - if (!favoritesLocal) return false; - return favoritesLocal.find((fav) => fav?.id === blogPost?.id); - }, [favoritesLocal, blogPost?.id]); + const isBookmarked = useMemo(() => { + try { + if (postHref) { + return bookmarksLocal.some((b) => b.href === postHref); + } + const id = blogPost?.id || ''; + if (!id.includes('-post-')) return false; + const [blogFull, postId] = id.split('-post-'); + const blogShort = removePrefix(blogFull); + const href = `/${blogPost.user}/${blogShort}/${postId}`; + return bookmarksLocal.some((b) => b.href === href); + } catch { + return false; + } + }, [bookmarksLocal, blogPost?.id, blogPost?.user, postHref]); const blockUserFunc = async (user: string) => { if (user === 'Q-Blog' || user === 'qblog') return; @@ -135,7 +144,6 @@ const BlogPostPreview: React.FC = ({ if (response === true) { dispatch(blockUser(user)); - dispatch(removeFavorites(blogPost.id)); } } catch (error) {} }; @@ -322,32 +330,49 @@ const BlogPostPreview: React.FC = ({ onMouseEnter={() => setShowIcons(true)} onMouseLeave={() => setShowIcons(false)} > - {username && isFavorite && ( - + {username && isBookmarked && ( + setShowIcons(true)} onMouseLeave={() => setShowIcons(false)} > { - dispatch(removeFavorites(blogPost.id)); + let href = postHref; + if (!href) { + const [blogFull, postId] = (blogPost.id || '').split('-post-'); + const blogShort = removePrefix(blogFull); + href = `/${blogPost.user}/${blogShort}/${postId}`; + } + dispatch(removeBookmark({ username, href: href! })); }} /> )} - {username && !isFavorite && ( - + {username && !isBookmarked && ( + setShowIcons(true)} onMouseLeave={() => setShowIcons(false)} > { - dispatch(upsertFavorites([blogPost])); + let href = postHref; + if (!href) { + const [blogFull, postId] = (blogPost.id || '').split('-post-'); + const blogShort = removePrefix(blogFull); + href = `/${blogPost.user}/${blogShort}/${postId}`; + } + dispatch( + addBookmark({ + username, + item: { href: href!, title: blogPost.title || href!, created: Date.now(), type: 'post' }, + }), + ); }} /> diff --git a/src/pages/Bookmarks/Bookmarks.tsx b/src/pages/Bookmarks/Bookmarks.tsx new file mode 100644 index 0000000..827f4b5 --- /dev/null +++ b/src/pages/Bookmarks/Bookmarks.tsx @@ -0,0 +1,276 @@ +import React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + Box, + Button, + Typography, + useTheme, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + IconButton, + Tooltip, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Input, + CircularProgress, +} from '@mui/material'; +import { RootState } from '../../state/store'; +import { loadBookmarksForUser, removeBookmark, setBookmarkFolder } from '../../state/features/bookmarksSlice'; +import { useNavigate } from 'react-router-dom'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder'; +import { addPrefix } from '../../utils/blogIdformats'; +import { formatDate } from '../../utils/time'; + +const BookmarksPage: React.FC = () => { + const theme = useTheme(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const username = useSelector((s: RootState) => s.auth.user?.name || null); + const bookmarks = useSelector((s: RootState) => s.bookmarks.bookmarksLocal); + + React.useEffect(() => { + dispatch(loadBookmarksForUser({ username })); + }, [dispatch, username]); + + const [confirmHref, setConfirmHref] = React.useState(null); + const [editFolderHref, setEditFolderHref] = React.useState(null); + const [folderValue, setFolderValue] = React.useState(''); + + type RowMeta = { updated?: number; created?: number }; + const [meta, setMeta] = React.useState>({}); + const [sortBy, setSortBy] = React.useState< + 'folder' | 'service' | 'name' | 'identifier' | 'title' | 'updated' + >('updated'); + const [sortDir, setSortDir] = React.useState<'asc' | 'desc'>('desc'); + + const parse = (href: string) => { + // href formats: /:user/:blog or /:user/:blog/:postId + const parts = href.split('/').filter(Boolean); + if (parts.length < 2) return { service: '', name: '', identifier: '', qortal: '' }; + const [name, blogShort, maybePostId] = parts; + const blogFull = addPrefix(blogShort); + if (maybePostId) { + const identifier = `${blogFull}-post-${maybePostId}`; + return { + service: 'BLOG_POST', + name, + identifier, + qortal: `qortal://APP/qblog/${name}/${blogShort}/${maybePostId}`, + }; + } + return { + service: 'BLOG', + name, + identifier: blogFull, + qortal: `qortal://APP/qblog/${name}/${blogShort}`, + }; + }; + + // Fetch metadata (updated/created) for rows + React.useEffect(() => { + let cancelled = false; + (async () => { + for (const b of bookmarks) { + if (meta[b.href]) continue; + const p = parse(b.href); + if (!p.service || !p.name || !p.identifier) continue; + try { + const url = `/arbitrary/resources?service=${encodeURIComponent( + p.service, + )}&name=${encodeURIComponent(p.name)}&identifier=${encodeURIComponent( + p.identifier, + )}&includemetadata=true`; + const res = await fetch(url); + const data = await res.json(); + const item = Array.isArray(data) && data.length > 0 ? data[0] : null; + const row: RowMeta = { + updated: item?.updated, + created: item?.created, + }; + if (!cancelled) setMeta((m) => ({ ...m, [b.href]: row })); + } catch { + // ignore + } + } + })(); + return () => { + cancelled = true; + }; + }, [bookmarks]); + + const sorted = React.useMemo(() => { + const rows = [...bookmarks]; + const dir = sortDir === 'asc' ? 1 : -1; + const getVal = (b: typeof bookmarks[number]) => { + const p = parse(b.href); + switch (sortBy) { + case 'folder': + return (b.folder || '').toLowerCase(); + case 'service': + return p.service; + case 'name': + return p.name.toLowerCase(); + case 'identifier': + return p.identifier.toLowerCase(); + case 'title': + return (b.title || '').toLowerCase(); + case 'updated': { + const m = meta[b.href]; + const ts = m?.updated || m?.created || 0; + return ts; + } + } + }; + rows.sort((a, b) => { + const av = getVal(a); + const bv = getVal(b); + if (av < bv) return -1 * dir; + if (av > bv) return 1 * dir; + return 0; + }); + return rows; + }, [bookmarks, sortBy, sortDir, meta]); + + const toggleSort = (field: typeof sortBy) => { + setSortBy((prev) => { + if (prev === field) { + setSortDir((d) => (d === 'asc' ? 'desc' : 'asc')); + return prev; + } + setSortDir('asc'); + return field; + }); + }; + + return ( + + + Bookmarks + + {bookmarks.length === 0 ? ( + No bookmarks saved yet. + ) : ( + + + + toggleSort('folder')} sx={{ cursor: 'pointer' }}>Folder {sortBy==='folder' ? (sortDir==='asc'?'▲':'▼') : ''} + toggleSort('service')} sx={{ cursor: 'pointer' }}>Service {sortBy==='service' ? (sortDir==='asc'?'▲':'▼') : ''} + toggleSort('name')} sx={{ cursor: 'pointer' }}>Name {sortBy==='name' ? (sortDir==='asc'?'▲':'▼') : ''} + toggleSort('identifier')} sx={{ cursor: 'pointer' }}>Identifier {sortBy==='identifier' ? (sortDir==='asc'?'▲':'▼') : ''} + toggleSort('title')} sx={{ cursor: 'pointer' }}>Title {sortBy==='title' ? (sortDir==='asc'?'▲':'▼') : ''} + Actions + toggleSort('updated')} sx={{ cursor: 'pointer' }} align="right">Updated {sortBy==='updated' ? (sortDir==='asc'?'▲':'▼') : ''} + + + + {sorted.map((b) => { + const p = parse(b.href); + const m = meta[b.href]; + const ts = m?.updated || m?.created || 0; + return ( + + {b.folder || ''} + {p.service} + {p.name} + {p.identifier} + {b.title} + + + { setEditFolderHref(b.href); setFolderValue(b.folder || ''); }}> + + + + + navigate(b.href)} size="small"> + + + + + + + + + + + + setConfirmHref(b.href)} + > + + + + + {ts ? formatDate(ts) : } + + ); + })} + +
+ )} + + setConfirmHref(null)}> + Remove bookmark? + + + This will remove the bookmark. This action cannot be undone. + + + + + + + + + setEditFolderHref(null)}> + Set bookmark folder + + Assign a folder name for organizing this bookmark. + + setFolderValue(e.target.value)} + placeholder="Folder name (e.g., Research)" + /> + + + + + + + +
+ ); +}; + +export default BookmarksPage; diff --git a/src/pages/CreatePost/CreatePost.tsx b/src/pages/CreatePost/CreatePost.tsx index 17e6d62..2a88496 100644 --- a/src/pages/CreatePost/CreatePost.tsx +++ b/src/pages/CreatePost/CreatePost.tsx @@ -26,8 +26,13 @@ export const CreatePost = ({ mode }: CreatePostProps) => { const dispatch = useDispatch(); const navigate = useNavigate(); - // IMPORTANT: in edit mode, :postId is already the full identifier (q-blog-*-post-*) - const fullPostId = useMemo(() => (mode === 'edit' && postId ? postId : ''), [postId, mode]); + // In edit mode, build the full identifier from :blog and :postId + const fullPostId = useMemo(() => { + if (mode !== 'edit') return ''; + if (!blog || !postId) return ''; + const blogFull = addPrefix(blog); + return `${blogFull}-post-${postId}`; + }, [postId, mode, blog]); const [toggleEditorType, setToggleEditorType] = useState(null); const [blogContentForEdit, setBlogContentForEdit] = useState(null); diff --git a/src/pages/CreatePost/CreatePostBuilder.tsx b/src/pages/CreatePost/CreatePostBuilder.tsx index 7b97749..3781c89 100644 --- a/src/pages/CreatePost/CreatePostBuilder.tsx +++ b/src/pages/CreatePost/CreatePostBuilder.tsx @@ -421,9 +421,7 @@ export const CreatePostBuilder = ({ const errMsg = `Missing: ${missingFieldsString}`; errorMsg = errMsg; } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content'; - } + // In edit mode, allow updating even if content array is empty (legacy/minimal posts) if (errorMsg) { dispatch( @@ -607,9 +605,7 @@ export const CreatePostBuilder = ({ const errMsg = `Missing: ${missingFieldsString}`; errorMsg = errMsg; } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content'; - } + // In edit mode, allow updating even if content array is empty (legacy/minimal posts) if (errorMsg) { dispatch( diff --git a/src/pages/CreatePost/CreatePostMinimal.tsx b/src/pages/CreatePost/CreatePostMinimal.tsx index 86b78b1..6430a7a 100644 --- a/src/pages/CreatePost/CreatePostMinimal.tsx +++ b/src/pages/CreatePost/CreatePostMinimal.tsx @@ -309,9 +309,7 @@ export const CreatePostMinimal = ({ const errMsg = `Missing: ${missingFieldsString}`; errorMsg = errMsg; } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content'; - } + // In edit mode, allow updating even if content array is empty (legacy/minimal posts) if (errorMsg) { dispatch( @@ -494,9 +492,7 @@ export const CreatePostMinimal = ({ const errMsg = `Missing: ${missingFieldsString}`; errorMsg = errMsg; } - if (newPostContent.length === 0) { - errorMsg = 'Your post has no content'; - } + // In edit mode, allow updating even if content array is empty (legacy/minimal posts) if (errorMsg) { dispatch( diff --git a/src/state/features/bookmarksSlice.ts b/src/state/features/bookmarksSlice.ts new file mode 100644 index 0000000..b446e81 --- /dev/null +++ b/src/state/features/bookmarksSlice.ts @@ -0,0 +1,77 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +export type BookmarkType = 'post' | 'blog' | 'link'; +export interface BookmarkItem { + href: string; // router path like /:user/:blog or /:user/:blog/:postId + title: string; + created: number; + type: BookmarkType; + folder?: string; // optional grouping +} + +interface BookmarksState { + bookmarksLocal: BookmarkItem[]; +} + +const initialState: BookmarksState = { + bookmarksLocal: [], +}; + +const storageKey = (username?: string | null) => + `q-blog-bookmarks-${username && username.length ? username : 'guest'}`; + +const bookmarksSlice = createSlice({ + name: 'bookmarks', + initialState, + reducers: { + loadBookmarksForUser: (state, action: PayloadAction<{ username?: string | null }>) => { + try { + const key = storageKey(action.payload.username); + const raw = localStorage.getItem(key); + const arr: BookmarkItem[] = raw ? JSON.parse(raw) : []; + state.bookmarksLocal = Array.isArray(arr) ? arr : []; + } catch { + state.bookmarksLocal = []; + } + }, + addBookmark: ( + state, + action: PayloadAction<{ username?: string | null; item: BookmarkItem }>, + ) => { + const { username, item } = action.payload; + const exists = state.bookmarksLocal.some((b) => b.href === item.href); + const next = exists + ? state.bookmarksLocal.map((b) => (b.href === item.href ? item : b)) + : [...state.bookmarksLocal, item]; + state.bookmarksLocal = next; + try { + localStorage.setItem(storageKey(username), JSON.stringify(next)); + } catch {} + }, + setBookmarkFolder: ( + state, + action: PayloadAction<{ username?: string | null; href: string; folder?: string }>, + ) => { + const { username, href, folder } = action.payload; + const next = state.bookmarksLocal.map((b) => (b.href === href ? { ...b, folder } : b)); + state.bookmarksLocal = next; + try { + localStorage.setItem(storageKey(username), JSON.stringify(next)); + } catch {} + }, + removeBookmark: ( + state, + action: PayloadAction<{ username?: string | null; href: string }>, + ) => { + const { username, href } = action.payload; + const next = state.bookmarksLocal.filter((b) => b.href !== href); + state.bookmarksLocal = next; + try { + localStorage.setItem(storageKey(username), JSON.stringify(next)); + } catch {} + }, + }, +}); + +export const { loadBookmarksForUser, addBookmark, removeBookmark, setBookmarkFolder } = bookmarksSlice.actions; +export default bookmarksSlice.reducer; diff --git a/src/state/store.ts b/src/state/store.ts index b77c914..eccbfcd 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -5,6 +5,7 @@ import authReducer from './features/authSlice'; import globalReducer from './features/globalSlice'; import blogReducer from './features/blogSlice'; import mailReducer from './features/mailSlice'; +import bookmarksReducer from './features/bookmarksSlice'; export const store = configureStore({ reducer: { @@ -14,6 +15,7 @@ export const store = configureStore({ global: globalReducer, blog: blogReducer, mail: mailReducer, + bookmarks: bookmarksReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ diff --git a/src/utils/migrateFavoritesToBookmarks.ts b/src/utils/migrateFavoritesToBookmarks.ts new file mode 100644 index 0000000..206f761 --- /dev/null +++ b/src/utils/migrateFavoritesToBookmarks.ts @@ -0,0 +1,76 @@ +import localforage from 'localforage'; +import { addBookmark, BookmarkItem } from '../state/features/bookmarksSlice'; +import { removePrefix } from '../utils/blogIdformats'; + +const FAVORITES_GLOBAL = 'q-blog-favorites'; +const FAVORITES_PREFIX = 'q-blog-favorites-'; + +interface FavoriteStoredItem { + user: string; + id: string; // identifier like q-blog--post- +} + +function buildHref(item: FavoriteStoredItem): string | null { + try { + const id = item.id; + if (!id || !id.includes('-post-')) return null; + const [blogFull, postId] = id.split('-post-'); + const blogShort = removePrefix(blogFull); + if (!blogShort || !postId || !item.user) return null; + return `/${encodeURIComponent(item.user)}/${encodeURIComponent(blogShort)}/${encodeURIComponent(postId)}`; + } catch { + return null; + } +} + +async function importFromInstance( + instanceName: string, + username: string | null | undefined, + dispatch: (action: any) => void, +): Promise { + const inst = localforage.createInstance({ name: instanceName }); + let count = 0; + try { + await inst.iterate((value, key, iterationNumber) => { + if (!value || !value.id || !value.user) return; + const href = buildHref(value); + if (!href) return; + const item: BookmarkItem = { + href, + title: value.id, + created: Date.now(), + type: 'post', + }; + dispatch(addBookmark({ username, item })); + count += 1; + }); + // Clear after import + await inst.clear(); + } catch { + // ignore errors, treat as zero imported + } + return count; +} + +export async function migrateFavoritesToBookmarks( + username: string | null | undefined, + dispatch: (action: any) => void, +): Promise { + const flagKey = `qblog_fav_migrated_${username || 'guest'}`; + try { + if (localStorage.getItem(flagKey) === '1') return 0; + } catch {} + + let total = 0; + // Import from both legacy buckets + total += await importFromInstance(FAVORITES_GLOBAL, username ?? null, dispatch); + if (username) total += await importFromInstance(`${FAVORITES_PREFIX}${username}`, username, dispatch); + + try { + localStorage.setItem(flagKey, '1'); + } catch {} + return total; +} + +export default migrateFavoritesToBookmarks; + diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index ba5a499..f7fdc3c 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -8,6 +8,9 @@ import { RootState } from '../state/store'; import PublishBlogModal from '../components/modals/PublishBlogModal'; import EditBlogModal from '../components/modals/EditBlogModal'; import NavBar from '../components/layout/Navbar/Navbar'; +import ScrollToTop from '../components/common/ScrollToTop'; +import { loadBookmarksForUser } from '../state/features/bookmarksSlice'; +import migrateFavoritesToBookmarks from '../utils/migrateFavoritesToBookmarks'; import { setCurrentBlog, @@ -64,6 +67,18 @@ const GlobalWrapper: React.FC = ({ children }) => { })(); }, [fullNotifications]); + // Load bookmarks and migrate legacy favorites into bookmarks on user change + useEffect(() => { + (async () => { + const username = user?.name || null; + dispatch(loadBookmarksForUser({ username })); + try { + await migrateFavoritesToBookmarks(username, dispatch as any); + dispatch(loadBookmarksForUser({ username })); + } catch {} + })(); + }, [user?.name, dispatch]); + const askForAccountInformation = useCallback(async () => { if (authStatus === 'loading') return; setAuthStatus('loading'); @@ -187,6 +202,7 @@ const GlobalWrapper: React.FC = ({ children }) => { /> {children} + ); }; diff --git a/tests/components/Navbar.multiblog.test.tsx b/tests/components/Navbar.multiblog.test.tsx index 03a247d..7060fda 100644 --- a/tests/components/Navbar.multiblog.test.tsx +++ b/tests/components/Navbar.multiblog.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { describe, it, expect, vi } from 'vitest'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import { MemoryRouter } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import NavBar from '@/components/layout/Navbar/Navbar'; import { Provider } from 'react-redux'; import { store } from '@/state/store'; @@ -20,7 +20,7 @@ const baseProps = { }; describe('Navbar multiblog', () => { - it('shows Create Blog when user has 0 blogs', () => { + it('shows Create Blog in main menu when user has 0 blogs', () => { render( @@ -30,10 +30,11 @@ describe('Navbar multiblog', () => { , ); + fireEvent.click(screen.getByText('Menu')); expect(screen.getByText('Create Blog')).toBeInTheDocument(); }); - it('shows My Blogs dropdown when user has >=1 blogs', () => { + it('shows My Blogs section in main menu when user has >=1 blogs', () => { render( @@ -46,6 +47,7 @@ describe('Navbar multiblog', () => { , ); + fireEvent.click(screen.getByText('Menu')); expect(screen.getByText('My Blogs')).toBeInTheDocument(); }); }); diff --git a/tests/components/ScrollToTop.test.tsx b/tests/components/ScrollToTop.test.tsx new file mode 100644 index 0000000..fd1ca73 --- /dev/null +++ b/tests/components/ScrollToTop.test.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter } from 'react-router-dom'; +import GlobalWrapper from '@/wrappers/GlobalWrapper'; +import { render, screen } from '@testing-library/react'; + +describe('ScrollToTop button', () => { + it('appears after scrolling', async () => { + render( + + + + +
Tall content
+
+
+
+
, + ); + // Simulate scroll + Object.defineProperty(window, 'pageYOffset', { value: 300, writable: true }); + window.dispatchEvent(new Event('scroll')); + expect(await screen.findByLabelText('Scroll to top')).toBeInTheDocument(); + }); +}); + diff --git a/tests/features/ImageOverlay.test.tsx b/tests/features/ImageOverlay.test.tsx new file mode 100644 index 0000000..5313278 --- /dev/null +++ b/tests/features/ImageOverlay.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { BlogIndividualPost } from '@/pages/BlogIndividualPost/BlogIndividualPost'; +import { http, HttpResponse } from 'msw'; +import { server } from '../msw/server'; + +describe('Image overlay', () => { + it('opens lightbox when clicking post image', async () => { + server.use( + http.get('/arbitrary/BLOG/:user/:blog', ({ params }) => + HttpResponse.json({ title: 'My Blog', blogId: params.blog }), + ), + http.get('/arbitrary/BLOG_POST/:user/:fullId', ({ params }) => { + const { fullId, user } = params as any; + if (typeof fullId === 'string' && fullId.includes('-post-') && user) { + return HttpResponse.json({ + title: 'Post With Image', + createdAt: 1, + postContent: [ + { type: 'image', id: 'img1', version: 1, content: { image: 'data:' } }, + ], + }); + } + return HttpResponse.json({}, { status: 404 }); + }), + http.get('/arbitrary/resources/search', () => HttpResponse.json([])), + ); + + render( + + + + + } /> + + + + , + ); + + const img = await screen.findByRole('img', { name: /image/i }); + fireEvent.click(img); + expect(await screen.findByTestId('image-lightbox-overlay')).toBeInTheDocument(); + }); +}); + diff --git a/tests/pages/Bookmarks.test.tsx b/tests/pages/Bookmarks.test.tsx new file mode 100644 index 0000000..f9b3357 --- /dev/null +++ b/tests/pages/Bookmarks.test.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { Provider } from 'react-redux'; +import { store } from '@/state/store'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { render, screen, fireEvent, within } from '@testing-library/react'; +import BookmarksPage from '@/pages/Bookmarks/Bookmarks'; +import { BlogIndividualPost } from '@/pages/BlogIndividualPost/BlogIndividualPost'; +import { http, HttpResponse } from 'msw'; +import { server } from '../msw/server'; + +describe('Bookmarks', () => { + it('saves a post bookmark and shows it in the list', async () => { + server.use( + http.get('/arbitrary/BLOG/:user/:blog', ({ params }) => + HttpResponse.json({ title: 'My Blog', blogId: params.blog }), + ), + http.get('/arbitrary/BLOG_POST/:user/:fullId', ({ params }) => { + const { fullId, user } = params as any; + if (typeof fullId === 'string' && fullId.includes('-post-') && user) { + return HttpResponse.json({ + title: 'Bookmark Me', + createdAt: 1, + postContent: [ + { type: 'editor', id: 'e1', version: 1, content: [{ text: 'Body' }] }, + ], + }); + } + return HttpResponse.json({}, { status: 404 }); + }), + http.get('/arbitrary/resources/search', () => HttpResponse.json([])), + ); + + render( + + + + + } /> + } /> + + + + , + ); + + // Wait for post title + expect(await screen.findByText('Bookmark Me')).toBeInTheDocument(); + // Click save to bookmarks + const saveBtn = await screen.findByLabelText('Save to bookmarks'); + fireEvent.click(saveBtn); + // Navigate to bookmarks within the same router and verify + const { container } = render( + + + + + } /> + + + + , + ); + // Assert within the Bookmarks render only + expect(within(container).getByText('Bookmarks')).toBeInTheDocument(); + expect(within(container).getByText('Bookmark Me')).toBeInTheDocument(); + }); +}); -- 2.43.0 From 738341020bcf591cc1461d8d3f0f0d0ac4ee124a Mon Sep 17 00:00:00 2001 From: greenflame089 Date: Sat, 30 Aug 2025 07:53:19 -0400 Subject: [PATCH 41/42] =?UTF-8?q?chore:=200.3.0=20polish=20=E2=80=94=20boo?= =?UTF-8?q?kmarks=20sync,=20menu=20UX,=20tests,=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RELEASE_NOTES_v0.3.0.md | 4 + docs/USER_ANNOUNCEMENT_v0.3.0.md | 1 + src/components/common/ImageLightbox.tsx | 3 +- src/components/common/ScrollToTop.tsx | 1 - src/components/layout/Navbar/Navbar.tsx | 73 +++++++++++++++---- .../BlogIndividualPost/BlogIndividualPost.tsx | 10 +-- src/pages/BlogList/PostPreview.tsx | 7 +- src/pages/Bookmarks/Bookmarks.tsx | 58 ++++++++++++--- src/pages/CreatePost/CreatePostBuilder.tsx | 33 +++------ src/pages/CreatePost/CreatePostMinimal.tsx | 33 +++------ src/state/features/bookmarksSlice.ts | 8 +- src/utils/migrateFavoritesToBookmarks.ts | 8 +- tests/components/ScrollToTop.test.tsx | 1 - tests/features/ImageOverlay.test.tsx | 5 +- tests/pages/Bookmarks.test.tsx | 4 +- tsconfig.json | 3 +- 16 files changed, 155 insertions(+), 97 deletions(-) diff --git a/docs/RELEASE_NOTES_v0.3.0.md b/docs/RELEASE_NOTES_v0.3.0.md index 4a445f4..0fcdd27 100644 --- a/docs/RELEASE_NOTES_v0.3.0.md +++ b/docs/RELEASE_NOTES_v0.3.0.md @@ -1,12 +1,14 @@ Q‑Blog v0.3.0 — Enhancements and UX updates Summary + - Image Lightbox: Click images inside posts to view them in a full‑window overlay. Close the overlay by clicking anywhere or pressing Escape. - Scroll‑To‑Top Button: A floating button appears when scrolled down; smoothly returns to the top. - Bookmarks: Favorites is becoming Bookmarks. Your existing Favorites are automatically migrated to Bookmarks on first launch and cleared from legacy storage. Bookmarks support folders, sorting, and a dedicated management page at /bookmarks. - Header/menu: The header now organizes features as: Q‑Blog logo, search, notifications, a unified Main Menu (Create Blog/Post, My Blogs, view toggle, Subscriptions, Bookmarks, Blocked Names, Q‑Mail), and the Name selector (for switching names only). The Authenticate button is positioned next to the Menu for stable layout. Developer Details + - New Components - ImageLightbox overlay: src/components/common/ImageLightbox.tsx - ScrollToTop FAB: src/components/common/ScrollToTop.tsx @@ -28,10 +30,12 @@ Developer Details - Total tests: 33 test files exercising routes, components, features (feed, wiki canonicalization, subscriptions, posting flows), a11y basics, and the new features above Upgrade Notes + - Favorites is becoming Bookmarks: On first launch, the app migrates Favorites to Bookmarks automatically and removes the old entries from LocalForage. No manual steps required. - No breaking changes to APIs. QA Checklist + - Lightbox renders and closes cleanly on click - Scroll to top button shows after scrolling and hides at top - Bookmark icons are in sync across list, post page, and blog page views diff --git a/docs/USER_ANNOUNCEMENT_v0.3.0.md b/docs/USER_ANNOUNCEMENT_v0.3.0.md index 8dd5c86..a408f10 100644 --- a/docs/USER_ANNOUNCEMENT_v0.3.0.md +++ b/docs/USER_ANNOUNCEMENT_v0.3.0.md @@ -7,6 +7,7 @@ Q‑Blog v0.3.0 — What’s New - QA: 3 new test sets added (33 total) covering the lightbox, scroll‑to‑top, and bookmarks flows, alongside existing route, component, wiki canonicalization, subscriptions, posting, and a11y coverage. Where to find it + - Main Menu (next to the bell): Create Blog, Create Post, My Blogs (expandable), switch Tile/List view, Subscriptions, Bookmarks, Blocked Names, Q‑Mail. - Bookmark toggles: On post cards, post pages, and blog pages — changes sync everywhere. - Bookmarks page: /bookmarks (also in the Main Menu). diff --git a/src/components/common/ImageLightbox.tsx b/src/components/common/ImageLightbox.tsx index 5cd2f33..2eba407 100644 --- a/src/components/common/ImageLightbox.tsx +++ b/src/components/common/ImageLightbox.tsx @@ -32,7 +32,7 @@ const ImageLightbox: React.FC = ({ src, alt, open, onClose } }} onClick={onClose} data-testid="image-lightbox-overlay" - > + > {src && ( = ({ src, alt, open, onClose } }; export default ImageLightbox; - diff --git a/src/components/common/ScrollToTop.tsx b/src/components/common/ScrollToTop.tsx index 0406aaf..b63c557 100644 --- a/src/components/common/ScrollToTop.tsx +++ b/src/components/common/ScrollToTop.tsx @@ -37,4 +37,3 @@ const ScrollToTop: React.FC = () => { }; export default ScrollToTop; - diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index e0e2ece..7723328 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -392,15 +392,25 @@ const NavBar: React.FC = ({ anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} > - {(isAuthenticated && userName && (userBlogs?.length || 0) === 0) && ( - { dispatch(togglePublishBlogModal(true)); closeMain(); }}> + {isAuthenticated && userName && (userBlogs?.length || 0) === 0 && ( + { + dispatch(togglePublishBlogModal(true)); + closeMain(); + }} + > Create Blog )} - {(isAuthenticated && userName && (userBlogs?.length || 0) >= 1) && ( + {isAuthenticated && userName && (userBlogs?.length || 0) >= 1 && ( <> - { navigate('/post/new'); closeMain(); }}> + { + navigate('/post/new'); + closeMain(); + }} + > Create Post @@ -427,16 +437,27 @@ const NavBar: React.FC = ({ key={b.blogId} button role="menuitem" - onClick={() => { onSelectBlog && onSelectBlog(b); closeMain(); }} + onClick={() => { + onSelectBlog && onSelectBlog(b); + closeMain(); + }} > - +
); })} - { dispatch(togglePublishBlogModal(true)); closeMain(); }}> + { + dispatch(togglePublishBlogModal(true)); + closeMain(); + }} + > Create new blog @@ -445,27 +466,53 @@ const NavBar: React.FC = ({ - { toggleViewMode(); closeMain(); }}> - {viewMode === 'tile' ? 'Switch to List View' : 'Switch to Tile View'} + { + toggleViewMode(); + closeMain(); + }} + > + + {viewMode === 'tile' ? 'Switch to List View' : 'Switch to Tile View'} + - { navigate('/subscriptions'); closeMain(); }}> + { + navigate('/subscriptions'); + closeMain(); + }} + > Subscriptions - { navigate('/bookmarks'); closeMain(); }}> + { + navigate('/bookmarks'); + closeMain(); + }} + > Bookmarks - { setIsOpenModal(true); closeMain(); }}> + { + setIsOpenModal(true); + closeMain(); + }} + > Blocked Names - + Q-Mail diff --git a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx index 0c5263c..ea37d8b 100644 --- a/src/pages/BlogIndividualPost/BlogIndividualPost.tsx +++ b/src/pages/BlogIndividualPost/BlogIndividualPost.tsx @@ -481,11 +481,7 @@ export const BlogIndividualPost = () => { - dispatch( - removeBookmark({ username, href }), - ) - } + onClick={() => dispatch(removeBookmark({ username, href }))} /> @@ -806,7 +802,9 @@ export const BlogIndividualPost = () => { {blogContent?.title = ({ dispatch( addBookmark({ username, - item: { href: href!, title: blogPost.title || href!, created: Date.now(), type: 'post' }, + item: { + href: href!, + title: blogPost.title || href!, + created: Date.now(), + type: 'post', + }, }), ); }} diff --git a/src/pages/Bookmarks/Bookmarks.tsx b/src/pages/Bookmarks/Bookmarks.tsx index 827f4b5..937723b 100644 --- a/src/pages/Bookmarks/Bookmarks.tsx +++ b/src/pages/Bookmarks/Bookmarks.tsx @@ -21,7 +21,11 @@ import { CircularProgress, } from '@mui/material'; import { RootState } from '../../state/store'; -import { loadBookmarksForUser, removeBookmark, setBookmarkFolder } from '../../state/features/bookmarksSlice'; +import { + loadBookmarksForUser, + removeBookmark, + setBookmarkFolder, +} from '../../state/features/bookmarksSlice'; import { useNavigate } from 'react-router-dom'; import { CopyToClipboard } from 'react-copy-to-clipboard'; import OpenInNewIcon from '@mui/icons-material/OpenInNew'; @@ -111,7 +115,7 @@ const BookmarksPage: React.FC = () => { const sorted = React.useMemo(() => { const rows = [...bookmarks]; const dir = sortDir === 'asc' ? 1 : -1; - const getVal = (b: typeof bookmarks[number]) => { + const getVal = (b: (typeof bookmarks)[number]) => { const p = parse(b.href); switch (sortBy) { case 'folder': @@ -163,13 +167,29 @@ const BookmarksPage: React.FC = () => { - toggleSort('folder')} sx={{ cursor: 'pointer' }}>Folder {sortBy==='folder' ? (sortDir==='asc'?'▲':'▼') : ''} - toggleSort('service')} sx={{ cursor: 'pointer' }}>Service {sortBy==='service' ? (sortDir==='asc'?'▲':'▼') : ''} - toggleSort('name')} sx={{ cursor: 'pointer' }}>Name {sortBy==='name' ? (sortDir==='asc'?'▲':'▼') : ''} - toggleSort('identifier')} sx={{ cursor: 'pointer' }}>Identifier {sortBy==='identifier' ? (sortDir==='asc'?'▲':'▼') : ''} - toggleSort('title')} sx={{ cursor: 'pointer' }}>Title {sortBy==='title' ? (sortDir==='asc'?'▲':'▼') : ''} + toggleSort('folder')} sx={{ cursor: 'pointer' }}> + Folder {sortBy === 'folder' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + + toggleSort('service')} sx={{ cursor: 'pointer' }}> + Service {sortBy === 'service' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + + toggleSort('name')} sx={{ cursor: 'pointer' }}> + Name {sortBy === 'name' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + + toggleSort('identifier')} sx={{ cursor: 'pointer' }}> + Identifier {sortBy === 'identifier' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + + toggleSort('title')} sx={{ cursor: 'pointer' }}> + Title {sortBy === 'title' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + Actions - toggleSort('updated')} sx={{ cursor: 'pointer' }} align="right">Updated {sortBy==='updated' ? (sortDir==='asc'?'▲':'▼') : ''} + toggleSort('updated')} + sx={{ cursor: 'pointer' }} + align="right" + > + Updated {sortBy === 'updated' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + @@ -186,7 +206,14 @@ const BookmarksPage: React.FC = () => { {b.title} - { setEditFolderHref(b.href); setFolderValue(b.folder || ''); }}> + { + setEditFolderHref(b.href); + setFolderValue(b.folder || ''); + }} + > @@ -213,7 +240,9 @@ const BookmarksPage: React.FC = () => { - {ts ? formatDate(ts) : } + + {ts ? formatDate(ts) : } + ); })} @@ -261,7 +290,14 @@ const BookmarksPage: React.FC = () => {